# Contributing This source carries only Jeff's own programs, under the single publisher `JCOllie`, so a pull request adding someone else's package will be declined. Fixes to the tooling, the documentation or an existing manifest are welcome at . ## Setting up Everything the repository needs comes from its Nix flake: ```console nix develop ``` ## Layout - `manifests/j/JCOllie///` — one directory per version of a package, holding `JCOllie..yaml`, `JCOllie..installer.yaml` and `JCOllie..locale.en-US.yaml`, exactly as in Microsoft's winget-pkgs. - `tools/wingetsource/` — the Python program that builds the source: merging manifests (`manifest.py`), writing the index (`index.py`, with `normalize.py` and `version.py` ported from winget-cli), packing the MSIX (`msix.py`), checking manifests (`check.py`, against winget-cli's manifest schemas in `schemas/`, copied unchanged), writing a package's first version (`new_package.py`), writing a new version from the newest (`new_version.py`), and reading an MSI's codes for both (`msi.py`). - `tests/` — its tests. `tests/data/winget-cli/` is winget-cli's own name normalization test data, copied unchanged. - `certificate/JCOllie.cer` — the public half of the signing certificate. `tools/make-signing-certificate` is how it was made; the private key is not in the repository. - `tools/setup-submitter` — the wizard that sets up the submitter for a program; see below. - `spike/` — scaffolding for proving the source on a Windows machine before it is published; see below. - `GLOSSARY.md` — the words this project uses and the ones it avoids. Use its terms in issues, commit messages and code. - `docs/adr/` — the decisions that shape the source and why they were made. Read the ones touching an area before changing it; a change that contradicts one should say so and argue for reopening it. ## Building and testing ```console nix develop -c python3 -m unittest discover -s tests -t . nix develop -c python3 -m wingetsource build \ --manifests manifests --out _site \ --certificate certificate/JCOllie.cer \ --certificate-pem /path/to/signing.pem --key /path/to/signing.key ``` The build recreates `_site/`, the whole static site. The package version comes from the commit time of `HEAD`; `--time now` overrides it, which is what a second local build needs for winget to see it as newer. The index writer follows winget-cli's `Microsoft/Schema/1_0` to `1_7` code. When changing it, check the result against Microsoft's own index: download , unzip `Public/index.db` from it, and compare `sqlite3 index.db .schema` with ours. ## Trying it on Windows before it is published `spike/serve` serves `_site/` over HTTPS with caddy, because winget refuses a source that is not `https://` and reads `source.msix` with range requests. It makes a throwaway TLS certificate for this host, which the Windows machine fetches over plain HTTP from the second port and trusts first: ```console nix develop -c spike/serve # https on 9443, the TLS certificate on 9080 ``` `spike/manifests/` holds a pretend `JCOllie.Clipwatch` 0.1.0 that installs the 0.2.0 executable, so there is something to upgrade from. Build with only it, install, then build again with `--manifests manifests --manifests spike/manifests --time now` and upgrade. ## Manifests A version is added by a new directory, never by editing an existing one: a published manifest is what clients have already installed from. Removing a version is a pull request that deletes its directory. A package's first version is written by `new-package`, from what is particular to the program: ```console nix develop -c python3 -m wingetsource new-package --manifests manifests \ --identifier JCOllie.Zfj --version 0.4.0 --repository jeff/zfj --type msi \ --installer x64=https://git.jcollie.dev/jeff/zfj/releases/download/v0.4.0/zfj-0.4.0-x86_64-windows.msi \ --name zfj \ --short-description 'A command-line tool for Forgejo, built on Zig bindings for its whole API.' \ --description-file description.txt \ --license MIT --tag cli --tag forgejo --tag git --tag zig \ --release-notes-url https://git.jcollie.dev/jeff/zfj/releases/tag/v0.4.0 ``` `--type` is `portable`, for an executable winget puts on the `PATH`, or `msi`. `--description` gives the description inline, a newline between paragraphs, and `--description-file` reads it from a file instead. `--moniker` and `--command` default to the program's name in lower case, `--release-date` to today in UTC, and `--json` prints `{"directory": ...}`. The publisher, the URLs into the program's repository (the license's being `LICENSES/.txt` on `main`), the SPDX and schema lines, the `en-US` locale and the manifest version all follow from those. For an MSI, the `ProductCode`, the `UpgradeCode` and whether it installs for all users (`ALLUSERS=1`, written as `Scope: machine`) are read from the downloaded MSI with msitools' `msiinfo`. Anything the program needs beyond that — a comment on how its MSI is built, say — is added by hand in the same pull request. It writes nothing if the package already has a version, if the identifier is not `JCOllie.`, if an installer does not download from git.jcollie.dev, if an MSI lacks either code, if several MSIs disagree on their `UpgradeCode` or on installing for all users, or if what it would write fails `check`'s rules. Only a person runs it, so unlike `new-version`'s, its options may change. Every later version is written from the newest, by winget's version ordering: ```console nix develop -c python3 -m wingetsource new-version --manifests manifests \ --identifier JCOllie.Clipwatch --version 0.3.0 \ --installer x64=https://git.jcollie.dev/jeff/clipwatch/releases/download/v0.3.0/clipwatch-x86_64-windows.exe \ --release-notes-url https://git.jcollie.dev/jeff/clipwatch/releases/tag/v0.3.0 ``` It copies every file of the newest version, comments included, and replaces only what changes with a release: `PackageVersion`, `ReleaseDate` (`--release-date`, today in UTC by default), and each installer's `InstallerUrl` and `InstallerSha256`, matched by `Architecture`, with the hash taken by downloading it. The default locale's `ReleaseNotesUrl` becomes `--release-notes-url`, or is dropped without it, and `ReleaseNotes` is always dropped. It writes nothing if the version already exists, if the package has no version yet, or if `--installer` does not name exactly the architectures the newest version has; changing those is done by hand. With `--json` it prints `{"directory": ..., "copied_from": ...}` rather than the new directory alone. An MSI's `ProductCode` changes with every release, so it is never copied: an installer that has one is given the new MSI's, read from it with msitools' `msiinfo`. `--product-code x64={GUID}` gives the code instead, and the MSI is then not read. It writes nothing if the MSI cannot be read or has no `ProductCode`, or if its `UpgradeCode` is not one the manifest already gives, since winget would then not see it as the same program. It refuses for the same reason when the installer file has a `DisplayVersion`, which it has no way to be given, or one `ProductCode` at the top for every installer rather than one under each. This is what [`jeff/winget-submit`](https://git.jcollie.dev/jeff/winget-submit) runs when a program submits its own release. Every program pins that action, but the action runs whatever `new-version` is on `main`, so its options may be added to and never changed or removed ([ADR 0004](docs/adr/0004-new-version-in-wingetsource.md)). Check a change before opening the pull request: ```console nix develop -c python3 -m wingetsource check --manifests manifests --changed-since origin/main ``` Every version is checked against the 1.12.0 manifest schemas, the way winget validates them, and against the rules here: the identifier is `JCOllie.`, the directory and file names follow from it and the version, and the files agree. The installers of the versions changed since `origin/main` must download from git.jcollie.dev and hash to the SHA-256 their manifest gives, so those are downloaded; without `--changed-since` every version's installers are. CI runs the same check on every pull request, and on `main` once it merges; a branch is checked through its pull request rather than on each push, so that a change is not tested twice. `main` only moves through a merged pull request that has passed validation. Branch protection enforces it for everyone, the repository's owner included: nothing can be pushed to `main` directly, a pull request can merge only once its `test / test (pull_request)` check has passed, and only `jeff` can merge one. Every merge to `main` then rebuilds and publishes the source. ## The submitter A program submits its own releases through [`jeff/winget-submit`](https://git.jcollie.dev/jeff/winget-submit), acting as the submitter `winget-bot`: a restricted Forgejo user with write access to this repository and nothing else, which can push branches and open pull requests here but not merge them. Each program holds its own token for it, in the Actions secret `WINGET_BOT_TOKEN`, limited to this repository and to the `write:repository` and `write:issue` scopes, which are all Forgejo allows a token limited to repositories. `tools/setup-submitter` sets all of that up for one program, and is run again for each new one. It creates `winget-bot` if it does not exist, makes it a collaborator here, restricts merging to `main` to `jeff`, makes the program's token, stores it as the program's secret, and checks what the token can and cannot do: ```console nix develop -c tools/setup-submitter jeff/clipwatch ``` It needs `fj` logged in to git.jcollie.dev as an administrator. The bot's password is random and kept only while the wizard runs; running it again resets it, and replaces the program's token. ## Licensing Every file carries an SPDX header or is covered by `REUSE.toml`, and `nix develop -c reuse lint` must pass.