Skip to content

itos 2.0.0

Choose a tag to compare

@github-actions github-actions released this 03 Oct 01:38
· 2832 commits to main since this release

itos 2.0.0. The release holds:

  • itos-2.0.0-<os>-<arch>.tar.gz, the Go binary, for linux and darwin on amd64 and arm64,
    and itos-2.0.0-windows-amd64.zip for windows; each holds the binary (itos, or
    itos.exe), LICENSE and README.md at its top level.
  • itos.schema.json, the config's JSON Schema, which an editor checks an itos.yaml against
    (# yaml-language-server: $schema=<its URL> atop the file).
  • checksums.txt, the SHA-256 of every file above, in sha256sum's format:
    sha256sum --ignore-missing -c checksums.txt checks the ones downloaded beside it.

Pin an asset's URL and its SHA-256 (README.md, "Install"), as checksums.txt lists them:

bd88d2b7829316d2051c21bd37696139fbecbf3ec50dafda85a769394a8f61d6  itos-2.0.0-linux-amd64.tar.gz
1de05e327ee52a9a396e3decd4c27488bb4b535beca7d0d80eb3de5602daec49  itos-2.0.0-linux-arm64.tar.gz
6e4f7d3cb154f742db61aa108c9a913d3fcb028e73d41fc70b75a3ccb3cd9d49  itos-2.0.0-darwin-amd64.tar.gz
692c676536017e2513cca77cddf6843f1861fa49a281642d817cedf1afde4d64  itos-2.0.0-darwin-arm64.tar.gz
95013218ff4959049aa5aac4cd40011fe5aac7efd61806ecd4ac6430635b6fb4  itos-2.0.0-windows-amd64.zip
74c7665ebce5c9ed06a07fa73206ee52e2c8ed4f92cb3046e175e017f8c630a9  itos.schema.json

itos 2.0.0

itos is Go only. The release publishes the Go binary, one file with no runtime, for linux and darwin
on amd64 and arm64 and for windows on amd64, beside the config's JSON Schema and checksums.txt;
the TypeScript tarball, itos-<version>.tgz, and with it Node 24 as itos's runtime, are gone. The
binary has run beside the tarball since v1.0.0, held to the same conformance corpus and every
feature on every push, and has judged every commit of itos's own repository since T-060, so a
project that ran the tarball gets the same answers from it.

itos also lints a commit's header itself, so a project can drop Node altogether. Up to v1.1.0 itos
judged the header through a delegate, in practice commitlint with @commitlint/config-conventional,
which is Node. Now commits.header_lint.use: builtin lints it in itos, by the rules
config-conventional holds, each problem under commitlint's rule id and in its words.

Nothing changes for a project that keeps its delegate: commits.header_lint.hook and stdin with
no use run it as before, and use: command says so explicitly. commits.header_lint.alongside is
still refused.

A commit can also say what it asks of a consumer, where the change is made: a footer whose source is
free text, required for the types a project names, with a since of its own so that a history
written before the rule still verifies, and itos commit footers to gather a range's footers into a
release's notes.

The keys added are commits.header_lint.use and commits.footers.<name>.since, and
commits.footers.<name>.source takes text beside ledger and { tests: <kind> }. No key is
removed or renamed and no default changes, so a config v1.1.0 accepts, v2.0.0 accepts and reads the
same. It is a major version because the pin moves: a project on the tarball moves to the binary's
install script (Upgrading, steps 1 and 2).

What is new

  • Go only (T-062, T-061). A release holds itos-<version>-<os>-<arch>.tar.gz for linux and
    darwin on amd64 and arm64 and itos-<version>-windows-amd64.zip, each with the binary (itos,
    or itos.exe), LICENSE and README.md; itos.schema.json; and checksums.txt, the SHA-256 of
    every one, which the release's description prints. There is no itos-2.0.0.tgz, so no
    package.json dependency and no Node: a project pins the binary with an install script
    (Upgrading, step 1). Go developers install it with
    go install github.com/donvargax/itos/v2/cmd/itos@v2.0.0: from v2 the module path ends in its
    major version, as Go requires, so the path without /v2 stays at v1.1.0.

  • The built-in header lint (slice 25, ID-HEADER-01 to ID-HEADER-06). With
    commits.header_lint: { use: builtin }, the commit-msg hook, commit check-message and verify
    lint the header in itos as commitlint 21 does with config-conventional. A message fails on any of
    config-conventional's errors: type-empty, type-enum, type-case, subject-empty,
    subject-case (not sentence-, start-, pascal- or upper-case), subject-full-stop,
    header-max-length (100), header-trim, body-max-line-length and footer-max-line-length
    (100, a line with a URL excepted). Its warnings, body-leading-blank and footer-leading-blank,
    are printed and never reject. type-enum's list is commits.types, or config-conventional's own
    when the config lists none. Each problem is printed as commitlint prints one, errors before
    warnings, then itos's footer rules, which run beside it as they run beside a delegate:

    ✖   subject may not be empty [subject-empty]
    ✖   type may not be empty [type-empty]
    ⚠   body must have leading blank line [body-leading-blank]
    

    Under --json, commit check-message lists each with its level and fix, the footer rules'
    after them, as it lists a delegate's. The messages commitlint leaves alone pass: a merge's, a
    revert's or a reapply's as git writes them, an amend!, fixup! or squash! commit, and a
    version alone. The commit-msg hook reads the message file as commitlint --edit does, git's
    comment lines (core.commentChar, # by default) and everything below its scissors line left
    out; commit check-message and verify read the message as given, as commitlint reads stdin.
    A blank message file passes the hook, as it passes commitlint --edit, and git aborts the commit
    for its empty message; a blank message on stdin is rejected, naming type-empty and
    subject-empty, where commitlint refuses it as no input. itos's own repository runs it: before
    commitlint left, the built-in lint gave commitlint's verdict on all 614 messages it was held to in
    both readings (every commit of itos's history, its test corpus's messages and messages aimed at
    each rule), and commitlint's words but for the known issue below.

  • commits.header_lint.use is accepted, builtin or command. config check refused it as an
    unknown key from v0.3.0, since nothing read it; a value other than those two is refused now
    (config-enum, exit 2).

  • A footer of free text (slice 26, ID-UPGRADE-01 to ID-UPGRADE-03).
    commits.footers.<name>.source: text is a footer with no IDs: each line <Key>: <text> is one
    footer, its text whatever a consumer of the project must do, or the word none (alone, in lower
    case) when a consumer changes nothing. Its rule is only that it is there, for the types
    required_for names, and not empty, for the types validate_for names:

    commits:
      footers:
        Upgrading: { source: text, required_for: [feat, fix, build] }
    ✖   feat commits need a "Upgrading: <what a consumer must do, or none>" footer [upgrading-footer]
    

    An empty Upgrading: is no footer to a type that needs one, and the Upgrading: footer is empty
    where the type validates it. config check refuses strip_prefix, must_be_live and read_at
    on a footer of free text (config-footer-text, exit 2), since they say how IDs are read, and its
    problem for a source it does not know (config-footer-source) names text among the sources.

  • A footer's own since (ID-UPGRADE-04). Any footer may name since, the full SHA of a
    commit, as commits.since does: verify leaves that commit and its ancestors out of the footer's
    required_for, so adding a required footer to a project's rules does not fail every commit
    written before it. The commit-msg hook and commit check-message judge the commit being made and
    always require it. config check and verify check it as they check commits.since: a full SHA
    (config-since), a commit the repository has (config-since-commit, exit 2), the shallow clone
    named as the likely cause.

  • itos commit footers <name> <from> <to> (ID-UPGRADE-05) lists the range's footers of a
    free-text footer, one line each, oldest commit first, the commit's short SHA and the text, leaving
    out those that say none and empty ones; a commit with several gives a line for each. --json
    gives {"schema":1,"footer","range","footers":[{"sha","subject","text"}]}, each with the full
    SHA. The range is from..to as git reads it, every commit up to <to> when from is empty or
    all zeros, and is not narrowed by commits.since. It exits 2 for a footer that is not free text.

  • The help says which header lint runs. itos commit check-message --help and
    itos hook commit-msg --help say the header lint is the built-in one with use: builtin, or the
    delegate; the hook's no longer names commitlint ("commitlint here"), since which lint a project
    runs is its config's to say. itos verify --help names a footer's since, and itos --help lists
    commit footers.

Known issues

v1.1.0's two known issues were the TypeScript's, and left with it: every pattern now matches as
RE2 matches it (Upgrading, step 5). Every known issue of v0.6.0 still stands
(docs/releases/v0.6.0.md, "Known issues"): config check accepts a ledger.group.label that
cannot be a flag (p1-group-label-flag-word) and any word in a range check's except_types
(p1-moves-except-types-checked); the commit-msg hook knows a merge by its message
(p1-moves-merge-by-git); itos work check does not name a registry left at the old default
(p1-registry-old-default-hint); ledger.id has no default (p1-ledger-id-default); wip_tag is
accepted on a command kind (p1-wip-tag-command-kind); the nightly stops at its first failure
(p1-nightly-past-first-failure, p1-ci-run-every-failure-json); a range start a shallow clone
lacks is not named as such (p1-range-start-shallow-clone); ci plan --data-at names a missing
ledger folder by a scratch path (p1-data-at-ledger-folder); a check two named tasks share runs
once per task (p1-ci-checks-once, p1-hook-checks-once); a ledger of <folder>/{group}.yaml
matches the registry (p1-ledger-matches-registry); the commit-time data check does not follow
every file (p1-people-checked-at-commit, p1-smoke-rule-at-commit, p1-verify-data-per-commit);
a pre-commit hook's early exit is hand-written (p1-pre-commit-asks-what-skips); and shell: [] is
accepted (p1-shell-not-empty). Three are new:

  • subject-case may name more cases than commitlint for a subject with some accented letters
    (p3-header-case-decomposition). commitlint finds start-case and pascal-case after decomposing
    every precomposed letter (NFD); Go's standard library has no such tables, and the built-in lint
    decomposes only the letters of Latin-1 and Latin Extended-A. For a subject such as Ǎb Cd it
    says subject must not be sentence-case, start-case where commitlint says
    subject must not be sentence-case. The verdict is the same: every case subject-case refuses
    is sentence-case too.

  • config check accepts hook and stdin beside use: builtin
    (p3-header-lint-unread-keys), though nothing reads them then. Take them out when you switch
    (Upgrading, step 3).

  • A free-text footer is one line (p3-text-footer-lines). A footer line may not pass 100
    characters (footer-max-line-length), so a longer step is written as several Upgrading: lines,
    and itos commit footers lists each as a footer of its own; an indented line below one is not
    read as part of it.

Upgrading

Do these in order, in one session, then commit as your project's rules ask (the install script,
the shim and .gitignore are a build commit). Steps 1 to 4 move a project from the tarball to the
binary and, if it wants, off Node; a project that already runs the binary, from v1.0.0's Upgrading,
step 6, does step 1's version and hashes only, then steps 3 and 4 if it still has commitlint. A
project on v1.1.0 can skip step 5; one on v1.0.x or v0.6.0 does it. itos.yaml needs no edit to
keep working: no key is removed or renamed and no default changes.

  1. Pin the binary. Commit this install script as tools/bin/install-itos (executable). It
    pins the version and each platform's SHA-256, so a replaced release cannot pass, installs into
    an ignored .tools/bin/, and does nothing when it already installed that version and hash
    there (.tools/bin/itos.pin records them; it never runs the binary to ask, since itos version
    loads itos.yaml and stops on a config it refuses):

    #!/bin/sh
    # tools/bin/install-itos: the pinned itos, into .tools/bin/
    set -eu
    version=2.0.0
    cd "$(git rev-parse --show-toplevel)"
    case "$(uname -s)-$(uname -m)" in
    Linux-x86_64) platform=linux-amd64 sum=<its line's hash> ;;
    Linux-aarch64 | Linux-arm64) platform=linux-arm64 sum=<its line's hash> ;;
    Darwin-x86_64) platform=darwin-amd64 sum=<its line's hash> ;;
    Darwin-arm64) platform=darwin-arm64 sum=<its line's hash> ;;
    MINGW*-x86_64 | MSYS*-x86_64) platform=windows-amd64 sum=<its line's hash> ;;
    *) echo "install-itos: no itos archive for $(uname -s)-$(uname -m)" >&2 && exit 1 ;;
    esac
    case "$platform" in
    windows-*) archive="itos-$version-$platform.zip" bin=.tools/bin/itos.exe ;;
    *) archive="itos-$version-$platform.tar.gz" bin=.tools/bin/itos ;;
    esac
    # What was installed, so a call with nothing to do runs nothing.
    pin=".tools/bin/itos.pin"
    if [ -x "$bin" ] && [ "$(cat "$pin" 2>/dev/null)" = "$version $sum" ]; then exit 0; fi
    tmp=$(mktemp -d)
    trap 'rm -rf "$tmp"' EXIT
    curl -fsSL -o "$tmp/$archive" "https://github.com/donvargax/itos/releases/download/v$version/$archive"
    if command -v sha256sum >/dev/null; then check="sha256sum -c -"; else check="shasum -a 256 -c -"; fi
    (cd "$tmp" && echo "$sum  $archive" | $check)
    mkdir -p .tools/bin
    case "$archive" in
    *.zip) unzip -o -q "$tmp/$archive" itos.exe -d .tools/bin ;;
    *) tar -xzf "$tmp/$archive" -C .tools/bin itos ;;
    esac
    echo "$version $sum" > "$pin"
    echo "install-itos: itos $version in $bin"

    Fill each <its line's hash> with that archive's hash from this release's checksums.txt, one
    <hash> <archive> line per asset: the release's description prints it above these notes
    (https://github.com/donvargax/itos/releases/tag/v2.0.0), and
    gh release download v2.0.0 -R donvargax/itos -p checksums.txt downloads it. Add .tools/ to
    .gitignore. Then make the script hooks.bin names (tools/bin/itos by default), which your
    hooks and CI call, run the binary, installing it first when it is missing or another version:

    #!/bin/sh
    # tools/bin/itos: the pinned itos binary, installed on first use
    root=$(git rev-parse --show-toplevel) || exit 3
    "$root/tools/bin/install-itos" >&2 || exit 3
    case "$(uname -s)" in MINGW* | MSYS*) exe=.exe ;; *) exe= ;; esac
    exec "$root/.tools/bin/itos$exe" "$@"

    A fresh CI checkout then installs it on the first call, so CI needs no step of its own; the
    runner needs curl, tar (or unzip), and sha256sum or shasum. Go developers can instead
    go install github.com/donvargax/itos/v2/cmd/itos@v2.0.0: note the /v2, without which Go
    installs v1.1.0. A later version is the same build commit: version= and each hash.

  2. Remove the tarball. npm uninstall itos (or pnpm remove itos, yarn remove itos) takes
    its URL out of package.json and the lockfile. Anything that called the package's bin
    (npx itos, node_modules/.bin/itos, a package.json script running itos) calls
    tools/bin/itos instead. Node 24 is no longer needed for itos.

  3. To drop commitlint, switch to the built-in header lint. In itos.yaml, replace
    commits.header_lint's hook and stdin with use: builtin:

    commits:
      header_lint: { use: builtin }

    Take hook and stdin out: config check accepts them beside use: builtin, but nothing
    reads them (Known issues). Then take commitlint out: its config (commitlint.config.*, or the
    commitlint key of package.json), @commitlint/cli, @commitlint/config-conventional and
    any @commitlint/* package beside them. Check it:
    printf 'update things\n' | tools/bin/itos commit check-message - exits 1 naming
    [type-empty], and a sound message of yours exits 0.

    The built-in lint holds config-conventional's rules and nothing else. If your commitlint config
    changes them (a rule of its own, a plugin, another level or limit, its own type-enum), keep the
    delegate with use: command instead, or accept config-conventional's rules. The built-in lint's
    type list is commits.types, or config-conventional's own when the config lists none, so make
    it the list your commitlint config allowed. A script that reads the lint's report finds the same
    lines, ✖ <words> [<rule>], but on stderr where the delegate's went to stdout, without
    commitlint's ⧗ --- input --- heading and its found N problems summary; under --json
    nothing changes. To keep your delegate, change nothing: hook and stdin with no use run it
    as before, and use: command beside them says so.

  4. Drop Node, if nothing else needs it. With the tarball and commitlint gone, Node may be left
    for nothing: then remove CI's Node setup and its install step, .nvmrc or engines, and
    package.json and its lockfile if they hold nothing else. If your git hooks are installed by a
    Node tool (husky, Vite+), keep it, or move them to one that is not:
    tools/bin/itos hooks install --manager git writes plain git hooks, which are not committed,
    so each clone runs it once; lefthook, pre-commit and prek are not Node either (set
    hooks.manager to the one you use).

  5. If you come from v1.0.x or v0.6.0 on the tarball, check your patterns as RE2. The binary
    compiles and matches the config's regular expressions as RE2: ledger.id,
    ledger.group.pattern, tests.<kind>.id, ci.cost.static and ci.covers' matches. Run
    tools/bin/itos config check. A pattern RE2 cannot compile is refused (exit 2, the rule
    config-regexp) with <key> is not an RE2 regular expression: <pattern>, and every command that
    loads the config stops on it: a lookaround ((?=…), (?!…), (?<=…), (?<!…)), a
    backreference (\1, \k<name>), a letter escape RE2 has no reading for (\c, \e, \u, and
    any letter but \A \b \B \d \D \p \P \Q \s \S \w \W \x \z \a \f
    \n \r \t \v, or inside a class \b, \A, \B, \Q and \z too), the classes [] and
    [^], a Unicode or POSIX class name RE2 lacks (\p{Foo}, [[:foo:]]), a range bounded by a
    class ([a-\d]), (?i-:…), and a repeat count above 1000. Rewrite it without them: a lookahead
    that only guards what follows is usually the same pattern with that part matched
    (^vp check(?= ) as ^vp check ), a backreference spelled out for the cases it meets, \e as
    the e JavaScript read it as, [\b] as \x08, and a long count split (\d{1,1001} as
    \d{1,1000}\d?). A pattern both compile can still match differently: \s matches only ASCII
    whitespace in RE2, and . matches \r. A script that read the old words ("is not a regular
    expression", "compiles as a JavaScript regular expression") matches the rule instead.

  6. Check itos.yaml's requires. requires is a list of comparators that must all hold, and
    2.0.0 is past <2, <1.2 or =1.1.0, so itos version --check would exit 1 with
    itos 2.0.0 does not satisfy >=1.1.0 <2 (the config's requires); widen such a cap. A lower bound
    alone (>=0.6.0) needs no change. If you use use: builtin, a footer's since or
    source: text, raise it to >=2.0.0: an older itos refuses those as unknown keys, and
    version --check then names the version instead.

  7. Point the schema line at v2.0.0's. If itos.yaml's first line names a release's
    itos.schema.json, make it
    # yaml-language-server: $schema=https://github.com/donvargax/itos/releases/download/v2.0.0/itos.schema.json,
    which knows the keys this release adds.

  8. To have each commit say what it asks of a consumer, add a footer of free text. Optional, and
    new: name it, the types that need it, and since, the full SHA of the commit you add it after
    (git rev-parse HEAD), so that verify does not require it of the commits written before:

    commits:
      footers:
        Upgrading: { source: text, required_for: [feat, fix], since: "<full SHA>" }

    The commit-msg hook then rejects a feat or fix without an Upgrading: line, so each says
    what its consumers must do, or Upgrading: none; the commit adding the rule needs one only if
    its own type is listed. itos commit footers Upgrading <last release> HEAD lists what your next
    release's notes must tell your consumers.

  9. Check: tools/bin/itos version prints itos 2.0.0, tools/bin/itos config check passes,
    tools/bin/itos version --check exits 0, and the commit of these changes goes through your hooks
    on the binary.

  10. Report a problem itos causes. To report a problem itos caused in this repository, open an
    issue on donvargax/itos with the exact command, its exact output and exit code, what
    itos version prints, the part of itos.yaml that matters, and what you expected, each under
    its own heading (What was run / Its output / itos version / The config / What was expected):

    gh issue create -R donvargax/itos --label consumer-report --title "consumer report: <one line>" --body-file <report.md>

    (or, in a browser, https://github.com/donvargax/itos/issues/new?template=consumer-report.yml).
    gh issue create --template does not fill in the form when nothing answers it, so a session
    writes the body itself, one ## heading per part, and passes the label. One problem per
    issue; leave out anything private, since the repository is public.