diff --git a/README.md b/README.md index ea1827510..403a1b947 100644 --- a/README.md +++ b/README.md @@ -17,37 +17,34 @@ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/11245/badge)](https://www.bestpractices.dev/projects/11245) -**DFetch can manage dependencies** +**Vendor dependencies without the pain.** -We make products that can last 15+ years; because of this we want to be able to have all sources available -to build the entire project from source without depending on external resources. -For this, we needed a dependency manager that was flexible enough to retrieve dependencies as plain text -from various sources. `svn externals`, `git submodules` and `git subtrees` solve a similar -problem, but not in a VCS-agnostic way or completely user-friendly way. -We want self-contained code repositories without any hassle for end-users. -Dfetch must promote upstreaming changes, but allow for local customizations. -The problem is described thoroughly in [managing external dependencies](https://embeddedartistry.com/blog/2020/06/22/qa-on-managing-external-dependencies/) and sometimes -is also known as [*vendoring*](https://dfetch.readthedocs.io/en/latest/vendoring.html). +**Dfetch** copies source code directly into your project — no Git submodules, no SVN externals, +no hidden external links. Fetch from Git, SVN, or plain archive URLs. Dependencies live as plain, +readable files inside your own repository. You stay in full control of every line. -Dfetch supports three source types: **Git**, **SVN**, and **archive files** (`.tar.gz`, `.tgz`, `.tar.bz2`, `.tar.xz`, `.zip`). Archives can be verified with a cryptographic hash (`sha256`, `sha384`, or `sha512`) to guarantee integrity on every fetch. +Dfetch supports **Git**, **SVN**, and **archive files** (`.tar.gz`, `.tgz`, `.tar.bz2`, `.tar.xz`, `.zip`). +Archives can be verified with a cryptographic hash (`sha256`, `sha384`, or `sha512`) to guarantee +integrity on every fetch. No proprietary formats, no lock-in — switch tools any time. -Other tools that do similar things are ``Zephyr's West``, ``CMake ExternalProject`` and other meta tools. +Other tools that do similar things are Zephyr's West, CMake ExternalProject, and other meta tools. See [alternatives](https://dfetch.readthedocs.io/en/latest/alternatives.html) for a complete list. +The broader concept is known as [*vendoring*](https://dfetch.readthedocs.io/en/latest/vendoring.html). [**Getting started**](https://dfetch.readthedocs.io/en/latest/getting_started.html) | -[**Manual**](https://dfetch.readthedocs.io/en/latest/manual.html) | +[**Commands**](https://dfetch.readthedocs.io/en/latest/commands.html) | [**Troubleshooting**](https://dfetch.readthedocs.io/en/latest/troubleshooting.html) | [**Contributing**](https://dfetch.readthedocs.io/en/latest/contributing.html) -## Problems DFetch Solves +## What Dfetch Does -* Declarative code reuse across projects ([inner sourcing](https://about.gitlab.com/topics/version-control/what-is-innersource/)) -* Compose multi-repo code bases into a single working tree -* Vendoring dependencies for reproducible builds -* Apply local patches while keeping upstream syncable -* VCS-agnostic dependency management (Git, SVN, and plain archives) +* Vendor source-only dependencies — fully self-contained, no external links at build time +* VCS-agnostic: mix Git, SVN, and plain archive URLs freely in one manifest * Fetch and verify archives with cryptographic integrity checks -* Self-contained exports for releases or audits +* Apply local patches while keeping upstream syncable (`dfetch diff` / `dfetch format-patch`) +* Supply-chain ready: SBOM generation, license detection, multi-format CI reports +* Migrate from Git submodules or SVN externals in seconds (`dfetch import`) +* Declarative code reuse across projects ([inner sourcing](https://about.gitlab.com/topics/version-control/what-is-innersource/)) ## Install @@ -122,10 +119,10 @@ manifest: hash: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ``` -## Github Action +## GitHub Action -You can use DFetch in your Github Actions workflow to check your dependencies. -The results will be uploaded to Github. Add the following to your workflow file: +You can use Dfetch in your GitHub Actions workflow to check your dependencies. +The results will be uploaded to GitHub. Add the following to your workflow file: ```yaml jobs: diff --git a/dfetch.yaml b/dfetch.yaml index 3bd895bf4..7a5c9cf2d 100644 --- a/dfetch.yaml +++ b/dfetch.yaml @@ -23,3 +23,30 @@ manifest: patch: - doc/_ext/patches/001-autoformat-sphinxcontrib.asciinema.patch - doc/_ext/patches/002-fix-options-sphinxcontrib.asciinema.patch + + - name: inter-font + remote: github + vcs: archive + src: web/ + dst: doc/static/fonts/inter + repo-path: rsms/inter/releases/download/v4.1/Inter-4.1.zip + ignore: + - inter.css + - InterDisplay-* + - InterVariable* + - Inter*Italic* + - Inter-Black* + + - name: jetbrains-mono-font + remote: github + vcs: archive + src: fonts/webfonts/ + dst: doc/static/fonts/jetbrains-mono + repo-path: JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip + ignore: + - JetBrainsMono-Bold* + - JetBrainsMono-Extra* + - JetBrainsMono-Light* + - JetBrainsMono-Semi* + - JetBrainsMono-Thin* + - JetBrainsMono-*Italic* diff --git a/dfetch/commands/add.py b/dfetch/commands/add.py index b58a478d2..acd04e642 100644 --- a/dfetch/commands/add.py +++ b/dfetch/commands/add.py @@ -1,48 +1,13 @@ -"""*Dfetch* can add projects to the manifest through the CLI. +"""Add a project to the manifest from the command line. -Sometimes you want to add a project to your manifest, but you don't want to -edit the manifest by hand. With ``dfetch add`` you can add a project to your -manifest through the command line. +Use ``dfetch add `` to append a project without prompts, or +``dfetch add -i `` for the interactive wizard. +See :ref:`adding-a-project` for the full guide. -Non-interactive mode --------------------- -In the simplest form you just provide the URL:: - - dfetch add https://github.com/some-org/some-repo.git - -Dfetch fetches remote metadata (branches, tags), picks the default branch, -guesses a destination path from your existing projects, shows a preview, and -appends the entry to ``dfetch.yaml`` immediately without prompting. - -Override any field with explicit flags:: - - dfetch add --name mylib --dst ext/mylib --version v2.0 --src lib https://github.com/some-org/some-repo.git - -Interactive mode ----------------- -Use ``--interactive`` (``-i``) for a guided, step-by-step wizard:: - - dfetch add -i https://github.com/some-org/some-repo.git - -Pre-fill individual fields to skip specific prompts:: - - dfetch add -i --version main --src lib/core https://github.com/some-org/some-repo.git - -The wizard walks through: - -* **name** - defaults to the repository name extracted from the URL -* **dst** - local destination; defaults to a path guessed from existing projects -* **version** - scrollable list of all branches and tags (arrow keys to - navigate, Enter to select, Esc to fall back to free-text input) -* **src** - optional sub-path; browse the remote tree with arrow keys, - expand/collapse folders with Enter/Right/Left -* **ignore** - optional list of paths to exclude; same tree browser with - Space to toggle multiple selections and Enter to confirm +.. scenario-include:: ../features/add-project-through-cli.feature -After confirming the add you are offered to run ``dfetch update`` immediately -so the dependency is materialised without a separate command. +.. scenario-include:: ../features/interactive-add.feature -.. scenario-include:: ../features/add-project-through-cli.feature """ from __future__ import annotations diff --git a/dfetch/commands/check.py b/dfetch/commands/check.py index b42c59a08..a03f5123f 100644 --- a/dfetch/commands/check.py +++ b/dfetch/commands/check.py @@ -64,32 +64,36 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None "--no-recommendations", "-N", action="store_true", - help="Ignore recommendations from fetched projects.", + help=( + "Do not check sub-manifests found inside fetched projects. " + "By default, dfetch.yaml files discovered in fetched dependencies " + "are also checked for outdated entries." + ), ) parser.add_argument( "projects", metavar="", type=str, nargs="*", - help="Specific project(s) to check", + help="Specific project(s) to check (default: all projects in manifest)", ) parser.add_argument( "--jenkins-json", metavar="outfile", type=str, - help="Generate a JSON that can be parsed by Jenkins.", + help="Write a Jenkins warnings-ng JSON report to .", ) parser.add_argument( "--sarif", metavar="outfile", type=str, - help="Generate a Sarif JSON that can be parsed by Github.", + help="Write a SARIF 2.1.0 report to (GitHub Advanced Security).", ) parser.add_argument( "--code-climate", metavar="outfile", type=str, - help="Generate a code-climate JSON that can be parsed by Gitlab.", + help="Write a Code Climate JSON report to (GitLab pipelines).", ) def __call__(self, args: argparse.Namespace) -> None: diff --git a/dfetch/commands/diff.py b/dfetch/commands/diff.py index 1cfa9ccd2..3f226ac4f 100644 --- a/dfetch/commands/diff.py +++ b/dfetch/commands/diff.py @@ -84,7 +84,13 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None metavar="[:]", type=str, default="", - help="Revision(s) to generate diff from", + help=( + "Revision(s) to generate diff from. " + "Omit to diff from the last fetched revision to the working copy. " + "Supply one revision to use it as the start point. " + "Supply two revisions separated by ':' (e.g. abc123:def456) for an " + "explicit range." + ), ) parser.add_argument( @@ -92,7 +98,10 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None metavar="", type=str, nargs=1, - help="Project to generate diff from", + help=( + "Project to generate diff from. " + "Output is written to .patch in the superproject root." + ), ) def __call__(self, args: argparse.Namespace) -> None: diff --git a/dfetch/commands/environment.py b/dfetch/commands/environment.py index 57cb655bb..b4cace015 100644 --- a/dfetch/commands/environment.py +++ b/dfetch/commands/environment.py @@ -1,4 +1,15 @@ -"""*Dfetch* can generate output about its working environment.""" +"""*Dfetch* can report information about its working environment. + +``dfetch environment`` prints: + +* **Platform** — operating system name and kernel version. +* **VCS tool versions** — the version of each supported VCS client + (Git, SVN) found on ``PATH``. + +Run this command when setting up a new machine to confirm that the required +VCS tools are installed and discoverable, or include the output when filing +a bug report so that developers can reproduce your environment exactly. +""" import argparse import platform diff --git a/dfetch/commands/format_patch.py b/dfetch/commands/format_patch.py index ad0926242..253eeb0a0 100644 --- a/dfetch/commands/format_patch.py +++ b/dfetch/commands/format_patch.py @@ -73,7 +73,11 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None metavar="", type=str, default=".", - help="Output directory for formatted patches", + help=( + "Directory to write formatted patches to " + "(default: current working directory). " + "The output file is named formatted-.patch." + ), ) def __call__(self, args: argparse.Namespace) -> None: diff --git a/dfetch/commands/import_.py b/dfetch/commands/import_.py index 39ff43b8b..6fdc0b413 100644 --- a/dfetch/commands/import_.py +++ b/dfetch/commands/import_.py @@ -6,77 +6,7 @@ After importing, you will need to remove the submodules or externals, and then you can let Dfetch update by running :ref:`dfetch update `. -Migrating from git submodules -============================= - -* Make sure your repository is up-to-date. -* Make sure your submodules are up-to-date (``git submodules update --init``). -* Generate a manifest using :ref:`dfetch import`. -* Remove all git submodules (see `How do I remove a submodule `_ ). -* Download all your projects using :ref:`dfetch update`. -* Commit your projects as part of your project. - -.. scenario-include:: ../features/import-from-git.feature - -Switching branches -~~~~~~~~~~~~~~~~~~ -After importing submodules into a manifest within a branch, you might encounter difficulties when switching branches. -If one branch has submodules located where your *DFetched* project dependencies should be, or vice versa. -In both situations below, assume a branch ``feature/use-dfetch`` with a manifest and ``master`` with the original -submodules in their place. - -Switching from branch with submodules to branch with manifest -------------------------------------------------------------- -When switching from branch with submodules to one without, git will warn that the -*Dfetched* project dependencies will overwrite the submodule that is currently in the -same location. - -.. code-block:: console - - $ git checkout feature/use-dfetch - error: The following untracked working tree files would be overwritten by checkout: - MySubmodule/somefile.c - MySubmodule/someotherfile.c - -However, ``git status`` will show nothing: - -.. code-block:: console - - $ git status - On branch master - Your branch is up to date with 'origin/master'. - - nothing to commit, working tree clean - -To overcome this, remove the submodule folder and checkout branch ``feature/use-dfetch``: - -.. code-block:: console - - $ rm -rf MySubmodule - $ git checkout feature/use-dfetch - -Switching from branch with manifest to branch with submodules -------------------------------------------------------------- -This situation gives no problem, *but* the submodules are gone and need to be initialized again. -To solve this: - -.. code-block:: console - - $ git checkout master - $ git submodule update --init - Submodule path 'MySubmodule': checked out '08f95e01b297d8b8c1c9101bde58e75cd4d428ce' - - -Migrating from SVN externals -============================ - -* Make sure your repository is up-to-date. -* Generate a manifest using :ref:`dfetch import`. -* Remove all svn externals (see `How do I remove svn::externals `_ ). -* Download all your projects using :ref:`dfetch update`. -* Commit your projects as part of your project. - -.. scenario-include:: ../features/import-from-svn.feature +For full step-by-step migration instructions see :ref:`migration`. """ diff --git a/dfetch/commands/init.py b/dfetch/commands/init.py index f6d35fdec..4f8a13477 100644 --- a/dfetch/commands/init.py +++ b/dfetch/commands/init.py @@ -1,6 +1,13 @@ """*Dfetch* can generate a starting manifest. -It will be created in the current folder. +Running ``dfetch init`` creates a ``dfetch.yaml`` file in the current +directory. The file contains a minimal template that you can open and edit +directly, or populate incrementally using :ref:`dfetch add `. + +Once you have listed your dependencies, fetch them with :ref:`dfetch update `. + +If a ``dfetch.yaml`` already exists in the current directory, *Dfetch* +prints a warning and exits without overwriting it. """ import argparse diff --git a/dfetch/commands/report.py b/dfetch/commands/report.py index 18d637634..ee3610a1d 100644 --- a/dfetch/commands/report.py +++ b/dfetch/commands/report.py @@ -1,6 +1,28 @@ -"""*Dfetch* can generate multiple reports. - -There are several report types that *DFetch* can generate. +"""*Dfetch* can generate reports about the projects in your manifest. + +Two report types are available via the ``-t`` / ``--type`` flag: + +``stdout`` (default) + Prints a dependency inventory to the terminal. For each project it shows + the remote URL, branch/tag/revision, last-fetch timestamp, applied patches, + and any licences detected in the fetched source tree. + +``sbom`` + Generates a `CycloneDX 1.6 `_ Software Bill of + Materials (SBOM) as a JSON file (``report.json`` by default, override with + ``-o``). The SBOM includes package URLs (PURLs), VCS references, licence + evidence, and — for archive projects — an optional SHA-256 integrity hash. + + This can be uploaded to GitHub as a supply-chain asset or attached to a + GitLab pipeline as a ``cyclonedx`` artefact. + +Licence detection +~~~~~~~~~~~~~~~~~ +*Dfetch* scans each fetched project for common licence files (``LICENSE``, +``COPYING``, etc.) and uses a best-effort heuristic to identify the licence +type. Only matches with a confidence of 80 % or higher are reported; ambiguous +files are silently skipped. If no licence is detected, the field is left +empty rather than guessing. """ import argparse diff --git a/dfetch/commands/update.py b/dfetch/commands/update.py index 6b6ec505b..1abe63fa5 100644 --- a/dfetch/commands/update.py +++ b/dfetch/commands/update.py @@ -1,52 +1,18 @@ -"""Update is the main functionality of dfetch. +"""Fetch every project listed in the manifest. -You can add Projects to your :ref:`Manifest` and update will fetch the version specified. -It tries to determine what kind of vcs it is: git, svn or something else. +VCS type (Git, SVN, archive) is detected automatically. +See :ref:`updating-projects` for the full guide. .. uml:: /static/uml/update.puml -.. tabs:: +.. scenario-include:: ../features/fetch-git-repo.feature - .. tab:: Git +.. scenario-include:: ../features/fetch-svn-repo.feature - .. scenario-include:: ../features/fetch-git-repo.feature - - .. tab:: SVN - - .. scenario-include:: ../features/fetch-svn-repo.feature - - .. tab:: Archive - - .. scenario-include:: ../features/fetch-archive.feature - -Sub-manifests -~~~~~~~~~~~~~~~ - -It is possible that fetched projects have manifests of their own. -When these projects are fetched (with ``dfetch update``), the manifests are read as well -and will be checked to look for further dependencies. If you don't what recommendations, you can prevent *Dfetch* -checking sub-manifests with ``--no-recommendations``. +.. scenario-include:: ../features/fetch-archive.feature .. scenario-include:: ../features/updated-project-has-dependencies.feature -Git submodules -~~~~~~~~~~~~~~ - -When a git dependency itself contains git submodules, *Dfetch* fetches and resolves -them automatically, no extra manifest entries or ``git submodule`` commands are needed. - -Each submodule is checked out at the exact revision pinned by the parent repository. -*Dfetch* reports every resolved submodule in the update output:: - - Dfetch (0.12.1) - my-project: - > Found & fetched submodule "./ext/vendor-lib" (https://github.com/example/vendor-lib @ master - 79698c9…) - > Fetched master - e1fda19… - -Nested submodules (submodules of submodules) are resolved recursively. The pinned -details for each submodule are recorded in the ``.dfetch_data.yaml`` metadata file -and are visible in :ref:`Report`. - .. scenario-include:: ../features/fetch-git-repo-with-submodule.feature """ @@ -84,20 +50,28 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None "-f", "--force", action="store_true", - help="Always perform update, ignoring version check or local changes.", + help=( + "Re-fetch and overwrite even if the project is already at the " + "requested version or has local modifications. " + "Any unsaved local changes in the destination directory will be lost." + ), ) parser.add_argument( "-N", "--no-recommendations", action="store_true", - help="Ignore recommendations from fetched projects.", + help=( + "Do not check sub-manifests found inside fetched projects. " + "By default, dfetch.yaml files discovered in fetched dependencies " + "are also checked for outdated entries." + ), ) parser.add_argument( "projects", metavar="", type=str, nargs="*", - help="Specific project(s) to update", + help="Specific project(s) to update (default: all projects in manifest)", ) def __call__(self, args: argparse.Namespace) -> None: diff --git a/dfetch/commands/validate.py b/dfetch/commands/validate.py index 4845f23a3..f31f49be5 100644 --- a/dfetch/commands/validate.py +++ b/dfetch/commands/validate.py @@ -1,6 +1,18 @@ -"""Note that you can validate your manifest using :ref:`validate`. +"""*Dfetch* can validate a manifest without fetching anything. -This will parse your :ref:`Manifest` and check if all fields can be parsed. +``dfetch validate`` parses ``dfetch.yaml`` and checks every field against the +manifest schema — project names, URLs, version strings, source paths, and +integrity hashes are all verified. Any structural or type error is reported +immediately with a clear message pointing at the offending field. + +This is useful in CI to catch manifest mistakes before a full ``dfetch update`` +run, or as a quick sanity-check after hand-editing the file. + +.. note:: + + Validation also runs automatically at the start of every ``dfetch update`` + and ``dfetch check`` — a separate ``dfetch validate`` step is only needed + when you want to check the manifest without triggering a fetch. .. scenario-include:: ../features/validate-manifest.feature diff --git a/doc/_ext/scenario_directive.py b/doc/_ext/scenario_directive.py index d1ff07ccd..6361654c4 100644 --- a/doc/_ext/scenario_directive.py +++ b/doc/_ext/scenario_directive.py @@ -59,10 +59,19 @@ def list_of_scenarios(self, feature_file_path: str) -> Tuple[str]: def run(self): """Generate the same literalinclude block for every scenario.""" + env = self.state.document.settings.env feature_file = self.arguments[0].strip() scenarios_available = self.list_of_scenarios(feature_file) + # Compute a path for literalinclude that is relative to the current + # RST document's directory. literalinclude resolves relative to the + # source file, while list_of_scenarios uses srcdir as the base — these + # differ once RST files are in sub-directories. + feature_abs = os.path.abspath(os.path.join(env.app.srcdir, feature_file)) + current_doc_dir = os.path.dirname(os.path.join(env.app.srcdir, env.docname)) + include_path = os.path.relpath(feature_abs, current_doc_dir) + scenario_titles = [ title.strip() for title in self.options.get("scenario", "").splitlines() @@ -84,7 +93,7 @@ def run(self):
Example: {html.escape(scenario_title)} -.. literalinclude:: {feature_file} +.. literalinclude:: {include_path} :language: gherkin :caption: {feature_file} :force: diff --git a/doc/conf.py b/doc/conf.py index 62f56e426..8e52d03b1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,6 +34,7 @@ # ones. extensions = [ "sphinx_sitemap", + "sphinx_design", "plantweb.directive", "scenario_directive", "sphinx.ext.autodoc", @@ -96,7 +97,10 @@ autosectionlabel_maxdepth = 3 # Suppress warnings about duplicate labels from argparse directive -suppress_warnings = ["autosectionlabel.manual"] +suppress_warnings = [ + "autosectionlabel.reference/commands", + "autosectionlabel.howto/updating-projects", +] # Options for sphinx-autoissues issuetracker = "github" @@ -122,6 +126,10 @@ "css/custom.css", ] +html_js_files = [ + "js/diataxis.js", +] + html_logo = "images/dfetch_logo.png" # Theme options are theme-specific and customize the look and feel of a theme diff --git a/doc/contributing.rst b/doc/contributing.rst deleted file mode 100644 index 23967f3c6..000000000 --- a/doc/contributing.rst +++ /dev/null @@ -1,129 +0,0 @@ - - -Contributing -============ -Before implementing a feature please ask one of the maintainers to avoid any unnecessary or double work. -Let other people know through the relevant GitHub issue that you are planning on implementing it. -Also for new features, first create an issue that can be discussed. - -After implementing (with tests and documentation) create a PR on Github and let your changes be reviewed. - -Virtual Environment -------------------- -Create a virtual environment by double-clicking ``script/create_venv.py`` or by running the following command. -This will install all ``development``, ``test`` and ``docs`` dependencies from ``pyproject.toml``, install -*DFetch* as `editable package `_ -and install all runtime dependencies from ``pyproject.toml``. - -.. code-block:: bash - - script/create_venv.py - -.. important :: *dfetch* is primarily developed with python 3.13 - -Running in Github Codespaces ----------------------------- -Github codespaces make it possible to edit dfetch directly in the browser in a VSCode instance. -All dependencies are pre-installed and makes it easy to get started. - -|CodespacesLink|_ - -.. |CodespacesLink| image:: https://github.com/codespaces/badge.svg -.. _CodespacesLink: https://codespaces.new/dfetch-org/dfetch - -.. tip:: - - You can preview the documentation locally by running the ``Serve Docs`` task. - Codespaces will automatically suggest to open the forwarded port to view the - changes in your browser. - -Running in VSCode ------------------ -To debug or run directly from VSCode create the :ref:`virtual environment`. -Select the python from the virtual environment. -And open the ``dfetch.code-workspace``. - -Quality -------- -To avoid any discussion about formatting `black `_ is used as code formatter. -Next to that `isort `_ is used for sorting the imports. -And `doc8 `_ is used as rst linter. - -`import-linter `_ is used to guard the :ref:`architecture` by verifying -that imports between modules respect the C4 layer boundaries. Run it with: - -.. code-block:: bash - - lint-imports - -Run ``script/check_quality.bat`` (or GitHub will run it for you). Alternatively when using VSCode run the `Check Quality` task from the command palette. - -Testing -------- - -Unit tests -~~~~~~~~~~ -`pytest `_ is used as testing framework. All code contributed should be accompanied with unit tests. -Tests can be found in the ``tests`` folder. - -To see coverage, in the virtual environment run ``pytest`` with coverage - -.. code-block:: bash - - pytest --cov=dfetch tests - -From VSCode all tests can be run from the Test view. - -Feature tests -~~~~~~~~~~~~~ -Feature tests are used for higher-level integration testing of functionality. -For this `behave `_ is used as testing framework. -Features are specified in *Gherkin* in so-called feature files in the ``features`` folder. -The sentences in the feature files, map to steps in the ``features/steps`` folder. - -Test can be run directly from the command-line - -.. code-block:: bash - - behave features - -Alternatively in VSCode run the ``Run all feature tests`` task from the command palette. - -To debug these tests, mark the ``Feature:`` or ``Scenario:`` to debug with the ``@wip`` tag -and add run the ``Feature tests (wip)`` debug configuration in VSCode. - - -Creating documentation ----------------------- -Run ``script/create_docs.bat`` and open ``index.html`` in ``doc/_build/html`` to read it. -See `This example `_ for documenting the code. -Alternatively in VSCode run the ``Create Docs`` task from the command palette. - -Releasing ---------- - -- Bump version number in ``dfetch/__init__.py``. -- Run ``./script/release.py``. -- Double check any feature scenarios for a version number. -- Run all unit / feature tests. -- Re-generate asciicasts using VSCode task (linux/mac). -- Commit all release changes. -- Create pull request & merge to ``main``. -- Create annotated tag on ``main`` and push it: - -.. code-block:: bash - - git checkout main - git pull - git tag -a '0.12.1' -m "Release version 0.12.1" - git push --tags - -- The ``ci.yml`` job will automatically create a draft release in `GitHub Releases `_ with all artifacts. -- Once the release is published, a new package is automatically pushed to `PyPi `_. - -- After release, add new header to ``CHANGELOG.rst``: - -.. code-block:: rst - - Release 0.13.0 (unreleased) - ==================================== diff --git a/doc/alternatives.rst b/doc/explanation/alternatives.rst similarity index 100% rename from doc/alternatives.rst rename to doc/explanation/alternatives.rst diff --git a/doc/internal.rst b/doc/explanation/internal.rst similarity index 100% rename from doc/internal.rst rename to doc/explanation/internal.rst diff --git a/doc/explanation/vendoring.rst b/doc/explanation/vendoring.rst new file mode 100644 index 000000000..ffb84b0ec --- /dev/null +++ b/doc/explanation/vendoring.rst @@ -0,0 +1,474 @@ + + +Vendoring +========= + +Vendoring is the practice of copying the source code of another project directly +into your own project's repository. Instead of relying on a package manager to +fetch dependencies at build or install time, the dependency code is stored +alongside the project itself and treated as part of the source tree. + +Although the definition is simple, vendoring has long been controversial. Some +engineers see it as a practical way to gain control and reliability, while others +have historically described it as an anti-pattern. Both views exist for good +reasons, and understanding the trade-offs matters more than choosing a side. + + +What People Mean by Vendoring +------------------------------ + +At a basic level, vendoring means that a project can be built using only what is +present in its repository. No network access is required, no external registries +need to be available, and no global package cache is assumed. Checking out a +specific revision of the repository is sufficient to reproduce a build from that +point in time. + +The term originally became common in communities that explicitly placed third- +party code into directories named ``vendor``. Over time, the word broadened to +describe any approach where dependency source code is copied into the project, +regardless of directory layout or tooling. + +Vendoring does not necessarily imply permanent forks or heavy modification of +third-party code. In many cases, vendored dependencies are kept pristine and +updated mechanically from upstream sources. + +---- + +.. div:: band-tint + + :material-regular:`thumb_up;2em;sd-text-primary` **Why vendoring can be helpful** + + .. grid:: 1 1 2 2 + :gutter: 3 + + .. grid-item-card:: :material-regular:`replay;2em` Reproducibility + :text-align: center + :class-card: stat-card + + When dependencies are fetched dynamically, builds implicitly depend on the + availability of external services. Vendoring makes build inputs **explicit and + local** — no registry, no CDN, no network required. + + .. grid-item-card:: :material-regular:`visibility;2em` Visibility + :text-align: center + :class-card: stat-card + + Dependency code lives in the same source tree. + Easier to **inspect, debug, and understand** — no context switching, + no tooling required to fetch sources on demand. + + .. grid-item-card:: :material-regular:`security;2em` Supply-chain trust + :text-align: center + :class-card: stat-card + + Vendoring shifts trust from live infrastructure to explicit review. + The project decides **when** dependencies are updated and **exactly what + code** is being included. + + .. grid-item-card:: :material-regular:`tune;2em` Healthy friction + :text-align: center + :class-card: stat-card + + When adding a dependency requires consciously pulling in its source code, + teams become **more selective** and more aware of the long-term cost of + that decision. + +---- + +.. div:: band-mint + + :material-regular:`warning;2em;sd-text-primary` **The costs and risks of vendoring** + + .. grid:: 1 1 2 2 + :gutter: 3 + + .. grid-item-card:: :material-regular:`history_toggle_off;2em` History loss + :text-align: center + :class-card: stat-card + + When code is copied into another repository, its original commit history, + tags, and branches are no longer directly visible. This can make updates + **harder**, especially if vendored code has been locally modified. + + .. grid-item-card:: :material-regular:`call_split;2em` Divergence risk + :text-align: center + :class-card: stat-card + + When dependency code is nearby and easy to change, there is a temptation + to patch it locally rather than contribute fixes upstream. Over time this + can create **silent forks** that are difficult to reconcile. + + .. grid-item-card:: :material-regular:`storage;2em` Repository size + :text-align: center + :class-card: stat-card + + Vendored dependencies increase clone size and can **dominate diffs** + during updates. Large dependency refreshes can obscure meaningful changes + to the project's own code, making review and merging harder. + + .. grid-item-card:: :material-regular:`build;2em` Maintenance burden + :text-align: center + :class-card: stat-card + + Vendored dependencies do not update themselves. Security fixes, bug fixes, + and compatibility updates must be **pulled in manually**. Without a clear + policy and tooling support, vendored code can easily become outdated. + + +Transitive Dependencies +----------------------- + +:material-regular:`account_tree;1.2em;sd-text-primary` Vendoring becomes significantly more complex once transitive dependencies are +considered. + +Vendoring a single library often requires vendoring everything that library +depends on, and everything those dependencies rely on in turn. For ecosystems +with deep or fast-moving dependency graphs, this can quickly become a substantial +burden. + +Package managers largely exist to manage this complexity by resolving dependency +graphs, handling version conflicts, and sharing common dependencies across +projects. Vendoring replaces that automation with explicit ownership, which is +sometimes desirable and sometimes overwhelming. + + +A Brief History +--------------- + +:material-regular:`history;1.2em;sd-text-primary` Vendoring predates modern package managers. + +Early C and C++ projects routinely shipped third-party libraries inline because +there was no reliable way to depend on system-installed packages. Many Unix +programs were distributed as self-contained source releases that included all +required code. + +As centralized package registries and dependency managers matured, vendoring +became less common and was sometimes criticized as outdated or unprofessional. +Automated updates, shared caches, and smaller repositories were seen as clear +wins. + +Interest in vendoring returned as software supply chain risks became more visible. +Registry outages, dependency hijacks, and ecosystem fragility highlighted the +costs of relying entirely on external infrastructure. + +Today, vendoring is best understood as a trade-off rather than a relic. + + +Vendoring Across Languages +-------------------------- + +:material-regular:`code;1.2em;sd-text-primary` Different language ecosystems have adopted vendoring to very different degrees. + +.. grid:: 1 1 3 3 + :gutter: 2 + + .. grid-item-card:: Go + :text-align: center + :class-card: stat-card + + :material-regular:`check_circle;1.5em;sd-text-primary` + + Strongly associated with vendoring. + First-class workflow via ``go mod vendor``. + *"A little copying is better than a little dependency."* + + .. grid-item-card:: Rust + :text-align: center + :class-card: stat-card + + :material-regular:`check_circle;1.5em;sd-text-primary` + + Supported but not the default. Common in **embedded, regulated, and + long-term-support** projects where reproducibility is paramount. + + .. grid-item-card:: C / C++ + :text-align: center + :class-card: stat-card + + :material-regular:`check_circle;1.5em;sd-text-primary` + + **Frequent** vendoring due to the lack of a universal package manager, + ABI compatibility concerns, and platform differences. + + .. grid-item-card:: JavaScript + :text-align: center + :class-card: stat-card + + :material-regular:`warning;1.5em` + + ``node_modules`` is local, but **rarely committed** due to size and churn. + Fully vendoring is possible but uncommon. + + .. grid-item-card:: Python + :text-align: center + :class-card: stat-card + + :material-regular:`warning;1.5em` + + Mixed history. Common in **small tools and embedded environments**. + Modern development more often relies on virtual environments and lock files. + + .. grid-item-card:: Java / JVM + :text-align: center + :class-card: stat-card + + :material-regular:`remove_circle;1.5em` + + Rarely vendored. Maven and Gradle ecosystems rely heavily on remote + repositories and dependency resolution. + + +Conclusion +---------- + +.. card:: + :class-card: card-tinted + + :material-regular:`balance;2em;sd-text-primary` **Vendoring is neither a best practice nor an anti-pattern.** + + It is a deliberate trade-off that exchanges convenience and automatic updates for + control, predictability, and independence from external systems. In some contexts, + that trade is clearly worthwhile. In others, it introduces more cost than benefit. + + Used intentionally and with an understanding of its limitations, vendoring is + simply one tool among many for managing dependencies. + +---- + +Best Practices +-------------- + +The following practices are drawn from our own usage of *Dfetch* and real-world +policies and respected guidelines. They *mitigate* vendoring risks; they do not +eliminate them. + +.. card:: :material-regular:`push_pin;1.5em;sd-text-primary` Explicit Version Pinning and Provenance + + Every vendored dependency must be pinned to an explicit version, tag, or commit, + and its source must be documented. + + **Rationale** Vendored code is often added once and then forgotten. Without + automation, vulnerabilities, license issues, and inconsistencies can persist + unnoticed long after initial inclusion. + + * pip: ``vendor.txt`` tracks versions and sources + * Go/Kubernetes: ``go.mod``, ``go.sum``, ``vendor/modules.txt`` + * Cargo: ``Cargo.lock`` + vendored sources + * Guidelines: `OWASP SCVS `_, OpenSSF, NIST SP 800-161, SLSA + + *Dfetch* addresses this by having a declarative :ref:`manifest` and the option to :ref:`freeze` + dependencies to make each revision explicit. + +.. card:: :material-regular:`offline_bolt;1.5em;sd-text-primary` Reproducible and Offline Builds + + Vendoring must enable fully reproducible and offline builds. + + **Rationale** The point of vendoring code is to remove external dependencies. + By making sure an offline build succeeds, it is proven that builds are not + dependent on external sources. + + * Go: committed ``vendor/`` + * Cargo: ``.cargo/config.toml`` + ``cargo vendor`` + * pip: vendored wheels and pure-Python dependencies + * Guidelines: SLSA, OpenSSF + + *Dfetch* doesn't directly address this — this is a policy to follow. + +.. card:: :material-regular:`verified;1.5em;sd-text-primary` Trust Upstream After Due Diligence + + Vendored dependencies are not reviewed line-by-line; upstream unit tests are not + run. Do not auto-format vendored code. + + **Rationale** Reviewing every line of external code is costly and rarely effective. + By performing due diligence on upstream sources, you can trust their correctness + and security while minimising maintenance burden. + + Reviewers should verify: + + * Version changes and pinning + * Changelogs and release notes for regressions or security issues + * License compatibility + * CI status and test results of the upstream project + * Evidence of active maintenance and community support + + Do not run upstream unit tests locally, apply formatting or style changes, or + make cosmetic changes to vendored code. + + Aligned with: OWASP SCVS · Google Open Source Security Guidelines · OpenSSF Best Practices. + + *Dfetch* supports this approach via its manifest and metadata file + (``.dfetch_data.yaml``), which can be reviewed independently of the vendored code itself. + +.. card:: :material-regular:`compare_arrows;1.5em;sd-text-primary` Separation of Vendor Updates from Product Changes + + Vendored dependency updates must be isolated from functional code changes. + + * Separate PRs or commits + * Clear commit messages documenting versions and rationale + + **Rationale** Separation reduces review noise, letting maintainers focus on + meaningful changes. + + *Dfetch* doesn't address this directly. + +.. card:: :material-regular:`edit_off;1.5em;sd-text-primary` Minimize Local Modifications + + Vendored code must not be modified directly unless unavoidable. + + If modifications are required: + + * Document patches explicitly + * Prefer patch/overlay mechanisms + * Upstream changes whenever possible + * Cargo: ``[patch]`` mechanism + * Guidelines: Google OSS, OWASP, OpenSSF + + **Rationale** Keeping the vendored dependency identical to upstream makes it easy + to follow upstream updates. Upstreaming any changes lets others in the wider + community benefit from your fixes. + + *Dfetch* addresses this by providing a ``dfetch diff`` (:ref:`Diff`) command and a + ``patch`` (:ref:`Patch`) attribute in the manifest. It also has a CI check to detect + local changes using :ref:`Check`. + +.. card:: :material-regular:`verified_user;1.5em;sd-text-primary` Continuous Automation and Security Scanning + + Vendored dependencies must be continuously verified through automation. + + * CI verifies vendor consistency + * Dependency and CVE scanning + * SBOM generation + + **Rationale** By copy-pasting a dependency, there may be silent security + degradation since there are no automatic updates. + + *Dfetch* addresses this by providing a ``dfetch check`` (:ref:`Check`) command to + see if vendored dependencies are out-of-date and various report formats (including + SBoM) to check vulnerabilities. + +.. card:: :material-regular:`gavel;1.5em;sd-text-primary` Track License and Legal Information + + All vendored dependencies must have their license and legal information explicitly + recorded. + + **Rationale** Ensuring license compliance prevents legal issues and maintains + compatibility with your project's license. + + * Track license type for each vendored dependency + * Use machine-readable formats where possible (e.g. SPDX identifiers) + * Include license documentation alongside vendored code + * Guidelines: OWASP, OpenSSF, Google OSS best practices + + *Dfetch* addresses this by retaining the license file even when only a subfolder + is fetched. + +.. card:: :material-regular:`filter_alt;1.5em;sd-text-primary` Vendor Only What You Need + + Minimise vendored code to what is strictly necessary for your project. + + **Rationale** Vendoring unnecessary code increases maintenance burden, security + risk, and potential for patch rot. Include only the specific modules, packages, + subfolder, or components your project depends on. + + *Dfetch* enables this by allowing you to fetch only a subfolder using the ``src:`` + attribute. + +.. card:: :material-regular:`workspaces;1.5em;sd-text-primary` Isolate Vendored Dependencies + + Vendored dependencies must be clearly isolated from first-party code. + + **Rationale** Isolation prevents accidental coupling, avoids namespace conflicts, + and makes audits, updates, and removals easier. Vendored code should be + unmistakably identifiable as third-party code. + + * Place vendored dependencies in a clearly named directory (e.g. ``vendor/``, ``_vendor/``) + * Avoid mixing vendored code with product or library sources + * Use language-supported namespace or module isolation where available + * Keep vendored code mechanically separable to enable future un-vendoring + + Follows established practices in: Go (``vendor/``) · pip (``pip/_vendor``) · Cargo (``vendor/``) · Google OSS / OpenSSF. + + *Dfetch* enables this by allowing you to store the vendored dependency in a folder + using the ``dst:`` attribute. + +---- + +.. div:: band-tint + + :material-regular:`public;2em;sd-text-primary` **Real-world projects using vendoring** + + .. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item-card:: Dynaconf (Python) + :link: https://github.com/dynaconf/dynaconf/tree/master/dynaconf/vendor + :link-type: url + + :material-regular:`code;1.2em` Python configuration management library. + + .. grid-item-card:: pip (Python) + :link: https://github.com/pypa/pip/tree/main/src/pip/_vendor + :link-type: url + + :material-regular:`code;1.2em` Python's own package installer vendors its dependencies. + + .. grid-item-card:: Kubernetes (Go) + :link: https://github.com/kubernetes/kubernetes/tree/master/vendor + :link-type: url + + :material-regular:`code;1.2em` The industry-standard container orchestrator. + + .. grid-item-card:: Cargo (Rust) + :link: https://doc.rust-lang.org/cargo/commands/cargo-vendor.html + :link-type: url + + :material-regular:`code;1.2em` Rust's package manager supports vendoring natively. + + +.. div:: band-mint + + :material-regular:`bolt;2em;sd-text-primary` **Real-world projects using Dfetch** + + .. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item-card:: Dfetch + :link: https://github.com/dfetch-org/dfetch + :link-type: url + + :material-regular:`hub;1.2em` Dfetch uses itself to vendor its own test fixtures. + + .. grid-item-card:: ModbusScope + :link: https://github.com/ModbusScope/ModbusScope + :link-type: url + + :material-regular:`hub;1.2em` Industrial Modbus visualisation tool. + + .. grid-item-card:: Example Yocto + :link: https://github.com/dfetch-org/example-yocto + :link-type: url + + :material-regular:`hub;1.2em` Dfetch in a Yocto/embedded Linux build. + + .. grid-item-card:: Example Zephyr + :link: https://github.com/dfetch-org/example-zephyr + :link-type: url + + :material-regular:`hub;1.2em` Dfetch in a Zephyr RTOS project. + + Internally we use *Dfetch* for various projects and uses. + + +Further Reading +--------------- + +:material-regular:`menu_book;1.2em;sd-text-primary` + +* `Vendoring is a vile anti pattern — Michael F. Lamb `_ +* `SO: What is "vendoring"? — Niels Bom `_ +* `Why we stopped vendoring our dependencies — Carlos Perez `_ +* `Vendoring — Carson Gross `_ +* `Our Software Dependency Problem — Russ Cox `_ +* `On Managing External Dependencies — Phillip Johnston `_ +* `PIP's vendoring policy `_ +* `SubPatch benefits `_ diff --git a/doc/howto/adding-a-project.rst b/doc/howto/adding-a-project.rst new file mode 100644 index 000000000..d956ae884 --- /dev/null +++ b/doc/howto/adding-a-project.rst @@ -0,0 +1,116 @@ + +.. _adding-a-project: + +Add a project +============= + +There are three ways to add a new dependency to your manifest — edit it +directly, use the ``dfetch add`` command, or use the interactive wizard +``dfetch add -i``. + +- :ref:`adding-manifest` — write the entry by hand for full control +- :ref:`adding-add` — one command, no prompts +- :ref:`adding-interactive` — guided wizard with branch/tag browsing + +.. _adding-manifest: + +Editing the manifest directly +------------------------------ + +Open ``dfetch.yaml`` and add a new entry under ``projects``. At minimum you +need a ``name`` and a ``url``: + +.. code-block:: yaml + + manifest: + version: '0.0' + projects: + - name: mylib + url: https://github.com/example/mylib.git + +Pin to a tag or branch with ``tag`` or ``branch``: + +.. code-block:: yaml + + manifest: + version: '0.0' + projects: + - name: mylib + url: https://github.com/example/mylib.git + tag: v1.2.3 + + - name: myother + url: https://github.com/example/myother.git + branch: main + dst: ext/myother # optional destination folder + +After saving the file, run ``dfetch update`` to fetch the new dependency. +See :ref:`Manifest` for the full list of project attributes. + +.. _adding-add: + +Using ``dfetch add`` +--------------------- + +Pass the repository URL to ``dfetch add`` and it will append a new entry to +``dfetch.yaml`` without any prompts. *Dfetch* fetches remote metadata +(branches and tags), selects the default branch, and guesses a destination +path based on your existing projects. + +.. code-block:: sh + + dfetch add https://github.com/some-org/some-repo.git + +.. asciinema:: ../asciicasts/add.cast + +Override individual fields with flags: + +.. code-block:: sh + + dfetch add \ + --name mylib \ + --dst ext/mylib \ + --version v2.0 \ + --src lib \ + https://github.com/some-org/some-repo.git + +After ``dfetch add`` finishes, run ``dfetch update`` to fetch the newly added +project. + +.. _adding-interactive: + +Using ``dfetch add -i`` (interactive wizard) +-------------------------------------------- + +The ``--interactive`` (``-i``) flag starts a step-by-step wizard. Use it +when you want to browse available branches and tags, choose a sub-directory +inside the remote repository, or configure which paths to ignore. + +.. code-block:: sh + + dfetch add -i https://github.com/some-org/some-repo.git + +.. asciinema:: ../asciicasts/interactive-add.cast + +The wizard walks through each field in turn: + +* **name** — defaults to the repository name from the URL +* **dst** — local destination folder; defaults to a path guessed from your + existing projects +* **version** — scrollable list of all remote branches and tags (arrow keys + to navigate, Enter to select, Esc to fall back to free-text input) +* **src** — optional sub-path inside the remote; browse the remote tree with + arrow keys and expand/collapse folders with Enter/Right/Left +* **ignore** — optional paths to exclude; use Space to toggle multiple + entries and Enter to confirm + +You can pre-fill any field to skip its prompt: + +.. code-block:: sh + + dfetch add -i --version main \ + --src lib/core \ + https://github.com/some-org/some-repo.git + +After you confirm the settings the wizard offers to run ``dfetch update`` +immediately so the new project is fetched right away. diff --git a/doc/howto/check-ci.rst b/doc/howto/check-ci.rst new file mode 100644 index 000000000..c3ba3884b --- /dev/null +++ b/doc/howto/check-ci.rst @@ -0,0 +1,224 @@ + +.. _check-ci: + +Check dependencies in CI +========================= + +``dfetch check`` exits non-zero when any project is out-of-date or has local +changes, making it a natural pipeline gate. Each supported CI system has its +own report format so findings surface natively inside the platform's UI. + +- :ref:`check-ci-run` — run ``dfetch check`` as a pipeline step +- :ref:`check-ci-jenkins` — surface results in the Jenkins warnings-ng plugin +- :ref:`check-ci-github` — upload SARIF results to GitHub code scanning +- :ref:`check-ci-gitlab` — publish code-quality reports in GitLab merge requests + +---- + +.. _check-ci-run: + +Running dfetch check in CI +--------------------------- + +.. automodule:: dfetch.reporting.check.reporter + +.. asciinema:: ../asciicasts/check-ci.cast + +Without extra flags the results are printed to stdout and the build fails if +any issue is found: + +.. code-block:: sh + + dfetch check + +Pass a ``--*-json`` flag to write a machine-readable report *and* continue +collecting results before deciding the build outcome (each section below shows +the exact flag). + +---- + +.. _check-ci-jenkins: + +Jenkins (warnings-ng) +--------------------- + +*Dfetch* writes a report in the `warnings-ng native JSON format`_ that the +`warnings-ng plugin`_ can ingest directly. + +.. asciinema:: ../asciicasts/check.cast + +**Severity mapping** + ++-------------------+----------+-----------------------------------------------------+ +| Dfetch result | Severity | Meaning | ++===================+==========+=====================================================+ +| unfetched | high | Project was never fetched | ++-------------------+----------+-----------------------------------------------------+ +| out-of-date | normal | A newer version is available | ++-------------------+----------+-----------------------------------------------------+ +| pinned-out-of-date| low | Pinned to a specific version; newer version exists | ++-------------------+----------+-----------------------------------------------------+ + +Jenkins will show an overview of all issues: + +.. image:: ../images/out-of-date-jenkins2.png + :alt: Cpputest is out-of-date and requires updating. + +Clicking an issue navigates to the exact line in the manifest: + +.. image:: ../images/out-of-date-jenkins.png + :alt: Cpputest is out-of-date and requires updating. + +**Pipeline snippet** + +.. code-block:: groovy + + /* Linux agent */ + sh 'dfetch check --jenkins-json jenkins.json' + + /* Windows agent */ + bat 'dfetch check --jenkins-json jenkins.json' + + recordIssues tool: issues(pattern: 'jenkins.json', name: 'DFetch') + +Use `quality gate configuration`_ in the warnings-ng plugin to control when +the build fails — for example, allow pinned-out-of-date without failing. + +.. scenario-include:: ../features/check-report-jenkins.feature + +.. _`warnings-ng plugin`: https://plugins.jenkins.io/warnings-ng/ +.. _`warnings-ng native JSON format`: https://github.com/jenkinsci/warnings-ng-plugin/blob/master/doc/Documentation.md#export-your-issues-into-a-supported-format +.. _`quality gate configuration`: https://github.com/jenkinsci/warnings-ng-plugin/blob/master/doc/Documentation.md#quality-gate-configuration + +---- + +.. _check-ci-github: + +GitHub Actions (SARIF) +----------------------- + +*Dfetch* can upload results to GitHub code scanning as a SARIF report, so +findings appear inline in pull requests. + +**Severity mapping** + ++-------------------+-----------+-----------------------------------------------------+ +| Dfetch result | Severity | Meaning | ++===================+===========+=====================================================+ +| unfetched | Error | Project was never fetched | ++-------------------+-----------+-----------------------------------------------------+ +| out-of-date | Warning | A newer version is available | ++-------------------+-----------+-----------------------------------------------------+ +| pinned-out-of-date| Note | Pinned; newer version exists | ++-------------------+-----------+-----------------------------------------------------+ + +Results appear on the Actions summary: + +.. image:: ../images/github-actions-result.png + :alt: Github action has run during a pull request. + +A locally changed project surfaces like this: + +.. image:: ../images/local-change-github.png + :alt: A project was locally changed. + +Clicking *details* brings you to the manifest entry: + +.. image:: ../images/local-change-github-details.png + :alt: A project was locally changed. + +**GitHub Actions workflow** + +The easiest integration is the official action, which runs ``dfetch check`` +and uploads the SARIF report in one step: + +.. code-block:: yaml + + name: DFetch + + on: push + + permissions: + contents: read + + jobs: + dfetch: + runs-on: ubuntu-latest + + permissions: + contents: read + security-events: write + + steps: + - name: Dfetch SARIF Check + uses: dfetch-org/dfetch@main + with: + working-directory: '.' + +To run *Dfetch* yourself and control the output path: + +.. code-block:: yaml + + - run: dfetch check --sarif dfetch.sarif + - uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: dfetch.sarif + +For more information see the `GitHub SARIF documentation`_. + +.. scenario-include:: ../features/check-report-sarif.feature + +.. _`GitHub SARIF documentation`: https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning + +---- + +.. _check-ci-gitlab: + +GitLab CI (Code Climate) +------------------------- + +*Dfetch* writes a `Code Climate JSON`_ report that GitLab shows inline in +merge requests, comparing issues between the feature branch and the base +branch. + +**Severity mapping** + ++-------------------+----------+-----------------------------------------------------+ +| Dfetch result | Severity | Meaning | ++===================+==========+=====================================================+ +| unfetched | major | Project was never fetched | ++-------------------+----------+-----------------------------------------------------+ +| out-of-date | minor | A newer version is available | ++-------------------+----------+-----------------------------------------------------+ +| pinned-out-of-date| info | Pinned; newer version exists | ++-------------------+----------+-----------------------------------------------------+ + +GitLab shows the results on the pipeline page: + +.. image:: ../images/gitlab-check-pipeline-result.png + :alt: Gitlab detected issues. + +Clicking an issue navigates to the manifest: + +.. image:: ../images/gitlab-highlighted-manifest.png + :alt: Gitlab highlights the project in the manifest with the issue. + +**``.gitlab-ci.yml`` snippet** + +.. code-block:: yaml + + dfetch: + image: "python:3.13" + script: + - pip install dfetch + - dfetch check --code-climate dfetch.json + artifacts: + reports: + codequality: dfetch.json + +See `GitLab code quality reports`_ for more information. + +.. scenario-include:: ../features/check-report-code-climate.feature + +.. _`Code Climate JSON`: https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types +.. _`GitLab code quality reports`: https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality diff --git a/doc/howto/contributing.rst b/doc/howto/contributing.rst new file mode 100644 index 000000000..1ce20bd2b --- /dev/null +++ b/doc/howto/contributing.rst @@ -0,0 +1,467 @@ + + +Contribute to Dfetch +==================== +Before implementing a feature please ask one of the maintainers to avoid any unnecessary or double work. +Let other people know through the relevant GitHub issue that you are planning on implementing it. +Also for new features, first create an issue that can be discussed. + +After implementing (with tests and documentation) create a PR on Github and let your changes be reviewed. + +Virtual Environment +------------------- +Create a virtual environment by double-clicking ``script/create_venv.py`` or by running the following command. +This will install all ``development``, ``test`` and ``docs`` dependencies from ``pyproject.toml``, install +*DFetch* as `editable package `_ +and install all runtime dependencies from ``pyproject.toml``. + +.. code-block:: bash + + script/create_venv.py + +.. important :: *dfetch* is primarily developed with python 3.13 + +Running in Github Codespaces +---------------------------- +Github codespaces make it possible to edit dfetch directly in the browser in a VSCode instance. +All dependencies are pre-installed and makes it easy to get started. + +|CodespacesLink|_ + +.. |CodespacesLink| image:: https://github.com/codespaces/badge.svg +.. _CodespacesLink: https://codespaces.new/dfetch-org/dfetch + +.. tip:: + + You can preview the documentation locally by running the ``Serve Docs`` task. + Codespaces will automatically suggest to open the forwarded port to view the + changes in your browser. + +Running in VSCode +----------------- +To debug or run directly from VSCode create the :ref:`virtual environment`. +Select the python from the virtual environment. +And open the ``dfetch.code-workspace``. + +Quality +------- +To avoid any discussion about formatting `black `_ is used as code formatter. +Next to that `isort `_ is used for sorting the imports. +And `doc8 `_ is used as rst linter. + +`import-linter `_ is used to guard the :ref:`architecture` by verifying +that imports between modules respect the C4 layer boundaries. Run it with: + +.. code-block:: bash + + lint-imports + +Run ``script/check_quality.bat`` (or GitHub will run it for you). Alternatively when using VSCode run the `Check Quality` task from the command palette. + +Testing +------- + +Unit tests +~~~~~~~~~~ +`pytest `_ is used as testing framework. All code contributed should be accompanied with unit tests. +Tests can be found in the ``tests`` folder. + +To see coverage, in the virtual environment run ``pytest`` with coverage + +.. code-block:: bash + + pytest --cov=dfetch tests + +From VSCode all tests can be run from the Test view. + +Feature tests +~~~~~~~~~~~~~ +Feature tests are used for higher-level integration testing of functionality. +For this `behave `_ is used as testing framework. +Features are specified in *Gherkin* in so-called feature files in the ``features`` folder. +The sentences in the feature files, map to steps in the ``features/steps`` folder. + +Test can be run directly from the command-line + +.. code-block:: bash + + behave features + +Alternatively in VSCode run the ``Run all feature tests`` task from the command palette. + +To debug these tests, mark the ``Feature:`` or ``Scenario:`` to debug with the ``@wip`` tag +and add run the ``Feature tests (wip)`` debug configuration in VSCode. + + +Creating documentation +---------------------- +Run ``script/create_docs.bat`` and open ``index.html`` in ``doc/_build/html`` to read it. +See `This example `_ for documenting the code. +Alternatively in VSCode run the ``Create Docs`` task from the command palette. + +Design Guide +~~~~~~~~~~~~ + +The docs and landing page share a single design system. Follow these tokens +whenever you add new pages, diagrams, or HTML blocks to keep the visual +language consistent. + +Colour palette +'''''''''''''' + +.. raw:: html + +
+ +
+
+
+
--primary
+
#c2620a
+
Amber Orange
+
CTAs, active borders, diagram start nodes, section pips
+
+
+ +
+
+
+
--primary-dark
+
#a0510a
+
Amber Dark
+
Hover states, gradient end, button borders
+
+
+ +
+
+
+
--accent
+
#4e7fa0
+
Slate Blue
+
Decision diamonds, links, secondary badges, note borders
+
+
+ +
+
+
+
--accent-dark
+
#3a6682
+
Slate Dark
+
Hover states on accent elements, diagram labels
+
+
+ +
+
+
+
--text
+
#1c1917
+
Near Black
+
Body copy, headings, diagram text
+
+
+ +
+
+
+
--text-muted
+
#78716c
+
Warm Gray
+
Captions, sub-labels, placeholder text, diagram arrow labels
+
+
+ +
+
+
+
--bg-tint
+
#fef8f0
+
Warm Cream
+
Alternate section bands, card backgrounds, table headers
+
+
+ +
+
+
+
--bg-mint
+
#eff6fa
+
Cool Mint
+
Tip/note backgrounds, accent section bands
+
+
+ +
+ +Typography +'''''''''' + +.. raw:: html + +
+ +
+
+ Inter + body · UI · headings +
+
+
+
+ 300 + Vendor dependencies without the pain +
+
+ 400 + Vendor dependencies without the pain +
+
+ 500 + Vendor dependencies without the pain +
+
+ 600 + Vendor dependencies without the pain +
+
+ 700 + Vendor dependencies without the pain +
+
+ 800 + Vendor dependencies without the pain +
+
+
+
+ +
+
+ JetBrains Mono + code · tokens · CLI +
+
+
+
+ 400 + dfetch update --force +
+
+ 500 + dfetch update --force +
+
+

+ Use for: inline code, CLI examples, hex values, token names, + cheatsheet syntax, diagram labels where monospace is needed. +

+
+
+ +
+ +CSS tokens reference +'''''''''''''''''''' + +Use CSS custom properties rather than raw hex values so changes propagate +everywhere automatically. + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TokenValueUse for
--primary#c2620aPrimary action colour
--primary-dark#a0510aHover / pressed state
--accent#4e7fa0Secondary / info colour
--accent-dark#3a6682Hover on accent elements
--text#1c1917Body text, headings
--text-2#3d3530Secondary text
--text-muted#78716cCaptions, labels, placeholders
--bg-tint#fef8f0Warm section backgrounds
--bg-mint#eff6faCool section backgrounds
--border#e7e0d8Card borders, dividers
--r / --r-sm / --r-lg12px / 8px / 20pxBorder radius (card / badge / hero)
--shad-sm / --shad-md / --shad-lgBox shadow tiers
+ +PlantUML diagrams +''''''''''''''''' + +All flow and mindmap diagrams must use the shared skinparam block from +``doc/static/uml/check.puml`` as a template. Key values: + +.. list-table:: + :header-rows: 1 + :widths: 35 30 35 + + * - Element + - Value + - Notes + * - Activity background + - ``#ffffff`` + - White nodes on transparent canvas + * - Activity border + - ``#c2620a`` (``--primary``) + - 1.5 pt thickness + * - Diamond background + - ``#eff6fa`` (``--bg-mint``) + - Decision nodes use the cool tint + * - Diamond border + - ``#4e7fa0`` (``--accent``) + - Consistent with accent colour + * - Arrow colour + - ``#78716c`` (``--text-muted``) + - Keeps arrows from competing with nodes + * - Start node + - ``#c2620a`` (``--primary``) + - Filled amber disc + * - Partition background + - ``#fef8f0`` (``--bg-tint``) + - Warm cream section bands + * - Default font + - Arial, 12 pt, ``#1c1917`` + - Matches ``--text`` + +Diataxis section colours +'''''''''''''''''''''''' + +Each of the four Diataxis sections has its own accent colour. These are +applied automatically by ``doc/static/js/diataxis.js``: it adds a CSS class +to ```` and to the sidebar caption elements so both the top page strip +and the sidebar navigation header reflect the section. + +.. raw:: html + +
+ +
+
+
+
--dxt-tutorial
+
#c2620a
+
Tutorials
+
Same as --primary; warm amber for learning-oriented pages
+
+
+ +
+
+
+
--dxt-howto
+
#4e7fa0
+
How-to Guides
+
Same as --accent; slate blue for task-oriented pages
+
+
+ +
+
+
+
--dxt-reference
+
#4a7a62
+
Reference
+
Sage green; neutral, precise tone for information pages
+
+
+ +
+
+
+
--dxt-explanation
+
#7a5a9a
+
Explanation
+
Soft purple; contemplative tone for conceptual pages
+
+
+ +
+ +To add a new page to a section, add its filename (without ``.rst``) to the +``PAGE_SECTIONS`` map in ``doc/static/js/diataxis.js``. + +Releasing +--------- + +- Bump version number in ``dfetch/__init__.py``. +- Run ``./script/release.py``. +- Double check any feature scenarios for a version number. +- Run all unit / feature tests. +- Re-generate asciicasts using VSCode task (linux/mac). +- Commit all release changes. +- Create pull request & merge to ``main``. +- Create annotated tag on ``main`` and push it: + +.. code-block:: bash + + git checkout main + git pull + git tag -a '0.12.1' -m "Release version 0.12.1" + git push --tags + +- The ``ci.yml`` job will automatically create a draft release in `GitHub Releases `_ with all artifacts. +- Once the release is published, a new package is automatically pushed to `PyPi `_. + +- After release, add new header to ``CHANGELOG.rst``: + +.. code-block:: rst + + Release 0.13.0 (unreleased) + ==================================== diff --git a/doc/howto/migration.rst b/doc/howto/migration.rst new file mode 100644 index 000000000..4823a96b2 --- /dev/null +++ b/doc/howto/migration.rst @@ -0,0 +1,197 @@ + +.. _migration: + +Migrate to Dfetch +================= + +*Dfetch* can convert an existing project that uses Git submodules or SVN +externals into a Dfetch-managed project. The :ref:`dfetch import ` +command reads your current dependency configuration and writes a +:ref:`Manifest` — after that you remove the old mechanism and let +:ref:`dfetch update ` take over. + +Choose the guide that matches your current setup: + +- :ref:`migration-git` +- :ref:`migration-svn` + +---- + +.. _migration-git: + +From Git submodules +------------------- + +**Before you start**, make sure: + +- Your repository is fully up-to-date (``git pull``). +- All submodules are initialised and checked out: + + .. code-block:: console + + $ git submodule update --init --recursive + +**Steps** + +1. Generate a manifest from the existing submodules: + + .. code-block:: console + + $ dfetch import + + This writes a ``dfetch.yaml`` file in the current directory listing each + submodule as a *Dfetch* project entry, pinned to the commit that is + currently checked out. + +2. Remove all Git submodules. For each submodule (replace ```` with + the submodule path, e.g. ``ext/mylib``): + + .. code-block:: console + + $ git submodule deinit -f + $ git rm -f + $ rm -rf .git/modules/ + + Repeat until ``git submodule status`` returns nothing. Commit the + result: + + .. code-block:: console + + $ git commit -m "chore: remove git submodules (switching to Dfetch)" + + .. seealso:: + + `How do I remove a submodule? + `_ + +3. Fetch all projects into your repository: + + .. code-block:: console + + $ dfetch update + +4. Commit the fetched files: + + .. code-block:: console + + $ git add . + $ git commit -m "chore: vendor dependencies with Dfetch" + +.. scenario-include:: ../features/import-from-git.feature + +.. _migration-git-branches: + +Switching branches after migration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you migrate on a feature branch while ``master`` still has the original +submodules, you will run into a few rough edges when switching back and +forth. + +**From submodule branch → manifest branch** + +Git will refuse to check out if a *Dfetched* dependency would overwrite a +submodule directory that is still in place: + +.. code-block:: console + + $ git checkout feature/use-dfetch + error: The following untracked working tree files would be overwritten by checkout: + MySubmodule/somefile.c + MySubmodule/someotherfile.c + +``git status`` shows a clean tree — the files belong to the submodule, which +Git does not track as ordinary working-tree files. + +To work around this, delete the directory before switching: + +.. code-block:: console + + $ rm -rf MySubmodule + $ git checkout feature/use-dfetch + +If you have several submodules, remove them all before checking out. + +**From manifest branch → submodule branch** + +This direction succeeds without errors, but Git leaves the submodule +directories empty. After checking out, re-initialise them: + +.. code-block:: console + + $ git checkout master + $ git submodule update --init --recursive + Submodule path 'MySubmodule': checked out '08f95e01b297d8b8c1c9101bde58e75cd4d428ce' + +---- + +.. _migration-svn: + +From SVN externals +------------------ + +**Before you start**, make sure your working copy is fully up-to-date: + +.. code-block:: console + + $ svn update + +**Steps** + +1. Generate a manifest from the existing externals: + + .. code-block:: console + + $ dfetch import + + This writes a ``dfetch.yaml`` listing every ``svn:externals`` entry as a + *Dfetch* project, pinned to the revision that is currently set. + +2. Remove all SVN externals. Externals are stored as a property on a + directory — you need to delete that property for every directory that has + one. + + List all directories with ``svn:externals`` set: + + .. code-block:: console + + $ svn proplist -R | grep -B1 svn:externals + + For each such directory (replace ````): + + .. code-block:: console + + $ svn propdel svn:externals + + Commit the property removal: + + .. code-block:: console + + $ svn commit -m "Remove SVN externals (switching to Dfetch)" + + .. seealso:: + + `How do I remove SVN externals? + `_ + + .. note:: + + If your externals contain nested externals, ``dfetch import`` only + reads the top-level ``svn:externals`` property. Run ``dfetch import`` + inside each nested external directory and merge the resulting entries + into your top-level ``dfetch.yaml`` by hand. + +3. Fetch all projects into your working copy: + + .. code-block:: console + + $ dfetch update + +4. Commit the fetched files: + + .. code-block:: console + + $ svn add --force . + $ svn commit -m "Vendor dependencies with Dfetch" + +.. scenario-include:: ../features/import-from-svn.feature diff --git a/doc/howto/patching.rst b/doc/howto/patching.rst new file mode 100644 index 000000000..c2d2bc463 --- /dev/null +++ b/doc/howto/patching.rst @@ -0,0 +1,161 @@ + +.. _patching: + +Patch a dependency +================== + +*Dfetch* has a first-class patch workflow. When you need to fix a bug or +apply a customisation to a vendored dependency, you can track that change as a +patch file that is automatically re-applied on every :ref:`dfetch update +`. When the fix is ready to share, :ref:`format-patch` converts it +into a contributor-ready unified diff that upstream maintainers can apply +directly. + +The full lifecycle looks like this: + +1. :ref:`patching-create` — capture local edits as a ``.patch`` file with ``dfetch diff`` +2. :ref:`patching-wire` — reference the patch from the manifest so it is applied on every fetch +3. :ref:`patching-update` — refresh the patch as your edits evolve with ``dfetch update-patch`` +4. :ref:`patching-upstream` — reformat the patch for upstream use with ``dfetch format-patch`` + +---- + +.. _patching-create: + +Capturing local changes +----------------------- + +After fetching a project with :ref:`dfetch update `, make your edits +directly in the vendored source tree. Once you are happy with the changes, +run: + +.. code-block:: sh + + dfetch diff some-project + +*Dfetch* compares the working tree against the revision recorded in the +metadata file and writes a patch file named ``some-project.patch`` (or +``some-project-N.patch`` if multiple patches already exist). + +.. asciinema:: ../asciicasts/diff.cast + +**Controlling which revisions are compared** + +By default, *Dfetch* uses the revision stored in the project's metadata as the +base. You can override this: + +- Single base revision: ``dfetch diff some-project --revs 23864ef2`` +- Explicit range: ``dfetch diff some-project --revs 23864ef2:4a9cb18`` + +.. tabs:: + + .. tab:: Git + + .. scenario-include:: ../features/diff-in-git.feature + + .. tab:: SVN + + .. scenario-include:: ../features/diff-in-svn.feature + +---- + +.. _patching-wire: + +Adding the patch to the manifest +--------------------------------- + +Once you have a patch file, reference it from the project entry in +``dfetch.yaml`` using the :ref:`patch` attribute: + +.. code-block:: yaml + + manifest: + version: '0.0' + projects: + - name: some-project + url: https://github.com/example/some-project + tag: v1.2.3 + patch: some-project.patch + +From this point on, every ``dfetch update`` will fetch the upstream source and +re-apply the patch on top. See :ref:`patch` in the manifest reference for the +full attribute syntax. + +---- + +.. _patching-update: + +Refreshing a patch +------------------ + +As your local edits evolve — or when the upstream version changes — the +existing patch file may no longer apply cleanly. Instead of manually +regenerating it, run: + +.. code-block:: sh + + dfetch update-patch some-project + +This regenerates the last patch for ``some-project`` from the current working +tree, keeping the upstream revision unchanged. It is safe to run repeatedly +as you iterate on the fix. + +.. asciinema:: ../asciicasts/update-patch.cast + +.. tabs:: + + .. tab:: Git + + .. scenario-include:: ../features/update-patch-in-git.feature + + .. tab:: SVN + + .. scenario-include:: ../features/update-patch-in-svn.feature + +---- + +.. _patching-upstream: + +Contributing the patch upstream +--------------------------------- + +Patches generated by ``dfetch diff`` are relative to the project's vendored +directory inside your repository. Most upstream projects expect patches to be +relative to their own root, which is a different path. To reformat all patches +for a project: + +.. code-block:: sh + + dfetch format-patch some-project + +This writes a ``formatted-some-project.patch`` file (or one file per patch if +there are several) that is ready to attach to a pull request or send by email. + + +You can verify the formatted patch applies cleanly before submitting: + +.. tabs:: + + .. tab:: Git + + .. code-block:: sh + + git apply --check formatted-some-project.patch + + .. tab:: SVN + + .. code-block:: sh + + svn patch formatted-some-project.patch + +.. asciinema:: ../asciicasts/format-patch.cast + +.. tabs:: + + .. tab:: Git + + .. scenario-include:: ../features/format-patch-in-git.feature + + .. tab:: SVN + + .. scenario-include:: ../features/format-patch-in-svn.feature diff --git a/doc/howto/sbom.rst b/doc/howto/sbom.rst new file mode 100644 index 000000000..0d5ae13ec --- /dev/null +++ b/doc/howto/sbom.rst @@ -0,0 +1,83 @@ + +.. _sbom: + +Generate an SBOM +=============== + +``dfetch report`` generates a `CycloneDX`_ SBOM listing every vendored +dependency with its URL, revision, and auto-detected license. Downstream +tools can use the SBOM to monitor for known vulnerabilities or enforce a +license policy across an organisation. + +.. asciinema:: ../asciicasts/sbom.cast + +.. code-block:: sh + + dfetch report -t sbom -o dfetch.cdx.json + +*Dfetch* parses each project's license at fetch time and stores it in the +``.dfetch_data.yaml`` metadata file, so reports stay accurate without +re-fetching. + +Archive dependencies (``tar.gz``, ``zip``, …) are recorded with a +``distribution`` external reference. When an ``integrity.hash:`` field is set +in the manifest the SBOM includes a ``SHA-256`` component hash for +supply-chain integrity verification. + +.. scenario-include:: ../features/report-sbom.feature + :scenario: A fetched project generates a json sbom + +.. scenario-include:: ../features/report-sbom-archive.feature + :scenario: A fetched archive without a hash generates a json sbom + +.. scenario-include:: ../features/report-sbom-archive.feature + :scenario: A fetched archive with sha256 hash generates a json sbom with hash + +---- + +GitLab +------ + +Upload the SBOM as a CycloneDX artifact so GitLab surfaces it in the +dependency scanning dashboard. See `GitLab dependency scanning`_ for details. + +.. code-block:: yaml + + dfetch: + image: "python:3.13" + script: + - pip install dfetch + - dfetch report -t sbom -o dfetch.cdx.json + artifacts: + reports: + cyclonedx: + - dfetch.cdx.json + +---- + +GitHub Actions +-------------- + +Generate and upload the SBOM as a workflow artifact. See `GitHub dependency +submission`_ for details. + +.. code-block:: yaml + + jobs: + SBOM-generation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + - name: Generate SBOM + run: pip install dfetch && dfetch report -t sbom -o dfetch.cdx.json + - uses: actions/upload-artifact@v4 + with: + name: sbom + path: dfetch.cdx.json + +.. _`CycloneDX`: https://cyclonedx.org/use-cases/ +.. _`GitLab dependency scanning`: https://docs.gitlab.com/user/application_security/dependency_scanning/dependency_scanning_sbom/#cyclonedx-software-bill-of-materials +.. _`GitHub dependency submission`: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api diff --git a/doc/troubleshooting.rst b/doc/howto/troubleshooting.rst similarity index 96% rename from doc/troubleshooting.rst rename to doc/howto/troubleshooting.rst index 0d26c3d22..bf5df5bbd 100644 --- a/doc/troubleshooting.rst +++ b/doc/howto/troubleshooting.rst @@ -1,7 +1,7 @@ -Troubleshooting -=============== +Troubleshoot +============ Sometimes *Dfetch* may not behave as expected. This is could be because it relies on standard command-line tools such as ``git`` to be available on your system. This section will help you @@ -19,7 +19,7 @@ This shows missing or incompatible dependencies. Run: Compare the output to the expected tools your commands require. -.. asciinema:: asciicasts/environment.cast +.. asciinema:: ../asciicasts/environment.cast Step 2: Use verbose mode ------------------------ diff --git a/doc/howto/updating-projects.rst b/doc/howto/updating-projects.rst new file mode 100644 index 000000000..ba319cfc2 --- /dev/null +++ b/doc/howto/updating-projects.rst @@ -0,0 +1,117 @@ + +.. _updating-projects: + +Update projects +=============== + +``dfetch update`` fetches every dependency listed in ``dfetch.yaml`` and +places the requested version in its destination folder. VCS type (Git, SVN, +or plain archive) is detected automatically. + +- :ref:`updating-basic` — fetch all projects at once +- :ref:`updating-selective` — update a single project +- :ref:`updating-force` — overwrite local changes +- :ref:`updating-sub-manifests` — nested manifests in dependencies +- :ref:`updating-submodules` — Git submodules inside dependencies + +.. _updating-basic: + +Fetching all projects +--------------------- + +Run without arguments to fetch every project in the manifest: + +.. code-block:: sh + + dfetch update + +.. asciinema:: ../asciicasts/update.cast + +*Dfetch* reads ``dfetch.yaml``, resolves each project's VCS type, and writes +the requested revision into the destination folder. A ``.dfetch_data.yaml`` +metadata file is created inside each destination so *Dfetch* can track what +version is present. + +.. tabs:: + + .. tab:: Git + + .. scenario-include:: ../features/fetch-git-repo.feature + + .. tab:: SVN + + .. scenario-include:: ../features/fetch-svn-repo.feature + + .. tab:: Archive + + .. scenario-include:: ../features/fetch-archive.feature + +.. _updating-selective: + +Updating a single project +-------------------------- + +Pass one or more project names to limit which entries are updated: + +.. code-block:: sh + + dfetch update mylib + + dfetch update mylib myother + +.. _updating-force: + +Overwriting local changes +-------------------------- + +By default *Dfetch* skips a project that is already at the requested version +or that has local modifications. Use ``--force`` (``-f``) to re-fetch and +overwrite regardless: + +.. code-block:: sh + + dfetch update --force mylib + +.. warning:: + + Any unsaved local edits in the destination directory will be lost. + Capture them first with ``dfetch diff`` — see :ref:`patching` for the + full patch workflow. + +.. _updating-sub-manifests: + +Sub-manifests +-------------- + +A fetched project may itself contain a ``dfetch.yaml``. *Dfetch* reads it +after fetching and reports any further dependencies it finds, so you can +decide whether to vendor those as well. + +To skip this check entirely: + +.. code-block:: sh + + dfetch update --no-recommendations + +.. scenario-include:: ../features/updated-project-has-dependencies.feature + +.. _updating-submodules: + +Git submodules +--------------- + +When a Git dependency contains submodules, *Dfetch* fetches and resolves them +automatically — no extra manifest entries or ``git submodule`` commands are +needed. Each submodule is checked out at the exact revision pinned by the +parent repository:: + + Dfetch (0.12.1) + my-project: + > Found & fetched submodule "./ext/vendor-lib" (https://github.com/example/vendor-lib @ master - 79698c9…) + > Fetched master - e1fda19… + +Nested submodules are resolved recursively. Pinned details for each +submodule are recorded in ``.dfetch_data.yaml`` and are visible in +:ref:`Report`. + +.. scenario-include:: ../features/fetch-git-repo-with-submodule.feature diff --git a/doc/index.rst b/doc/index.rst index 7715f5f54..c2b9ad0f5 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,23 +1,22 @@ .. meta:: - :description: Dfetch is a VCS-agnostic tool that simplifies dependency management by retrieving - source-only dependencies from various repositories, promoting upstream changes and - allowing local customizations. - :keywords: dfetch, dependency management, embedded development, fetch tool, vendoring, multi-repo, dependencies, git, svn, package manager, multi-project, monorepo + :description: Dfetch vendors source code from Git or SVN repositories or plain archives directly + into your project. No submodules, no lock-in, fully self-contained. Supply-chain ready. + :keywords: dfetch, dependency management, vendoring, git, svn, archive, embedded development, source-only dependencies, multi-repo, supply chain, sbom, license compliance :author: Dfetch Contributors :google-site-verification: yCnoTogJMh7Nm5gxlREDuONIXT4ijHcj972Y5k9p-sU .. raw:: html - - + + - - + + @@ -25,41 +24,55 @@ :width: 100% :align: center +Dfetch — *vendor dependencies without the pain* +================================================ + +**Dfetch** copies source code directly into your project — no Git submodules, no SVN externals, +no hidden external links. Fetch from Git, SVN, or plain archive URLs. Dependencies live as plain, +readable files inside your own repository. You stay in full control of every line. + +Dfetch is supply-chain ready out of the box: generate SBOMs, detect licenses, and export +reports for Jenkins, SARIF, and Code Climate. Apply local patches and keep them syncable with +upstream. See :ref:`vendoring` for background on the problem this solves. + +.. asciinema:: asciicasts/basic.cast + +---- + .. toctree:: :maxdepth: 2 + :caption: Tutorials - installation - getting_started - manifest - manual - troubleshooting - contributing - changelog - alternatives - vendoring - legal - internal - -Dfetch - *a source-only no-hassle project-dependency aggregator* -================================================================ - -What is Dfetch? ---------------- - -We make products that can last 15+ years; because of this we want to be able to have all sources available -to build the entire project from source without depending on external resources. -For this, we needed a dependency manager that was flexible enough to retrieve dependencies as plain text -from various sources. `svn externals`, `git submodules` and `git subtrees` solve a similar -problem, but not in a VCS-agnostic way or completely user-friendly way. -We want self-contained code repositories without any hassle for end-users. -Dfetch must promote upstreaming changes, but allow for local customizations. -The problem is described thoroughly in `managing external dependencies `_ and sometimes -is also known as :ref:`vendoring`. - -Other tools that do similar things are ``Zephyr's West``, ``CMake ExternalProject`` and other meta tools. -See :ref:`alternatives` for a complete list. - -Basic usage ------------ + tutorials/installation + tutorials/getting_started -.. asciinema:: asciicasts/basic.cast +.. toctree:: + :maxdepth: 2 + :caption: How-to Guides + + howto/migration + howto/adding-a-project + howto/updating-projects + howto/patching + howto/check-ci + howto/sbom + howto/troubleshooting + howto/contributing + +.. toctree:: + :maxdepth: 2 + :caption: Reference + + reference/manifest + reference/commands + reference/cli_cheatsheet + reference/changelog + reference/legal + +.. toctree:: + :maxdepth: 2 + :caption: Explanation + + explanation/vendoring + explanation/alternatives + explanation/internal diff --git a/doc/landing-page/index.rst b/doc/landing-page/index.rst index 19188b907..ad0f43d83 100644 --- a/doc/landing-page/index.rst +++ b/doc/landing-page/index.rst @@ -394,7 +394,7 @@ readable files inside your own repository. You stay in full control of every lin ``dfetch import`` automatically converts **Git submodules and SVN externals** into a dfetch manifest. No manual work, no lost history — start benefiting from dfetch's workflow immediately. - .. button-link:: https://dfetch.rtfd.io/en/latest/manual.html#import + .. button-link:: https://dfetch.rtfd.io/en/latest/commands.html#import :color: primary :shadow: diff --git a/doc/manual.rst b/doc/manual.rst deleted file mode 100644 index 0f5b3c487..000000000 --- a/doc/manual.rst +++ /dev/null @@ -1,242 +0,0 @@ - - -Manual -====== - -Introduction ------------- -*Dfetch* can perform various actions based on the projects listed in the `manifest `_. -Each of these actions are a separate command. Below an overview of all available commands and -their usage. For detailed information on each command, please refer to the respective sections below. -For a step-by-step guide see the `Getting Started `_. - -.. program-output:: dfetch --help - :shell: - -Init ------ -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: init - -.. asciinema:: asciicasts/init.cast - -.. automodule:: dfetch.commands.init - -Import ------- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: import - -.. asciinema:: asciicasts/import.cast - -.. automodule:: dfetch.commands.import_ - -Check ------ -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: check - -.. asciinema:: asciicasts/check.cast - -.. automodule:: dfetch.commands.check - -Reporting -````````` -.. automodule:: dfetch.reporting.check.reporter - -Jenkins reporter -'''''''''''''''' -.. automodule:: dfetch.reporting.check.jenkins_reporter - -.. asciinema:: asciicasts/check-ci.cast - -Sarif reporter -'''''''''''''' -.. automodule:: dfetch.reporting.check.sarif_reporter - -Code-climate reporter -''''''''''''''''''''' -.. automodule:: dfetch.reporting.check.code_climate_reporter - -Update ------- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: update - -.. asciinema:: asciicasts/update.cast - -.. automodule:: dfetch.commands.update - -Report ------- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: report - -.. asciinema:: asciicasts/report.cast - -.. automodule:: dfetch.commands.report - -List (default) -`````````````` -.. automodule:: dfetch.reporting.stdout_reporter - -Software Bill-of-Materials -`````````````````````````` -.. automodule:: dfetch.reporting.sbom_reporter - -.. asciinema:: asciicasts/sbom.cast - -Diff ----- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: diff - -.. asciinema:: asciicasts/diff.cast - -.. automodule:: dfetch.commands.diff - -Update patch ------------- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: update-patch - -.. asciinema:: asciicasts/update-patch.cast - -.. automodule:: dfetch.commands.update_patch - -Format patch ------------- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: format-patch - -.. asciinema:: asciicasts/format-patch.cast - -.. automodule:: dfetch.commands.format_patch - -Freeze ------- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: freeze - -.. asciinema:: asciicasts/freeze.cast - -.. automodule:: dfetch.commands.freeze - -Environment ------------ -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: environment - -.. asciinema:: asciicasts/environment.cast - -.. automodule:: dfetch.commands.environment - -Validate --------- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: validate - -.. asciinema:: asciicasts/validate.cast - -.. automodule:: dfetch.commands.validate - -Add ---- -.. argparse:: - :module: dfetch.__main__ - :func: create_parser - :prog: dfetch - :path: add - -.. asciinema:: asciicasts/add.cast - -.. automodule:: dfetch.commands.add - - -CLI Cheatsheet --------------- - -A source-only, no-hassle project-dependency aggregator. -It uses a **manifest file** to describe your project's dependencies and fetches them into your codebase. -Also called vendoring. More info: ``_. - -- Start a new manifest (`dfetch.yaml`) with placeholder content: - - .. code-block:: console - - dfetch init - -- Add a new project to the manifest (interactive step-by-step wizard): - - .. code-block:: console - - dfetch add -i - - or non-interactively (auto-accept defaults, skip confirmation): - - .. code-block:: console - - dfetch add - -- Generate a manifest from existing git submodules or svn externals: - - .. code-block:: console - - dfetch import - -- Check for newer versions of dependencies and create a machine parseable report for your CI: - - .. code-block:: console - - dfetch check [--jenkins-json] [--sarif] [--code-climate] [project] - -- Download one or all projects from the manifest: - - .. code-block:: console - - dfetch update [-f] [project] - -- Freeze all projects to their current version: - - .. code-block:: console - - dfetch freeze - -- Report about the current state of the project(s): - - .. code-block:: console - - dfetch report [-o ] [-t {sbom,list}] [project] diff --git a/doc/changelog.rst b/doc/reference/changelog.rst similarity index 100% rename from doc/changelog.rst rename to doc/reference/changelog.rst diff --git a/doc/reference/cli_cheatsheet.rst b/doc/reference/cli_cheatsheet.rst new file mode 100644 index 000000000..848b80a03 --- /dev/null +++ b/doc/reference/cli_cheatsheet.rst @@ -0,0 +1,185 @@ + +CLI Cheatsheet +============== + +.. raw:: html + +
+ + +
+
+ +
+
Dfetch CLI Cheatsheet
+
Vendor dependencies without the pain  ·  dfetch.yaml found automatically
+
+
+
+ subcommand + --flag + <arg> +
+ + +
+ + +
+ + +
+ +
+
+ Foundational + core workflow · daily use +
+ +
+
dfetch init
+
Create a new dfetch.yaml manifest
+
+
+
dfetch add <url>
+
Add a dependency, auto-fill defaults
+
+
+
dfetch add -i <url>
+
Add interactively, step-by-step wizard
+
+
+
dfetch import
+
Migrate from git submodules / SVN externals
+
+
+
dfetch check [project]
+
Show dependencies with newer versions available
+
+
+
dfetch update [-f] [project]
+
Fetch / update one or all dependencies
+
+
+ +
+
+ Utilities + maintenance · setup · validation +
+ +
+
dfetch freeze
+
Pin all dependencies to currently fetched version
+
+
+
dfetch environment
+
Verify VCS tools and environment setup
+
+
+
dfetch validate
+
Validate the manifest without fetching
+
+
+ +
+ + +
+ +
+
+ Patching + local changes · upstream sync +
+ +
+
dfetch diff [project]
+
Capture local changes as a patch file
+
+
+
dfetch update-patch [project]
+
Re-apply patches after upstream version bump
+
+
+
dfetch format-patch [project]
+
Export contributor-ready unified diff
+
+
+ +
+
+ CI / CD Integration + reports · sbom · security +
+ +
+
dfetch check --jenkins-json
+
Jenkins-compatible JSON report
+
+
+
dfetch check --sarif
+
SARIF (GitHub Advanced Security etc.)
+
+
+
dfetch check --code-climate
+
Code Climate / GitLab report
+
+
+
dfetch report
+
Print a dependency inventory list
+
+
+
dfetch report -t sbom
+
Generate a Software Bill of Materials
+
+
+ +
+ +
+ + + +
+ + diff --git a/doc/reference/commands.rst b/doc/reference/commands.rst new file mode 100644 index 000000000..274a8c9da --- /dev/null +++ b/doc/reference/commands.rst @@ -0,0 +1,167 @@ + + +Commands +======== + +*Dfetch* is driven entirely from the command line. Each subcommand +operates on the projects listed in the :ref:`Manifest`, which +*Dfetch* searches for automatically from the current directory recursively +downward. + +This page is the complete CLI reference — flags, arguments, and +behaviour for every subcommand. If you are new to *Dfetch*, start with +:doc:`../tutorials/getting_started` instead. For specific tasks the How-to Guides +in the sidebar go further. + +.. program-output:: dfetch --help + :shell: + +Init +---- +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: init + +.. asciinema:: ../asciicasts/init.cast + +.. automodule:: dfetch.commands.init + +Import +------ +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: import + +.. asciinema:: ../asciicasts/import.cast + +.. automodule:: dfetch.commands.import_ + +.. seealso:: :doc:`../howto/migration` — step-by-step guide for switching from Git submodules or SVN externals. + +Add +--- +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: add + +.. automodule:: dfetch.commands.add + +.. seealso:: :doc:`../howto/adding-a-project` — walks through adding a new dependency from start to finish. + +Check +----- +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: check + +.. asciinema:: ../asciicasts/check.cast + +.. automodule:: dfetch.commands.check + +.. seealso:: :doc:`../howto/check-ci` — how to run dependency checks in CI pipelines and interpret the output formats. + +Update +------ +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: update + +.. asciinema:: ../asciicasts/update.cast + +.. automodule:: dfetch.commands.update + +.. seealso:: :doc:`../howto/updating-projects` — covers the update workflow, pinning versions, and force-fetching. + +Diff +---- +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: diff + +.. asciinema:: ../asciicasts/diff.cast + +.. seealso:: :doc:`../howto/patching` — creating, applying, and maintaining patches across upstream version bumps. + +Update patch +------------ +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: update-patch + +.. asciinema:: ../asciicasts/update-patch.cast + +.. _format-patch: + +Format patch +------------ +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: format-patch + +.. asciinema:: ../asciicasts/format-patch.cast + +Report +------ +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: report + +.. asciinema:: ../asciicasts/report.cast + +.. automodule:: dfetch.reporting.stdout_reporter + +.. seealso:: :doc:`../howto/sbom` — generating a Software Bill of Materials with ``dfetch report``. + +Freeze +------ +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: freeze + +.. asciinema:: ../asciicasts/freeze.cast + +.. automodule:: dfetch.commands.freeze + +Environment +----------- +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: environment + +.. asciinema:: ../asciicasts/environment.cast + +.. automodule:: dfetch.commands.environment + +Validate +-------- +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: validate + +.. asciinema:: ../asciicasts/validate.cast + +.. automodule:: dfetch.commands.validate diff --git a/doc/legal.rst b/doc/reference/legal.rst similarity index 100% rename from doc/legal.rst rename to doc/reference/legal.rst diff --git a/doc/manifest.rst b/doc/reference/manifest.rst similarity index 100% rename from doc/manifest.rst rename to doc/reference/manifest.rst diff --git a/doc/static/css/custom.css b/doc/static/css/custom.css index c502c39b1..33f134f38 100644 --- a/doc/static/css/custom.css +++ b/doc/static/css/custom.css @@ -1,44 +1,427 @@ -@import url("https://fonts.googleapis.com/css?family=Roboto:100,300,300i,400,500,700,900"); +/* Inter — vendored from doc/static/fonts/inter/ */ +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 300; font-display: swap; src: url('../fonts/inter/Inter-Light.woff2') format('woff2'); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/inter/Inter-Regular.woff2') format('woff2'); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('../fonts/inter/Inter-Medium.woff2') format('woff2'); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('../fonts/inter/Inter-SemiBold.woff2') format('woff2'); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap; src: url('../fonts/inter/Inter-Bold.woff2') format('woff2'); } +@font-face { font-family: 'Inter'; font-style: normal; font-weight: 800; font-display: swap; src: url('../fonts/inter/Inter-ExtraBold.woff2') format('woff2'); } -.sphinxsidebar .caption-text { - font-size: 130%; +/* JetBrains Mono — vendored from doc/static/fonts/jetbrains-mono/ */ +@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/jetbrains-mono/JetBrainsMono-Regular.woff2') format('woff2'); } +@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap; src: url('../fonts/jetbrains-mono/JetBrainsMono-Medium.woff2') format('woff2'); } + +/* ============================================================ + Design Tokens — mirrors the landing page palette exactly + ============================================================ */ +:root { + --primary: #c2620a; + --primary-dark: #a0510a; + --accent: #4e7fa0; + --accent-dark: #3a6682; + --text: #1c1917; + --text-2: #3d3530; + --text-muted: #78716c; + --bg-tint: #fef8f0; + --bg-mint: #eff6fa; + + /* Diataxis quadrant colours */ + --dxt-tutorial: #c2620a; /* amber — same as --primary */ + --dxt-howto: #4e7fa0; /* blue — same as --accent */ + --dxt-reference: #4a7a62; /* sage green */ + --dxt-explanation: #7a5a9a; /* soft purple */ + --border: #e7e0d8; + --grad: linear-gradient(135deg, #c2620a 0%, #7a3a0a 100%); + --shad-sm: 0 1px 3px rgba(0,0,0,.10), 0 1px 2px rgba(0,0,0,.06); + --shad-md: 0 4px 6px -1px rgba(0,0,0,.10), 0 2px 4px -2px rgba(0,0,0,.10); + --shad-lg: 0 10px 15px -3px rgba(0,0,0,.10), 0 4px 6px -4px rgba(0,0,0,.10); + --r: 12px; + --r-sm: 8px; + --r-lg: 20px; + --ease: 0.22s ease; } -.logo { - width: 100%; +/* ============================================================ + Keyframes + ============================================================ */ +@keyframes fade-slide-up { + from { opacity: 0; transform: translateY(18px); } + to { opacity: 1; transform: translateY(0); } } -.sphinxsidebarwrapper .internal, -.sphinxsidebarwrapper .external { - font-weight: 300; +/* ============================================================ + Base + ============================================================ */ +html, body { + overflow-x: clip; } body { - font-family: Roboto; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + color: var(--text); + background-color: #fff; + /* Subtle dot-grid background — same formula as landing page */ + background-image: + linear-gradient(to right, #fff 0%, transparent 10%, transparent 90%, #fff 100%), + radial-gradient(circle, rgba(194, 98, 10, 0.22) 1.5px, transparent 1.5px); + background-size: 100% 100%, 28px 28px; + background-repeat: no-repeat, repeat; } -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: Roboto !important; - font-weight: 300 !important; +/* Clip band pseudo-elements so they don't bleed over the sidebar */ +div.bodywrapper { + overflow: hidden; } -.toctree-l1 { - padding-bottom: 0.5em; +/* Keep content areas transparent so body dot-grid shows through */ +div.documentwrapper, +div.body { + background-color: transparent; + /* Soft amber glow at the top of each page */ + background-image: radial-gradient( + ellipse 120% 40% at 50% 0%, + rgba(194, 98, 10, 0.07) 0%, + transparent 65% + ); + background-size: 100% 100%; + background-repeat: no-repeat; +} + +/* ============================================================ + Typography + ============================================================ */ +h1, h2, h3, h4, h5, h6 { + font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif !important; + font-weight: 700 !important; + letter-spacing: -0.02em; + line-height: 1.25 !important; + color: var(--text) !important; } +h1 { font-size: 2rem !important; } +h2 { font-size: 1.5rem !important; margin-top: 2.5rem !important; } +h3 { font-size: 1.2rem !important; margin-top: 2rem !important; } + .body p, .body dd, .body li { - font-family: Roboto; - font-size: 1em; - font-weight: 300; - line-height: 2; - text-align: justify; + font-family: "Inter", sans-serif; + font-size: 1.0625rem; + font-weight: 400; + line-height: 1.8; + color: var(--text-2); + text-align: left; +} + +strong { + font-weight: 600; + color: var(--text); +} + +/* Links */ +a { + color: var(--primary) !important; + text-decoration: none; + transition: color var(--ease), opacity var(--ease); +} +a:hover { + color: var(--primary-dark) !important; + opacity: 0.85; +} + +img { + height: auto; + max-width: 100%; + border-radius: var(--r-sm); +} + +/* ============================================================ + Code Blocks + ============================================================ */ +div.highlight { + border-radius: var(--r-sm) !important; + overflow: hidden !important; + box-shadow: var(--shad-sm) !important; + border: 1px solid var(--border) !important; +} + +div.highlight pre { + font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", Consolas, monospace !important; + font-size: 0.875rem !important; + line-height: 1.7 !important; + padding: 1.25rem 1.5rem !important; +} + +/* Inline code */ +code, tt { + font-family: "JetBrains Mono", "Fira Code", Consolas, monospace !important; + font-size: 0.875em; + background: var(--bg-tint); + border: 1px solid var(--border); + border-radius: 4px; + padding: 0.1em 0.35em; + color: var(--primary-dark); +} + +/* Sphinx wraps each word in inside inline code — strip the + per-word styling so only the outer element gets the box. */ +code .pre, tt .pre { + font-family: inherit; + font-size: inherit; + background: none; + border: none; + border-radius: 0; + padding: 0; + color: inherit; +} + +/* ============================================================ + Admonitions + ============================================================ */ +div.admonition { + border: none !important; + border-radius: var(--r-sm) !important; + border-left: 4px solid var(--accent) !important; + box-shadow: var(--shad-sm) !important; + font-family: "Inter", sans-serif; + padding: 1.25rem 1.5rem !important; + background: var(--bg-mint) !important; + transition: box-shadow var(--ease) !important; + margin: 1.5rem 0 !important; +} + +div.admonition.warning, +div.admonition.caution { + border-left-color: var(--primary) !important; + background: var(--bg-tint) !important; +} + +div.admonition.danger, +div.admonition.error { + border-left-color: #c0544a !important; + background: #fff5f4 !important; +} + +div.admonition.tip, +div.admonition.hint { + border-left-color: #7aad6b !important; + background: #f3faf2 !important; +} + +div.admonition p.admonition-title { + font-family: "Inter", sans-serif; + font-weight: 700 !important; + color: var(--accent) !important; + margin: 0 0 0.5rem 0 !important; + font-size: 0.95rem !important; +} + +div.admonition.warning p.admonition-title, +div.admonition.caution p.admonition-title { color: var(--primary) !important; } +div.admonition.danger p.admonition-title, +div.admonition.error p.admonition-title { color: #c0544a !important; } +div.admonition.tip p.admonition-title, +div.admonition.hint p.admonition-title { color: #5a9a50 !important; } + +div.admonition p { + font-weight: 400 !important; + margin-top: 0.5rem !important; + color: var(--text-2); +} + +.admonition a { + font-weight: 500; +} + +/* ============================================================ + Full-bleed Color Bands (sphinx_design .. div:: band-tint / band-mint) + ============================================================ */ +.band-tint, +.band-mint { + position: relative; + padding: 3rem 0; + margin: 2.5rem 0; + z-index: 0; +} + +.band-tint::before, +.band-mint::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + width: 80vw; + z-index: -1; +} + +/* Bleeds to left viewport edge — rounded corner on the right */ +.band-tint::before { + left: calc(50% - 50vw); + border-radius: 0 var(--r-lg) var(--r-lg) 0; + background: rgba(254, 248, 240, 0.55); +} + +/* Bleeds to right viewport edge — rounded corner on the left */ +.band-mint::before { + left: calc(50% - 30vw); + border-radius: var(--r-lg) 0 0 var(--r-lg); + background: rgba(239, 246, 250, 0.55); +} + +/* Lead paragraph inside a band */ +.band-tint > p, +.band-mint > p { + font-size: 1.25rem !important; + font-weight: 700 !important; + color: var(--text) !important; + letter-spacing: -0.02em; + margin-bottom: 1.5rem !important; + line-height: 1.3 !important; +} + +/* ============================================================ + Cards — sphinx_design + ============================================================ */ +.sd-card { + border: 1px solid var(--border) !important; + border-top: 3px solid var(--primary) !important; + border-radius: var(--r) !important; + box-shadow: var(--shad-sm) !important; + transition: box-shadow var(--ease), transform var(--ease) !important; + overflow: hidden !important; + background: #fff !important; +} + +/* Extra spacing for standalone (non-grid) cards */ +:not(.sd-col) > .sd-card { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +.sd-card:hover { + box-shadow: var(--shad-lg) !important; + transform: translateY(-3px); +} + +.sd-card-body { + padding: 1.5rem 2rem !important; +} + +.sd-card-title { + font-family: "Inter", sans-serif !important; + font-size: 1.0625rem !important; + font-weight: 700 !important; + color: var(--text) !important; + margin-bottom: 0.75rem !important; + letter-spacing: -0.01em; +} + +/* Tinted variant */ +.card-tinted { + background: var(--bg-tint) !important; +} + +/* Dark variant */ +.sd-bg-dark { + background: linear-gradient(150deg, #1e1610 0%, #2d2018 100%) !important; + border-top-color: rgba(255, 255, 255, 0.12) !important; + border-color: rgba(255, 255, 255, 0.07) !important; +} +.sd-bg-dark .sd-card-title { color: #fff !important; } +.sd-bg-dark p { color: rgba(255,255,255,0.78) !important; } +.sd-bg-dark strong { color: #fff !important; font-weight: 600; } + +/* Stat cards (small centred grid items) */ +.stat-card .sd-card-title { + font-size: 1rem !important; +} + +/* sd-text-primary maps to accent blue on the landing page */ +.sd-text-primary { + color: var(--accent) !important; +} + +/* ============================================================ + Tables + ============================================================ */ +table.docutils { + border-collapse: collapse; + width: 100%; + border-radius: var(--r-sm); + overflow: hidden; + box-shadow: var(--shad-sm); + border: 1px solid var(--border) !important; + margin: 1.5rem 0; +} + +table.docutils thead tr th { + background: var(--bg-tint); + font-family: "Inter", sans-serif; + font-weight: 600; + font-size: 0.875rem; + letter-spacing: 0.03em; + text-transform: uppercase; + color: var(--text) !important; + padding: 0.75rem 1rem; + border-bottom: 2px solid var(--border) !important; +} + +table.docutils tbody tr td { + padding: 0.625rem 1rem; + border-bottom: 1px solid var(--border) !important; + font-size: 0.9375rem; + color: var(--text-2); +} + +table.docutils tbody tr:last-child td { + border-bottom: none !important; +} + +table.docutils tbody tr:nth-child(even) td { + background: rgba(254, 248, 240, 0.4); +} + +/* ============================================================ + Sidebar + ============================================================ */ +.sphinxsidebar .caption-text { + font-family: "Inter", sans-serif; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted) !important; +} + +.sphinxsidebarwrapper .caption-text { + font-size: 0.8rem; +} + +.logo { + width: 100%; +} + +.sphinxsidebarwrapper .logo-name { + display: none; +} + +.sphinxsidebarwrapper .logo { + margin-bottom: 2em; +} + +.sphinxsidebarwrapper .internal, +.sphinxsidebarwrapper .external { + font-family: "Inter", sans-serif; + font-weight: 400; + font-size: 0.9rem; +} + +/* Sidebar active link */ +.sphinxsidebarwrapper a.current { + color: var(--primary) !important; + font-weight: 600; } .search { @@ -49,62 +432,936 @@ h6 { margin-bottom: 1em; } +.sphinxsidebar input[type="text"] { + font-family: "Inter", sans-serif; + border: 1px solid var(--border); + border-radius: var(--r-sm); + padding: 0.35rem 0.6rem; + width: 100%; + box-sizing: border-box; + font-size: 0.9rem; + transition: border-color var(--ease); +} + +.sphinxsidebar input[type="text"]:focus { + outline: none; + border-color: var(--primary); +} + .sphinxsidebar input[type="submit"] { - font-family: "Roboto", serif; + font-family: "Inter", sans-serif; + background: var(--primary); + color: #fff; + border: none; + border-radius: var(--r-sm); + padding: 0.4rem 0.8rem; + cursor: pointer; + font-weight: 600; + transition: background var(--ease); +} + +.sphinxsidebar input[type="submit"]:hover { + background: var(--primary-dark); +} + +/* ============================================================ + Toctree / Navigation + ============================================================ */ +.toctree-l1 { + padding-bottom: 0.5em; } .caption { - margin-top: 1em; + margin-top: 1.5em; } .caption-text { - font-weight: 500; + font-family: "Inter", sans-serif !important; + font-weight: 700 !important; + font-size: 0.75rem !important; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted) !important; } -.sphinxsidebarwrapper .logo-name { - display: none; +/* Diataxis section colour-coding in sidebar captions */ +/* Tutorials → amber/primary */ +.toctree-wrapper p.caption:nth-of-type(1) .caption-text, +p.caption[id*="tutorial"] .caption-text { color: var(--primary) !important; } + +/* How-to → accent */ +.toctree-wrapper p.caption:nth-of-type(2) .caption-text, +p.caption[id*="how-to"] .caption-text { color: var(--accent) !important; } + +/* Reference → text */ +.toctree-wrapper p.caption:nth-of-type(3) .caption-text, +p.caption[id*="reference"] .caption-text { color: var(--text) !important; } + +/* Explanation → muted */ +.toctree-wrapper p.caption:nth-of-type(4) .caption-text, +p.caption[id*="explanation"] .caption-text { color: var(--text-muted) !important; } + +/* ============================================================ + PlantUML / plantweb flow diagrams + plantweb stores rendered images under _images/plantweb/ so + img[src*="plantweb"] selects exactly these and nothing else. + ============================================================ */ +img[src*="plantweb"] { + display: block; + margin: 2rem auto !important; + max-width: 780px; + width: 100%; + background: #fff; + border: 1px solid var(--border); + border-radius: var(--r); + box-shadow: var(--shad-md); + padding: 1.75rem 2rem; + box-sizing: border-box; } -.sphinxsidebarwrapper .logo { - margin-bottom: 2em; +/* ============================================================ + Asciinema Player — match landing page terminal theme + ============================================================ */ +.asciinema-player-wrapper { + border-radius: var(--r) !important; + overflow: hidden !important; + box-shadow: var(--shad-lg) !important; + margin: 1.5rem 0 !important; } -img { - height: auto; - max-width: 100%; +.asciinema-player-theme-monokai { + --term-color-background: #1e1610; + --term-color-foreground: #f0e8dc; + --term-color-0: #2d2018; + --term-color-1: #c0544a; + --term-color-2: #7aad6b; + --term-color-3: #c2820a; + --term-color-4: #4e7fa0; + --term-color-5: #9b6eb0; + --term-color-6: #5da0b5; + --term-color-7: #e8ddd0; + --term-color-8: #6b5e54; + --term-color-9: #e07060; + --term-color-10: #c2620a; + --term-color-11: #d9a44a; + --term-color-12: #7aa8c5; + --term-color-13: #b08ac0; + --term-color-14: #7dc0d5; + --term-color-15: #f0e8dc; } -div.admonition { +/* ============================================================ + Miscellaneous + ============================================================ */ + +/* Section permalink anchors — subtle */ +a.headerlink { + color: var(--border) !important; + font-size: 0.8em; + transition: color var(--ease) !important; +} +a.headerlink:hover { + color: var(--primary) !important; + opacity: 1 !important; +} + +/* Horizontal rule */ +hr { border: none; - border-radius: 0; - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), - 0 3px 1px -2px rgba(0, 0, 0, 0.2); - font-family: Roboto; - padding: 2em; - position: relative; - transition: box-shadow 0.25s; + height: 2px; + background-image: repeating-linear-gradient( + 90deg, + rgba(194, 98, 10, 0.3) 0, + rgba(194, 98, 10, 0.3) 2px, + transparent 2px, + transparent 24px + ); + margin: 2.5rem 0; } -div.admonition p.admonition-title { - font-family: "Roboto", serif; - font-weight: 300; - margin: 0 0 5px 0; +/* Field lists (option tables) */ +table.field-list th { + font-family: "Inter", sans-serif; + font-weight: 600; + color: var(--text-muted); + font-size: 0.875rem; } -div.admonition p { - font-weight: 300; - margin-top: 1em; +/* Sphinx "rubric" headings */ +p.rubric { + font-family: "Inter", sans-serif !important; + font-weight: 700 !important; + font-size: 0.9rem !important; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-muted) !important; + border-bottom: 1px solid var(--border); + padding-bottom: 0.4rem; + margin-bottom: 1rem; } -.admonition a { - font-weight: 300; +/* Page title (h1) accent underline */ +h1::after { + content: ""; + display: block; + width: 3rem; + height: 3px; + background: var(--grad); + border-radius: 2px; + margin-top: 0.5rem; } +body.dxt-tutorial h1::after { background: var(--dxt-tutorial); } +body.dxt-howto h1::after { background: var(--dxt-howto); } +body.dxt-reference h1::after { background: var(--dxt-reference); } +body.dxt-explanation h1::after { background: var(--dxt-explanation); } -strong { +/* Sphinx tabs — match palette */ +.sphinx-tabs-tab { + font-family: "Inter", sans-serif !important; + font-weight: 500 !important; +} + +.sphinx-tabs-tab[aria-selected="true"] { + color: var(--primary) !important; + border-bottom-color: var(--primary) !important; +} + +/* JSON schema tables */ +.json-schema-description p { + font-size: 0.9375rem; + line-height: 1.7; +} + +/* ============================================================ + CLI Cheatsheet — dark terminal card with syntax colouring + ============================================================ */ + +/* Outer card */ +.cheatsheet { + background: linear-gradient(150deg, #1c1410 0%, #281a0f 100%); + border-radius: var(--r-lg); + box-shadow: var(--shad-xl); + overflow: hidden; + margin: 2rem 0 3rem; + font-family: inherit; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +/* ── Masthead ─────────────────────────────────────────── */ +.cs-masthead { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 1rem 1.5rem 0.9rem; + background: rgba(194, 98, 10, 0.12); + border-bottom: 1px solid rgba(194, 98, 10, 0.25); +} + +.cs-masthead-brand { + display: flex; + align-items: center; + gap: 0.875rem; +} + +/* "df" logo badge */ +.cs-logo { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.25rem; + height: 2.25rem; + border-radius: 8px; + background: var(--primary); + color: #fff; + font-family: "JetBrains Mono", monospace; + font-size: 0.875rem; + font-weight: 700; + letter-spacing: -0.04em; + flex-shrink: 0; + box-shadow: 0 2px 8px rgba(194, 98, 10, 0.5); +} + +.cs-masthead-title { + font-family: "Inter", sans-serif; + font-size: 0.9375rem; + font-weight: 700; + color: #f0e8dc; + letter-spacing: -0.01em; + line-height: 1.3; +} + +.cs-masthead-tagline { + font-family: "Inter", sans-serif; + font-size: 0.7rem; + color: rgba(240, 232, 220, 0.45); + margin-top: 0.1rem; +} + +.cs-masthead-tagline code { + font-family: "JetBrains Mono", monospace !important; + font-size: 0.7rem !important; + background: none !important; + border: none !important; + padding: 0 !important; + color: rgba(194, 98, 10, 0.8) !important; +} + +/* Syntax legend in the top-right */ +.cs-masthead-legend { + display: flex; + gap: 0.625rem; + flex-shrink: 0; +} + +/* ── Token sample pills in the legend ── */ +.cs-tok { + font-family: "JetBrains Mono", monospace; + font-size: 0.65rem; + font-weight: 500; + padding: 0.2rem 0.5rem; + border-radius: 4px; + white-space: nowrap; +} + +.cs-tok-sc { background: rgba(232, 144, 74, 0.18); color: #e8904a; } +.cs-tok-fl { background: rgba(122, 168, 197, 0.18); color: #7aa8c5; } +.cs-tok-ag { background: rgba(160, 144, 128, 0.14); color: #9b8e85; } + +/* ── Two-column body ──────────────────────────────────── */ +.cs-body { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; + padding: 1.25rem 1.5rem 1.25rem; + column-gap: 0; +} + +.cs-col { + /* hairline divider between columns */ + padding: 0 1.25rem; +} + +.cs-col:first-child { + padding-left: 0; + border-right: 1px solid rgba(255, 255, 255, 0.07); +} + +.cs-col:last-child { + padding-right: 0; +} + +/* ── Section labels ───────────────────────────────────── */ +.cs-section { + margin-bottom: 1.25rem; +} + +.cs-section:last-child { + margin-bottom: 0; +} + +.cs-label { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0; + margin-bottom: 0.2rem; + font-family: "Inter", sans-serif; + font-size: 0.65rem; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + border-bottom: 1px solid; +} + +.cs-label-pip { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} + +.cs-label-sub { font-weight: 400; + letter-spacing: 0; + text-transform: none; + opacity: 0.65; + margin-left: auto; + font-size: 0.6rem; +} + +/* Colour variants */ +.cs-l-primary { + color: #e8904a; + border-color: rgba(232, 144, 74, 0.3); +} +.cs-l-primary .cs-label-pip { background: #e8904a; } + +.cs-l-accent { + color: #7aa8c5; + border-color: rgba(122, 168, 197, 0.3); +} +.cs-l-accent .cs-label-pip { background: #7aa8c5; } + +.cs-l-ci { + color: #b08ac0; + border-color: rgba(176, 138, 192, 0.3); +} +.cs-l-ci .cs-label-pip { background: #b08ac0; } + +.cs-l-utility { + color: #8ba88b; + border-color: rgba(139, 168, 139, 0.3); +} +.cs-l-utility .cs-label-pip { background: #8ba88b; } + +/* ── Command rows ─────────────────────────────────────── */ +.cs-row { + display: flex; + align-items: baseline; + gap: 0.75rem; + padding: 0.28rem 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); } -.pre { - padding-left: 0.2em; - padding-right: 0.2em; +.cs-row:last-child { + border-bottom: none; +} + +.cs-syn { + font-family: "JetBrains Mono", "Fira Code", Consolas, monospace; + font-size: 0.775rem; + white-space: nowrap; + flex-shrink: 0; + min-width: 0; +} + +/* Token colours */ +.cs-kw { color: rgba(240, 232, 220, 0.38); } /* dfetch — dim, always same */ +.cs-sc { color: #e8904a; font-weight: 600; } /* subcommand — amber hero */ +.cs-fl { color: #7aa8c5; } /* --flag / -f — accent blue */ +.cs-ag { color: #9b8e85; } /* / [opt] — muted */ + +.cs-dsc { + font-family: "Inter", sans-serif; + font-size: 0.75rem; + color: rgba(240, 232, 220, 0.52); + line-height: 1.45; + min-width: 0; +} + +/* inline code inside descriptions */ +.cs-dsc code { + font-family: "JetBrains Mono", monospace !important; + font-size: 0.7rem !important; + background: rgba(194, 98, 10, 0.15) !important; + border: none !important; + padding: 0.05em 0.3em !important; + border-radius: 3px !important; + color: #d97a3a !important; +} + +/* ── Footer ───────────────────────────────────────────── */ +.cs-footer { + padding: 0.6rem 1.5rem; + background: rgba(0, 0, 0, 0.25); + border-top: 1px solid rgba(255, 255, 255, 0.06); + font-family: "Inter", sans-serif; + font-size: 0.6875rem; + color: rgba(240, 232, 220, 0.3); + text-align: center; + letter-spacing: 0.02em; +} + +/* ── Responsive ───────────────────────────────────────── */ +@media (max-width: 960px) { + .cs-body { + grid-template-columns: 1fr; + } + .cs-col:first-child { + border-right: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.07); + padding-left: 0; + padding-bottom: 1.25rem; + margin-bottom: 1.25rem; + } + .cs-col:last-child { + padding-left: 0; + } + .cs-masthead { + flex-wrap: wrap; + gap: 0.75rem; + } + .cs-masthead-legend { + flex-shrink: 1; + flex-wrap: wrap; + } +} + +/* ── Fullscreen button ───────────────────────────────── */ +.cs-fullscreen-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.75rem; + height: 1.75rem; + padding: 0; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 5px; + color: rgba(240, 232, 220, 0.5); + cursor: pointer; + flex-shrink: 0; + transition: background 0.15s, color 0.15s; +} + +.cs-fullscreen-btn:hover { + background: rgba(255, 255, 255, 0.12); + color: rgba(240, 232, 220, 0.9); +} + +/* ── Fullscreen state ────────────────────────────────── */ +.cheatsheet:fullscreen, +.cheatsheet:-webkit-full-screen { + border-radius: 0; + overflow-y: auto; + padding: 2rem; + box-sizing: border-box; +} + +/* ── Print — light / black-and-white ─────────────────── */ +@media print { + @page { size: A4 landscape; margin: 0.6cm; } + + body, div.documentwrapper, div.body { + background-image: none !important; + background: #fff !important; + } + + /* Hide everything on the page except the cheatsheet */ + .sphinxsidebar, + .related, + .footer, + div.bodywrapper > *:not(div.body), + div.body > *:not(section), + section > *:not(.cheatsheet):not(h1) { + display: none !important; + } + + h1 { display: none !important; } + + .cheatsheet { + background: #fff !important; + box-shadow: none !important; + border: 0.5pt solid #ccc !important; + border-radius: 4px !important; + margin: 0 !important; + width: 100% !important; + box-sizing: border-box !important; + font-size: 8.5pt !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + .cs-masthead { + background: #f5f5f5 !important; + border-bottom: 1pt solid #ccc !important; + padding: 0.4rem 0.8rem !important; + } + + .cs-body { + padding: 0.6rem 0.8rem !important; + } + + .cs-section { margin-bottom: 0.6rem !important; } + + .cs-row { padding: 0.15rem 0 !important; } + + .cs-label { padding: 0.2rem 0 !important; margin-bottom: 0.1rem !important; } + + .cs-footer { padding: 0.3rem 0.8rem !important; } + + .cs-masthead-title { color: #111 !important; } + .cs-masthead-tagline { color: #555 !important; } + .cs-masthead-tagline code { color: #555 !important; } + + .cs-logo { + background: #333 !important; + color: #fff !important; + box-shadow: none !important; + } + + .cs-tok-sc { background: #eee !important; color: #333 !important; } + .cs-tok-fl { background: #eee !important; color: #333 !important; } + .cs-tok-ag { background: #eee !important; color: #333 !important; } + + .cs-col:first-child { border-right-color: #ddd !important; } + + .cs-l-primary { color: #333 !important; border-color: #bbb !important; } + .cs-l-accent { color: #333 !important; border-color: #bbb !important; } + .cs-l-ci { color: #333 !important; border-color: #bbb !important; } + .cs-l-utility { color: #333 !important; border-color: #bbb !important; } + + .cs-l-primary .cs-label-pip { background: #555 !important; } + .cs-l-accent .cs-label-pip { background: #555 !important; } + .cs-l-ci .cs-label-pip { background: #555 !important; } + .cs-l-utility .cs-label-pip { background: #555 !important; } + + .cs-row { border-bottom-color: #eee !important; } + .cs-kw { color: #888 !important; } + .cs-sc { color: #111 !important; font-weight: 700 !important; } + .cs-fl { color: #444 !important; } + .cs-ag { color: #666 !important; } + + .cs-dsc { color: #444 !important; } + .cs-dsc code { + background: #f0f0f0 !important; + color: #333 !important; + border: none !important; + } + + .cs-footer { + background: #f5f5f5 !important; + border-top-color: #ccc !important; + color: #777 !important; + } + + .cs-fullscreen-btn { display: none !important; } + + a::after { content: none !important; } } + +/* ============================================================ + Design Guide — colour swatches, type specimens, tokens + ============================================================ */ + +/* ── Colour palette grid ─────────────────────────────────── */ +.dg-palette { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1rem; + margin: 1.5rem 0 2rem; +} + +.dg-swatch { + border-radius: var(--r); + border: 1px solid var(--border); + overflow: hidden; + box-shadow: var(--shad-sm); +} + +.dg-swatch-color { + height: 72px; + width: 100%; +} + +.dg-swatch-body { + padding: 0.6rem 0.75rem 0.7rem; + background: #fff; +} + +.dg-swatch-token { + font-family: "JetBrains Mono", monospace; + font-size: 0.7rem; + color: var(--primary); + letter-spacing: -0.01em; + margin-bottom: 0.1rem; +} + +.dg-swatch-hex { + font-family: "JetBrains Mono", monospace; + font-size: 0.8rem; + font-weight: 500; + color: var(--text); + margin-bottom: 0.25rem; +} + +.dg-swatch-label { + font-family: "Inter", sans-serif; + font-size: 0.7rem; + font-weight: 600; + color: var(--text); + margin-bottom: 0.1rem; +} + +.dg-swatch-usage { + font-family: "Inter", sans-serif; + font-size: 0.675rem; + color: var(--text-muted); + line-height: 1.4; +} + +/* ── Typography specimens ────────────────────────────────── */ +.dg-type-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.25rem; + margin: 1.5rem 0 2rem; +} + +@media (max-width: 640px) { + .dg-type-grid { grid-template-columns: 1fr; } +} + +.dg-type-card { + border: 1px solid var(--border); + border-radius: var(--r); + overflow: hidden; + box-shadow: var(--shad-sm); +} + +.dg-type-header { + padding: 0.6rem 0.875rem; + background: var(--bg-tint); + border-bottom: 1px solid var(--border); + display: flex; + align-items: baseline; + gap: 0.75rem; +} + +.dg-type-name { + font-family: "Inter", sans-serif; + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--primary); +} + +.dg-type-meta { + font-family: "JetBrains Mono", monospace; + font-size: 0.65rem; + color: var(--text-muted); +} + +.dg-type-body { + padding: 1rem 0.875rem 0.875rem; + background: #fff; +} + +.dg-type-specimen { + font-family: "Inter", sans-serif; + margin: 0; + line-height: 1.4; +} + +.dg-type-specimen-mono { + font-family: "JetBrains Mono", monospace; + font-size: 0.9rem; + line-height: 1.6; + margin: 0; +} + +.dg-type-weights { + display: flex; + flex-direction: column; + gap: 0.35rem; + margin-top: 0.5rem; +} + +.dg-type-weight-row { + display: flex; + align-items: baseline; + gap: 0.75rem; +} + +.dg-weight-label { + font-family: "JetBrains Mono", monospace; + font-size: 0.6rem; + color: var(--text-muted); + width: 2.5rem; + flex-shrink: 0; +} + +.dg-weight-sample { + font-family: "Inter", sans-serif; + font-size: 0.9rem; + color: var(--text); + line-height: 1; +} + +/* ── Token reference table ───────────────────────────────── */ +.dg-tokens { + width: 100%; + border-collapse: collapse; + margin: 1.25rem 0 2rem; + font-size: 0.8rem; + border-radius: var(--r); + overflow: hidden; + box-shadow: var(--shad-sm) !important; + border: none !important; +} + +.dg-tokens th { + background: var(--bg-tint) !important; + color: var(--text) !important; + font-family: "Inter", sans-serif !important; + font-size: 0.7rem !important; + font-weight: 700 !important; + text-transform: uppercase !important; + letter-spacing: 0.06em !important; + padding: 0.55rem 0.875rem !important; + border-bottom: 2px solid var(--border) !important; + text-align: left !important; +} + +.dg-tokens td { + padding: 0.5rem 0.875rem !important; + border-bottom: 1px solid var(--border) !important; + vertical-align: middle !important; + background: #fff !important; + color: var(--text) !important; +} + +.dg-tokens tr:last-child td { border-bottom: none !important; } + +.dg-tokens code { + font-family: "JetBrains Mono", monospace !important; + font-size: 0.75rem !important; + background: var(--bg-tint) !important; + border: none !important; + padding: 0.1em 0.35em !important; + border-radius: 4px !important; + color: var(--primary) !important; +} + +.dg-dot { + display: inline-block; + width: 14px; + height: 14px; + border-radius: 50%; + vertical-align: middle; + margin-right: 0.4rem; + border: 1px solid rgba(0,0,0,.08); +} + +/* ============================================================ + Diataxis section colouring + Four quadrants: Tutorial · How-to · Reference · Explanation + Driven by diataxis.js which adds classes to and the + sidebar caption

/

    elements. + ============================================================ */ + +/* ── Coloured top strip on the content area ──────────────── */ +body.dxt-tutorial div.body { border-top: 4px solid var(--dxt-tutorial); } +body.dxt-howto div.body { border-top: 4px solid var(--dxt-howto); } +body.dxt-reference div.body { border-top: 4px solid var(--dxt-reference); } +body.dxt-explanation div.body { border-top: 4px solid var(--dxt-explanation); } + +/* ── Floating kind badge ──────────────────────────────────── */ +.dxt-badge { + display: inline-block; + padding: 0.2rem 0.65rem; + border-radius: 20px; + font-family: "Inter", sans-serif; + font-size: 0.675rem; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + float: right; + margin: 0.35rem 0 0.75rem 1rem; + clear: right; +} + +.dxt-badge-tutorial { + background: rgba(194, 98, 10, 0.10); + color: var(--dxt-tutorial); + border: 1px solid rgba(194, 98, 10, 0.22); +} +.dxt-badge-howto { + background: rgba(78, 127, 160, 0.10); + color: var(--dxt-howto); + border: 1px solid rgba(78, 127, 160, 0.22); +} +.dxt-badge-reference { + background: rgba(74, 122, 98, 0.10); + color: var(--dxt-reference); + border: 1px solid rgba(74, 122, 98, 0.22); +} +.dxt-badge-explanation { + background: rgba(122, 90, 154, 0.10); + color: var(--dxt-explanation); + border: 1px solid rgba(122, 90, 154, 0.22); +} + +/* ── Sidebar caption headings ─────────────────────────────── */ +.sphinxsidebar p.caption { + margin-bottom: 0.25rem; +} + +.sphinxsidebar p.caption.dxt-caption-tutorial, +.sphinxsidebar p.caption.dxt-caption-howto, +.sphinxsidebar p.caption.dxt-caption-reference, +.sphinxsidebar p.caption.dxt-caption-explanation { + display: flex; + align-items: center; + gap: 0.45rem; + padding: 0.3rem 0.5rem 0.3rem 0.6rem; + border-radius: var(--r-sm); + margin-bottom: 0.15rem; +} + +/* Coloured left border + tinted background per section */ +.sphinxsidebar p.caption.dxt-caption-tutorial { + border-left: 3px solid var(--dxt-tutorial); + background: rgba(194, 98, 10, 0.07); +} +.sphinxsidebar p.caption.dxt-caption-howto { + border-left: 3px solid var(--dxt-howto); + background: rgba(78, 127, 160, 0.07); +} +.sphinxsidebar p.caption.dxt-caption-reference { + border-left: 3px solid var(--dxt-reference); + background: rgba(74, 122, 98, 0.07); +} +.sphinxsidebar p.caption.dxt-caption-explanation { + border-left: 3px solid var(--dxt-explanation); + background: rgba(122, 90, 154, 0.07); +} + +/* Caption text colour */ +.sphinxsidebar p.caption.dxt-caption-tutorial .caption-text { color: var(--dxt-tutorial); } +.sphinxsidebar p.caption.dxt-caption-howto .caption-text { color: var(--dxt-howto); } +.sphinxsidebar p.caption.dxt-caption-reference .caption-text { color: var(--dxt-reference); } +.sphinxsidebar p.caption.dxt-caption-explanation .caption-text { color: var(--dxt-explanation); } + +/* Dot pip before caption text */ +.sphinxsidebar p.caption.dxt-caption-tutorial .caption-text::before, +.sphinxsidebar p.caption.dxt-caption-howto .caption-text::before, +.sphinxsidebar p.caption.dxt-caption-reference .caption-text::before, +.sphinxsidebar p.caption.dxt-caption-explanation .caption-text::before { + content: ''; + display: inline-block; + width: 7px; + height: 7px; + border-radius: 50%; + margin-right: 0.45rem; + vertical-align: middle; + flex-shrink: 0; +} +.sphinxsidebar p.caption.dxt-caption-tutorial .caption-text::before { background: var(--dxt-tutorial); } +.sphinxsidebar p.caption.dxt-caption-howto .caption-text::before { background: var(--dxt-howto); } +.sphinxsidebar p.caption.dxt-caption-reference .caption-text::before { background: var(--dxt-reference); } +.sphinxsidebar p.caption.dxt-caption-explanation .caption-text::before { background: var(--dxt-explanation); } + +/* ── Sidebar links — coloured hover per section ───────────── */ +.sphinxsidebar ul.dxt-section-tutorial a:hover { color: var(--dxt-tutorial); } +.sphinxsidebar ul.dxt-section-howto a:hover { color: var(--dxt-howto); } +.sphinxsidebar ul.dxt-section-reference a:hover { color: var(--dxt-reference); } +.sphinxsidebar ul.dxt-section-explanation a:hover { color: var(--dxt-explanation); } + +/* Active (current page) link uses section colour */ +.sphinxsidebar ul.dxt-section-tutorial .current > a { color: var(--dxt-tutorial); font-weight: 600; } +.sphinxsidebar ul.dxt-section-howto .current > a { color: var(--dxt-howto); font-weight: 600; } +.sphinxsidebar ul.dxt-section-reference .current > a { color: var(--dxt-reference); font-weight: 600; } +.sphinxsidebar ul.dxt-section-explanation .current > a { color: var(--dxt-explanation); font-weight: 600; } + +/* ── Gherkin syntax highlighting — diataxis section colours ── */ +/* Keywords (Given/When/Then/And/table pipes/docstring markers) */ +.highlight-gherkin .k { color: var(--primary); font-weight: bold; } + +body.dxt-tutorial .highlight-gherkin .k { color: var(--dxt-tutorial); } +body.dxt-howto .highlight-gherkin .k { color: var(--dxt-howto); } +body.dxt-reference .highlight-gherkin .k { color: var(--dxt-reference); } +body.dxt-explanation .highlight-gherkin .k { color: var(--dxt-explanation); } diff --git a/doc/static/fonts/inter/.dfetch_data.yaml b/doc/static/fonts/inter/.dfetch_data.yaml new file mode 100644 index 000000000..6b056726c --- /dev/null +++ b/doc/static/fonts/inter/.dfetch_data.yaml @@ -0,0 +1,10 @@ +# This is a generated file by dfetch. Don't edit this, but edit the manifest. +# For more info see https://dfetch.rtfd.io/en/latest/getting_started.html +dfetch: + branch: '' + hash: 428a1aa8c0009c0c0583bbb49edd99b7 + last_fetch: 29/03/2026, 18:08:23 + patch: '' + remote_url: https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip + revision: https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip + tag: '' diff --git a/doc/static/fonts/inter/Inter-Bold.woff2 b/doc/static/fonts/inter/Inter-Bold.woff2 new file mode 100644 index 000000000..b9e3cb3b1 Binary files /dev/null and b/doc/static/fonts/inter/Inter-Bold.woff2 differ diff --git a/doc/static/fonts/inter/Inter-ExtraBold.woff2 b/doc/static/fonts/inter/Inter-ExtraBold.woff2 new file mode 100644 index 000000000..bbd000698 Binary files /dev/null and b/doc/static/fonts/inter/Inter-ExtraBold.woff2 differ diff --git a/doc/static/fonts/inter/Inter-ExtraLight.woff2 b/doc/static/fonts/inter/Inter-ExtraLight.woff2 new file mode 100644 index 000000000..f69100372 Binary files /dev/null and b/doc/static/fonts/inter/Inter-ExtraLight.woff2 differ diff --git a/doc/static/fonts/inter/Inter-Light.woff2 b/doc/static/fonts/inter/Inter-Light.woff2 new file mode 100644 index 000000000..f3e012a45 Binary files /dev/null and b/doc/static/fonts/inter/Inter-Light.woff2 differ diff --git a/doc/static/fonts/inter/Inter-Medium.woff2 b/doc/static/fonts/inter/Inter-Medium.woff2 new file mode 100644 index 000000000..fdfdcc699 Binary files /dev/null and b/doc/static/fonts/inter/Inter-Medium.woff2 differ diff --git a/doc/static/fonts/inter/Inter-Regular.woff2 b/doc/static/fonts/inter/Inter-Regular.woff2 new file mode 100644 index 000000000..2bcd222ec Binary files /dev/null and b/doc/static/fonts/inter/Inter-Regular.woff2 differ diff --git a/doc/static/fonts/inter/Inter-SemiBold.woff2 b/doc/static/fonts/inter/Inter-SemiBold.woff2 new file mode 100644 index 000000000..fbae113d2 Binary files /dev/null and b/doc/static/fonts/inter/Inter-SemiBold.woff2 differ diff --git a/doc/static/fonts/inter/Inter-Thin.woff2 b/doc/static/fonts/inter/Inter-Thin.woff2 new file mode 100644 index 000000000..83bf54be3 Binary files /dev/null and b/doc/static/fonts/inter/Inter-Thin.woff2 differ diff --git a/doc/static/fonts/inter/LICENSE.txt b/doc/static/fonts/inter/LICENSE.txt new file mode 100644 index 000000000..9b2ca37b3 --- /dev/null +++ b/doc/static/fonts/inter/LICENSE.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/doc/static/fonts/jetbrains-mono/.dfetch_data.yaml b/doc/static/fonts/jetbrains-mono/.dfetch_data.yaml new file mode 100644 index 000000000..09be96327 --- /dev/null +++ b/doc/static/fonts/jetbrains-mono/.dfetch_data.yaml @@ -0,0 +1,10 @@ +# This is a generated file by dfetch. Don't edit this, but edit the manifest. +# For more info see https://dfetch.rtfd.io/en/latest/getting_started.html +dfetch: + branch: '' + hash: f4abafca39b328491c53a4f4f4d86ef4 + last_fetch: 29/03/2026, 18:08:24 + patch: '' + remote_url: https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip + revision: https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip + tag: '' diff --git a/doc/static/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 b/doc/static/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 new file mode 100644 index 000000000..669d04cdf Binary files /dev/null and b/doc/static/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 differ diff --git a/doc/static/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 b/doc/static/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 new file mode 100644 index 000000000..40da42765 Binary files /dev/null and b/doc/static/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 differ diff --git a/doc/static/js/diataxis.js b/doc/static/js/diataxis.js new file mode 100644 index 000000000..b71a71ebc --- /dev/null +++ b/doc/static/js/diataxis.js @@ -0,0 +1,96 @@ +/** + * Diataxis section colouring + * + * Classifies each page into one of the four Diataxis quadrants, then: + * 1. Adds a `dxt-
    ` class to so CSS can paint a coloured + * top strip on the content area. + * 2. Inserts a small floating badge ("Tutorial", "How-to Guide", etc.) + * at the top of the page body. + * 3. Adds `dxt-caption-
    ` to each sidebar caption

    and + * `dxt-section-

    ` to the
      that follows it, so CSS can + * colour the sidebar navigation headers. + */ +(function () { + "use strict"; + + /* ── Page → section map ─────────────────────────────── */ + var PAGE_SECTIONS = { + installation: "tutorial", + getting_started:"tutorial", + migration: "howto", + patching: "howto", + "check-ci": "howto", + sbom: "howto", + troubleshooting:"howto", + contributing: "howto", + manifest: "reference", + manual: "reference", + changelog: "reference", + legal: "reference", + vendoring: "explanation", + alternatives: "explanation", + internal: "explanation", + }; + + /* ── Sidebar caption text → section key ─────────────── */ + var CAPTION_SECTIONS = { + "Tutorials": "tutorial", + "How-to Guides": "howto", + "Reference": "reference", + "Explanation": "explanation", + }; + + /* ── Badge labels ───────────────────────────────────── */ + var BADGE_LABELS = { + tutorial: "Tutorial", + howto: "How-to Guide", + reference: "Reference", + explanation: "Explanation", + }; + + /* ── Determine current page ─────────────────────────── */ + var page = window.location.pathname + .replace(/\/$/, "/index") + .replace(/.*\//, "") + .replace(/\.html$/, ""); + + var section = PAGE_SECTIONS[page] || null; + + function applyClasses() { + /* ── 1. Body class ──────────────────────────────────── */ + if (section && document.body) { + document.body.classList.add("dxt-" + section); + } + + /* ── 2. Floating badge ──────────────────────────────── */ + if (section) { + var body = document.querySelector("div.body") || document.querySelector("div.document"); + if (body) { + var badge = document.createElement("span"); + badge.className = "dxt-badge dxt-badge-" + section; + badge.textContent = BADGE_LABELS[section]; + body.insertBefore(badge, body.firstChild); + } + } + + /* ── 3. Sidebar captions ────────────────────────────── */ + var captions = document.querySelectorAll(".sphinxsidebar p.caption, .sphinxsidebarwrapper p.caption"); + captions.forEach(function (el) { + var span = el.querySelector(".caption-text"); + if (!span) return; + var key = CAPTION_SECTIONS[span.textContent.trim()]; + if (!key) return; + el.classList.add("dxt-caption-" + key); + var ul = el.nextElementSibling; + if (ul && ul.tagName === "UL") { + ul.classList.add("dxt-section-" + key); + } + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", applyClasses); + } else { + applyClasses(); + } +})(); diff --git a/doc/static/uml/check.puml b/doc/static/uml/check.puml index ba9fce82c..84c6ea238 100644 --- a/doc/static/uml/check.puml +++ b/doc/static/uml/check.puml @@ -1,8 +1,28 @@ @startuml -start +skinparam defaultFontName Arial +skinparam defaultFontSize 12 +skinparam defaultFontColor #1c1917 +skinparam shadowing false +skinparam BackgroundColor transparent +skinparam RoundCorner 8 + +skinparam ActivityBackgroundColor #ffffff +skinparam ActivityBorderColor #c2620a +skinparam ActivityBorderThickness 1.5 +skinparam ActivityFontColor #1c1917 +skinparam ActivityFontSize 12 +skinparam ActivityFontName Arial +skinparam ActivityArrowColor #78716c +skinparam ActivityArrowFontColor #78716c +skinparam ActivityArrowFontSize 11 +skinparam ActivityStartColor #c2620a +skinparam ActivityEndColor #1c1917 +skinparam ActivityBarColor #c2620a +skinparam ActivityDiamondBackgroundColor #eff6fa +skinparam ActivityDiamondBorderColor #4e7fa0 +skinparam ActivityDiamondFontColor #3a6682 -skinparam monochrome true -skinparam defaultFontName Frutiger +start :Get version on disk; diff --git a/doc/static/uml/commands.puml b/doc/static/uml/commands.puml new file mode 100644 index 000000000..da5fceab1 --- /dev/null +++ b/doc/static/uml/commands.puml @@ -0,0 +1,91 @@ +@startmindmap + + +* dfetch +** Foundational <> +*** init +*** import +*** add +*** check +*** update + +** Utilities <> +*** freeze +*** environment +*** validate + +left side + +** Patching <> +*** diff +*** update-patch +*** format-patch + +** CI / CD <> +*** check\n--jenkins-json +*** check\n--sarif +*** check\n--code-climate +*** report + +@endmindmap diff --git a/doc/static/uml/update.puml b/doc/static/uml/update.puml index 66394eed8..e8f2a8a11 100644 --- a/doc/static/uml/update.puml +++ b/doc/static/uml/update.puml @@ -1,8 +1,34 @@ @startuml -start +skinparam defaultFontName Arial +skinparam defaultFontSize 12 +skinparam defaultFontColor #1c1917 +skinparam shadowing false +skinparam BackgroundColor transparent +skinparam RoundCorner 8 + +skinparam ActivityBackgroundColor #ffffff +skinparam ActivityBorderColor #c2620a +skinparam ActivityBorderThickness 1.5 +skinparam ActivityFontColor #1c1917 +skinparam ActivityFontSize 12 +skinparam ActivityFontName Arial +skinparam ActivityArrowColor #78716c +skinparam ActivityArrowFontColor #78716c +skinparam ActivityArrowFontSize 11 +skinparam ActivityStartColor #c2620a +skinparam ActivityEndColor #1c1917 +skinparam ActivityBarColor #c2620a +skinparam ActivityDiamondBackgroundColor #eff6fa +skinparam ActivityDiamondBorderColor #4e7fa0 +skinparam ActivityDiamondFontColor #3a6682 -skinparam monochrome true -skinparam defaultFontName Frutiger +skinparam PartitionBackgroundColor #fef8f0 +skinparam PartitionBorderColor #e7e0d8 +skinparam PartitionFontColor #78716c +skinparam PartitionFontStyle bold +skinparam PartitionFontSize 10 + +start partition "Update required" { @@ -14,12 +40,6 @@ partition "Update required" { else (no) stop endif - - ' if (Hash on disk same\nas in metadata?) then (yes) - ' elseif (Force flag given?) then (yes) - ' else - ' stop - ' endif } partition "Prepare for update" { diff --git a/doc/getting_started.rst b/doc/tutorials/getting_started.rst similarity index 100% rename from doc/getting_started.rst rename to doc/tutorials/getting_started.rst diff --git a/doc/installation.rst b/doc/tutorials/installation.rst similarity index 97% rename from doc/installation.rst rename to doc/tutorials/installation.rst index 5efd777e6..5e01cac17 100644 --- a/doc/installation.rst +++ b/doc/tutorials/installation.rst @@ -80,4 +80,4 @@ Run the following command to verify the installation dfetch environment -.. asciinema:: asciicasts/environment.cast +.. asciinema:: ../asciicasts/environment.cast diff --git a/doc/vendoring.rst b/doc/vendoring.rst deleted file mode 100644 index bf42d9553..000000000 --- a/doc/vendoring.rst +++ /dev/null @@ -1,350 +0,0 @@ - - -Vendoring -========= - -Vendoring is the practice of copying the source code of another project directly -into your own project's repository. Instead of relying on a package manager to -fetch dependencies at build or install time, the dependency code is stored -alongside the project itself and treated as part of the source tree. - -Although the definition is simple, vendoring has long been controversial. Some -engineers see it as a practical way to gain control and reliability, while others -have historically described it as an anti-pattern. Both views exist for good -reasons, and understanding the trade-offs matters more than choosing a side. - -What People Mean by Vendoring ------------------------------ - -At a basic level, vendoring means that a project can be built using only what is -present in its repository. No network access is required, no external registries -need to be available, and no global package cache is assumed. Checking out a -specific revision of the repository is sufficient to reproduce a build from that -point in time. - -The term originally became common in communities that explicitly placed third- -party code into directories named `vendor`. Over time, the word broadened to -describe any approach where dependency source code is copied into the project, -regardless of directory layout or tooling. - -Vendoring does not necessarily imply permanent forks or heavy modification of -third-party code. In many cases, vendored dependencies are kept pristine and -updated mechanically from upstream sources. - -Why Vendoring Can Be Helpful ----------------------------- - -The strongest argument for vendoring is reproducibility. - -When dependencies are fetched dynamically, builds implicitly depend on the -continued availability and behavior of external services. Registries can go -offline, packages can be removed or yanked, and transitive dependencies can change -in ways that are difficult to predict. Vendoring removes these uncertainties by -making the build inputs explicit and local. - -Vendoring also improves visibility. When dependency code lives in the same source -tree, it is easier to inspect, debug, and understand. Developers can step into -library code without context switching or relying on tooling to fetch sources on -demand. This visibility can reveal how much code a project is actually relying on, -especially when compared to the apparent simplicity of a short dependency list in -a configuration file. - -Another benefit is reduced exposure to supply chain risk. Vendoring does not make -third-party code safe, but it shifts trust from live infrastructure to explicit -review. The project decides when dependencies are updated and exactly what code is -being included. - -Some proponents also argue that vendoring creates a healthy friction around -dependencies. When adding a dependency requires consciously pulling in its source -code, teams may be more selective and more aware of the long-term cost of that -decision. - -The Costs and Risks of Vendoring --------------------------------- - -Vendoring introduces real downsides. - -One common criticism is that vendoring discards upstream version control history. -When code is copied into another repository, its original commit history, tags, -and branches are no longer directly visible. This can make updates harder, -especially if vendored code has been locally modified. - -Vendoring can also encourage divergence. When dependency code is nearby and easy -to change, there is a temptation to patch it locally rather than contribute fixes -upstream. Over time, this can result in silent forks that are difficult to -maintain or reconcile with new releases. - -Repository size and noise are practical concerns as well. Vendored dependencies -increase clone size and can dominate diffs during updates. Large dependency -refreshes can obscure meaningful changes to the project's own code, making review -and merging more difficult. - -Maintenance responsibility is another cost. Vendored dependencies do not update -themselves. Security fixes, bug fixes, and compatibility updates must be pulled in -manually. Without a clear policy and tooling support, vendored code can easily -become outdated. - -Transitive Dependencies ------------------------ - -Vendoring becomes significantly more complex once transitive dependencies are -considered. - -Vendoring a single library often requires vendoring everything that library -depends on, and everything those dependencies rely on in turn. For ecosystems -with deep or fast-moving dependency graphs, this can quickly become a substantial -burden. - -Package managers largely exist to manage this complexity by resolving dependency -graphs, handling version conflicts, and sharing common dependencies across -projects. Vendoring replaces that automation with explicit ownership, which is -sometimes desirable and sometimes overwhelming. - -A Brief History ---------------- - -Vendoring predates modern package managers. - -Early C and C++ projects routinely shipped third-party libraries inline because -there was no reliable way to depend on system-installed packages. Many Unix -programs were distributed as self-contained source releases that included all -required code. - -As centralized package registries and dependency managers matured, vendoring -became less common and was sometimes criticized as outdated or unprofessional. -Automated updates, shared caches, and smaller repositories were seen as clear -wins. - -Interest in vendoring returned as software supply chain risks became more visible. -Registry outages, dependency hijacks, and ecosystem fragility highlighted the -costs of relying entirely on external infrastructure. - -Today, vendoring is best understood as a trade-off rather than a relic. - -Vendoring Across Languages --------------------------- - -Different language ecosystems have adopted vendoring to very different degrees. - -Go is strongly associated with vendoring. Early versions of Go lacked a central -dependency manager, which made vendoring a practical solution. Even after the -introduction of module support, vendoring remains a first-class workflow, with -tooling that understands and prioritizes vendored dependencies. One of Go's proverbs -is "A little copying is better than a little dependency." - -Rust supports vendoring but does not encourage it by default. The Rust ecosystem -places a strong emphasis on reproducibility through its package registry and lock -files, reducing the need for vendoring in everyday development. Vendoring is still -common in embedded systems, regulated environments, and long-term-support -projects. - -JavaScript occupies an unusual position. Dependency source code is typically -present locally in directories such as `node_modules`, but it is rarely checked -into version control due to size and churn. Fully vendoring dependencies is -possible but uncommon. - -Python has a mixed history. Vendoring was common in earlier projects and remains -common in small tools, embedded Python environments, and source-distributed -applications. Modern Python development more often relies on virtual environments -and lock files, but vendoring has never disappeared entirely. - -C and C++ continue to vendor dependencies frequently. The lack of a universal -package manager, combined with ABI compatibility concerns and platform -differences, makes vendoring a practical and sometimes unavoidable choice. - -Conclusion ----------- - -Vendoring is neither a best practice nor an anti-pattern. - -It is a deliberate trade-off that exchanges convenience and automatic updates for -control, predictability, and independence from external systems. In some contexts, -that trade is clearly worthwhile. In others, it introduces more cost than benefit. - -Used intentionally and with an understanding of its limitations, vendoring is -simply one tool among many for managing dependencies. - -Best Practices --------------- - -The following practices are drawn from our own usage of *Dfetch* and real-world policies and respected guidelines. -They *mitigate* vendoring risks; they do not eliminate them. - -.. admonition :: Explicit Version Pinning and Provenance - - Every vendored dependency must be pinned to an explicit version, tag, or commit, and its source must be documented. - - **Rationale** Vendored code is often added once and then forgotten. Without automation, vulnerabilities, license issues, - and inconsistencies can persist unnoticed long after initial inclusion. - - * pip: `vendor.txt` tracks versions and sources - * Go/Kubernetes: `go.mod`, `go.sum`, `vendor/modules.txt` - * Cargo: `Cargo.lock` + vendored sources - * Guidelines: `OWASP SCVS `_, OpenSSF, NIST SP 800-161, SLSA - - *Dfetch* addresses this by having a declarative :ref:`manifest` and the option to :ref:`freeze` dependences to make each - revision explicit. - -.. admonition :: Reproducible and Offline Builds - - Vendoring must enable fully reproducible and offline builds. - - **Rationale** The point of vendoring code is to remove external dependencies, by making sure an offline build succeeds it is - proven that builds are not dependent on external sources. - - * Go: committed `vendor/` - * Cargo: `.cargo/config.toml` + `cargo vendor` - * pip: vendored wheels and pure-Python dependencies - * Guidelines: SLSA, OpenSSF - - *Dfetch* doesn't directly address this, this is a policy to follow. - -.. admonition :: Trust Upstream After Due Diligence - - Vendored dependencies are not reviewed line-by-line, upstream (unit) tests are not run. - Do not auto-format vendored code. - - **Rationale** Reviewing every line of external code is costly and rarely effective. By performing due diligence - on upstream sources, you can trust their correctness and security while minimizing maintenance burden. - - Reviewers should verify: - - * Version changes and pinning - * Changelogs and release notes for regressions or security issues - * License compatibility - * CI status and test results of the upstream project - * Evidence of active maintenance and community support - - Do not run upstream unit tests locally, apply formatting or style changes, or make cosmetic changes to vendored code. - - This principle is aligned with: - - * OWASP Software Component Verification Standard - * Google Open Source Security Guidelines - * OpenSSF Best Practices - - *Dfetch* supports this approach via its manifest and metadata file (``.dfetch_data.yaml``), which can be reviewed independently - of the vendored code itself. - - -.. admonition :: Separation of Vendor Updates from Product Changes - - Vendored dependency updates must be isolated from functional code changes. - - * Separate PRs or commits - * Clear commit messages documenting versions and rationale - - **Rationale** By separation the review noise will be reduced, letting maintainers focus on important changes. - - *Dfetch* doesn't address this directly. - -.. admonition :: Minimize Local Modifications - - Vendored code must not be modified directly unless unavoidable. - - If modifications are required: - - * Document patches explicitly - * Prefer patch/overlay mechanisms - * Upstream changes whenever possible - * pip: documented adaptations - * Cargo: `[patch]` mechanism - * Guidelines: Google OSS, OWASP, OpenSSF - - **Rationale** The vendored dependency may diverge, keeping it the same as upstream makes it easy to keep following - upstream updates. Also by upstreaming any changes, more people outside the project can profit from any fixes. - - *Dfetch* addresses this by providing a ``dfetch diff`` (:ref:`Diff`) command and a ``patch`` (:ref:`Patch`) attribute in the manifest. - This attribute can point to one or more patches that should be applied to the vendored dependency. - Next to this there is a CI system to detect local changes using :ref:`Check`. - -.. admonition :: Continuous Automation and Security Scanning - - Vendored dependencies must be continuously verified through automation. - - * CI verifies vendor consistency - * Dependency and CVE scanning - * SBOM generation - - **Rationale** By copy-pasting a dependency, there maybe silent security degradation since there is no automatic updates. - - *Dfetch* addresses this by providing a ``dfetch check`` (:ref:`Check`) command to see if vendored dependencies are out-of-date and - various report formats (including SBoM) to check vulnerabilities. - -.. admonition :: Track License and Legal Information - - All vendored dependencies must have their license and legal information explicitly recorded. - - **Rationale** Ensuring license compliance prevents legal issues and maintains compatibility with your project's license. - - * Track license type for each vendored dependency. - * Use machine-readable formats where possible (e.g., SPDX identifiers). - * Include license documentation alongside vendored code. - * Guidelines: OWASP, OpenSSF, Google OSS best practices. - - *Dfetch* addresses this by even when only a subfolder is fetched, retaining the license file. - -.. admonition :: Vendor Only What You Need - - Minimize vendored code to what is strictly necessary for your project. - - **Rationale** Vendoring unnecessary code increases maintenance burden, security risk, and potential for patch rot. - Include only the specific modules, packages, subfolder or components your project depends on. - - *Dfetch* enables this by allowing to fetch only a subfolder using the ``src:`` attribute. - -.. admonition :: Isolate Vendored Dependencies - - Vendored dependencies must be clearly isolated from first-party code. - - **Rationale** Isolation prevents accidental coupling, avoids namespace conflicts, and makes audits, updates, and - removals easier. Vendored code should be unmistakably identifiable as third-party code. - - * Place vendored dependencies in a clearly named and well-known directory (e.g. ``vendor/``, ``_vendor/``). - * Avoid mixing vendored code with product or library sources. - * Use language-supported namespace or module isolation where available. - * Prevent accidental imports of vendored internals by first-party code. - * Keep vendored code mechanically separable to enable future un-vendoring. - - This principle follows established practices in: - - * Go (``vendor/`` directory) - * pip (``pip/_vendor``) - * Cargo (``vendor/`` layout) - * Google OSS and OpenSSF guidelines - - *Dfetch* enables this by allowing to store the vendored dependency in a folder using the ``dst:`` attribute. - -Real-world projects using vendoring ------------------------------------ - -- `Dynaconf - (Python) `_ -- `PIP - (Python) `_ -- `Kubernetes - (Go) `_ -- `Cargo - (Rust) `_ - -Real world projects using Dfetch --------------------------------- - -Here are some links to example projects using *Dfetch*. - -- `Dfetch`: https://github.com/dfetch-org/dfetch -- `ModbusScope`: https://github.com/ModbusScope/ModbusScope -- `Red Jackets Jazzband`: https://github.com/red-jackets-jazzband/website -- `Example Yocto`: https://github.com/dfetch-org/example-yocto -- `Example Zephyr`: https://github.com/dfetch-org/example-zephyr - -Internally we use *Dfetch* for various projects and uses. - - -Further Reading ---------------- - -- `Vendoring is a vile anti pattern - Michael F. Lamb `_ -- `SO: What is "vendoring"? - Niels Bom `_ -- `Why we stopped vendoring our dependencies - Carlos Perez `_ -- `Vendoring - Carson Gross `_ -- `Our Software Dependency Problem - Russ Cox `_ -- `On Managing External Dependencies - Phillip Johnston `_ -- `PIP's vendoring policy `_ -- `SubPatch benefits `_ diff --git a/features/add-project-through-cli.feature b/features/add-project-through-cli.feature index 8c4628520..733d588a0 100644 --- a/features/add-project-through-cli.feature +++ b/features/add-project-through-cli.feature @@ -3,14 +3,10 @@ Feature: Add a project to the manifest via the CLI *DFetch* can add a new project entry to the manifest without requiring manual YAML editing. ``dfetch add `` inspects the remote repository, fills in sensible defaults (name, destination, default branch), shows a - preview, and appends the entry to ``dfetch.yaml`` after confirmation. - - Pass ``--interactive`` / ``-i`` to be guided step-by-step through every - manifest field (name, destination, branch/tag/revision, optional src, - optional ignore list). + preview, and appends the entry to ``dfetch.yaml``. Use ``--name``, ``--dst``, ``--version``, ``--src``, ``--ignore`` to - pre-fill individual fields (works with and without ``-i``). + pre-fill individual fields. Background: Given a git repository "MyLib.git" @@ -70,189 +66,6 @@ Feature: Add a project to the manifest via the CLI dst: ext/MyLib """ - Scenario: Interactive add guides through each field - Given the manifest 'dfetch.yaml' - """ - manifest: - version: '0.0' - projects: - - name: ext/existing - url: some-remote-server/existing.git - """ - When I run "dfetch add -i some-remote-server/MyLib.git" with inputs - | Question | Answer | - | Project name | my-lib | - | Destination path | libs/my | - | Version | master | - | Source path | | - | Ignore paths | | - | Add project to manifest? | y | - | Run update | n | - Then the manifest 'dfetch.yaml' contains entry - """ - - name: my-lib - url: some-remote-server/MyLib.git - branch: master - dst: libs/my - """ - - Scenario: Interactive add with tag version - Given the manifest 'dfetch.yaml' - """ - manifest: - version: '0.0' - projects: - - name: existing - url: some-remote-server/existing.git - """ - When I run "dfetch add -i some-remote-server/MyLib.git" with inputs - | Question | Answer | - | Project name | my-lib | - | Destination path | my-lib | - | Version | v1 | - | Source path | | - | Ignore paths | | - | Add project to manifest? | y | - | Run update | n | - Then the manifest 'dfetch.yaml' contains entry - """ - - name: my-lib - url: some-remote-server/MyLib.git - tag: v1 - """ - - Scenario: Interactive add with src subpath - Given the manifest 'dfetch.yaml' - """ - manifest: - version: '0.0' - projects: - - name: existing - url: some-remote-server/existing.git - """ - When I run "dfetch add -i some-remote-server/MyLib.git" with inputs - | Question | Answer | - | Project name | my-lib | - | Destination path | my-lib | - | Version | master | - | Source path | docs/api | - | Ignore paths | | - | Add project to manifest? | y | - | Run update | n | - Then the manifest 'dfetch.yaml' contains entry - """ - - name: my-lib - url: some-remote-server/MyLib.git - branch: master - src: docs/api - """ - - Scenario: Interactive add with ignore list - Given the manifest 'dfetch.yaml' - """ - manifest: - version: '0.0' - projects: - - name: existing - url: some-remote-server/existing.git - """ - When I run "dfetch add -i some-remote-server/MyLib.git" with inputs - | Question | Answer | - | Project name | my-lib | - | Destination path | my-lib | - | Version | master | - | Source path | | - | Ignore paths | docs, tests | - | Add project to manifest? | y | - | Run update | n | - Then the manifest 'dfetch.yaml' contains entry - """ - - name: my-lib - url: some-remote-server/MyLib.git - branch: master - ignore: - - docs - - tests - """ - - Scenario: Interactive add triggers immediate fetch when update is accepted - Given the manifest 'dfetch.yaml' - """ - manifest: - version: '0.0' - projects: - - name: existing - url: some-remote-server/existing.git - """ - When I run "dfetch add -i some-remote-server/MyLib.git" with inputs - | Question | Answer | - | Project name | MyLib | - | Destination path | MyLib | - | Version | master | - | Source path | | - | Ignore paths | | - | Add project to manifest? | y | - | Run update | y | - Then the manifest 'dfetch.yaml' contains entry - """ - - name: MyLib - url: some-remote-server/MyLib.git - branch: master - """ - And 'MyLib/README.md' exists - - Scenario: Interactive add with abort does not modify manifest - Given the manifest 'dfetch.yaml' - """ - manifest: - version: '0.0' - projects: - - name: existing - url: some-remote-server/existing.git - """ - When I run "dfetch add -i some-remote-server/MyLib.git" with inputs - | Question | Answer | - | Project name | MyLib | - | Destination path | MyLib | - | Version | master | - | Source path | | - | Ignore paths | | - | Add project to manifest? | n | - Then the manifest 'dfetch.yaml' is replaced with - """ - manifest: - version: '0.0' - projects: - - name: existing - url: some-remote-server/existing.git - """ - - Scenario: Interactive add with empty src (repo root) does not add src field - Given the manifest 'dfetch.yaml' - """ - manifest: - version: '0.0' - projects: - - name: existing - url: some-remote-server/existing.git - """ - When I run "dfetch add -i some-remote-server/MyLib.git" with inputs - | Question | Answer | - | Project name | MyLib | - | Destination path | MyLib | - | Version | master | - | Source path | | - | Ignore paths | | - | Add project to manifest? | y | - | Run update | n | - Then the manifest 'dfetch.yaml' contains entry - """ - - name: MyLib - url: some-remote-server/MyLib.git - branch: master - """ - And the manifest 'dfetch.yaml' does not contain 'src:' - Scenario: Non-interactive add with field overrides Given the manifest 'dfetch.yaml' """ @@ -270,27 +83,3 @@ Feature: Add a project to the manifest via the CLI branch: master dst: libs/my-lib """ - - Scenario: Interactive add with pre-filled fields skips those prompts - Given the manifest 'dfetch.yaml' - """ - manifest: - version: '0.0' - projects: - - name: existing - url: some-remote-server/existing.git - """ - When I run "dfetch add -i some-remote-server/MyLib.git --name my-lib --dst libs/my" with inputs - | Question | Answer | - | Version | master | - | Source path | | - | Ignore paths | | - | Add project to manifest? | y | - | Run update | n | - Then the manifest 'dfetch.yaml' contains entry - """ - - name: my-lib - url: some-remote-server/MyLib.git - branch: master - dst: libs/my - """ diff --git a/features/interactive-add.feature b/features/interactive-add.feature new file mode 100644 index 000000000..4f8f5d180 --- /dev/null +++ b/features/interactive-add.feature @@ -0,0 +1,218 @@ +Feature: Add a project interactively via the CLI + + Pass ``--interactive`` / ``-i`` to ``dfetch add`` to be guided step-by-step + through every manifest field (name, destination, branch/tag/revision, + optional src, optional ignore list). + + Pre-fill individual fields with ``--name``, ``--dst``, ``--version``, + ``--src``, ``--ignore`` — those prompts are then skipped. + + Background: + Given a git repository "MyLib.git" + + Scenario: Interactive add guides through each field + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: ext/existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | my-lib | + | Destination path | libs/my | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + dst: libs/my + """ + + Scenario: Interactive add with tag version + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | my-lib | + | Destination path | my-lib | + | Version | v1 | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + tag: v1 + """ + + Scenario: Interactive add with src subpath + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | my-lib | + | Destination path | my-lib | + | Version | master | + | Source path | docs/api | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + src: docs/api + """ + + Scenario: Interactive add with ignore list + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | my-lib | + | Destination path | my-lib | + | Version | master | + | Source path | | + | Ignore paths | docs, tests | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + ignore: + - docs + - tests + """ + + Scenario: Interactive add triggers immediate fetch when update is accepted + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | MyLib | + | Destination path | MyLib | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | y | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: MyLib + url: some-remote-server/MyLib.git + branch: master + """ + And 'MyLib/README.md' exists + + Scenario: Interactive add with abort does not modify manifest + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | MyLib | + | Destination path | MyLib | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | n | + Then the manifest 'dfetch.yaml' is replaced with + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + + Scenario: Interactive add with empty src (repo root) does not add src field + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | MyLib | + | Destination path | MyLib | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: MyLib + url: some-remote-server/MyLib.git + branch: master + """ + And the manifest 'dfetch.yaml' does not contain 'src:' + + Scenario: Interactive add with pre-filled fields skips those prompts + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git --name my-lib --dst libs/my" with inputs + | Question | Answer | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + dst: libs/my + """