diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 07fef86..b73b782 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -71,7 +71,7 @@ jobs: node scripts/validate-supply-chain-workflow.mjs node scripts/validate-release-workflow.mjs node --test scripts/release-security.test.mjs scripts/supply-chain-workflow.test.mjs scripts/resolve-release-context.test.mjs scripts/release-workflow.test.mjs - python3 -m unittest scripts/test_normalize_macos_binary.py scripts/test_package_release.py + python3 -m unittest scripts/test_generate_cli_assets.py scripts/test_normalize_macos_binary.py scripts/test_package_release.py - name: Validate pinned example templates env: STACK_SPECIFICATION_DIR: ${{ github.workspace }}/.stack-specification @@ -82,6 +82,24 @@ jobs: run: cargo +stable fmt --check - name: Run tests run: cargo +stable test --locked + - name: Validate generated completion and manual assets + shell: bash + run: | + python3 scripts/generate_cli_assets.py --binary target/debug/stack --check + bash -n distribution/generated/share/bash-completion/completions/stack + source distribution/generated/share/bash-completion/completions/stack + COMP_WORDS=(stack r) + COMP_CWORD=1 + _stack_completion + test "${COMPREPLY[*]}" = render + COMP_WORDS=(stack icons li) + COMP_CWORD=2 + _stack_completion + test "${COMPREPLY[*]}" = list + COMP_WORDS=(stack icons list a) + COMP_CWORD=3 + _stack_completion + test "${COMPREPLY[*]}" = "aws azure" - name: Run canonical formatter suite env: STACK_SPECIFICATION_DIR: ${{ github.workspace }}/.stack-specification @@ -127,6 +145,11 @@ jobs: ./target/release/stack update --help ./target/release/stack lsp --help ./target/release/stack icons --help + ./target/release/stack completions --help + ./target/release/stack completions bash | cmp distribution/generated/share/bash-completion/completions/stack - + ./target/release/stack completions zsh | cmp distribution/generated/share/zsh/site-functions/_stack - + ./target/release/stack completions fish | cmp distribution/generated/share/fish/vendor_completions.d/stack.fish - + ./target/release/stack manpage | cmp distribution/generated/share/man/man1/stack.1 - ./target/release/stack icons list ./target/release/stack icons list aws s3 ./target/release/stack icons import --help @@ -140,6 +163,7 @@ jobs: test -s CONTRIBUTING.md test -s SECURITY.md test -s docs/language-server.md + test -s docs/completions.md test -s docs/self-update.md test -s docs/publication-audit.md test -s docs/provider-icon-import.md @@ -151,9 +175,14 @@ jobs: test -s distribution/distribution-contract.schema.json test -s distribution/install-receipt.schema.json test -s distribution/release-manifest.schema.json + test -s distribution/generated/share/bash-completion/completions/stack + test -s distribution/generated/share/zsh/site-functions/_stack + test -s distribution/generated/share/fish/vendor_completions.d/stack.fish + test -s distribution/generated/share/man/man1/stack.1 test -s Cargo.toml test -s Cargo.lock test -s src/config.rs + test -s src/command_docs.rs test -s src/lsp.rs test -s src/update.rs test -s src/update/install.rs @@ -176,6 +205,8 @@ jobs: test -s scripts/release-security.mjs test -s scripts/release-security.test.mjs test -s scripts/package_release.py + test -s scripts/generate_cli_assets.py + test -s scripts/test_generate_cli_assets.py test -s scripts/normalize_macos_binary.py test -s scripts/test_normalize_macos_binary.py test -s scripts/test_package_release.py diff --git a/Cargo.lock b/Cargo.lock index 0bf9243..7f31445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -385,7 +385,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "stack-cli" -version = "0.3.0" +version = "0.4.0" dependencies = [ "flate2", "roxmltree", diff --git a/Cargo.toml b/Cargo.toml index 705b75b..74631b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stack-cli" -version = "0.3.0" +version = "0.4.0" edition = "2024" rust-version = "1.85" publish = false diff --git a/README.md b/README.md index 2f58788..dc30187 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ stack icons list aws s3 stack icons import gcp --accept-terms stack icons import simple-icons --accept-terms stack render arch.stack -o arch.svg --notice arch.NOTICE.md +stack completions zsh +stack manpage ``` `stack help`, `stack -h`, and `stack --help` print top-level help. Use `stack help ` or ` -h` / ` --help` for command-specific usage and examples; nested icon help is available through `stack help icons `. `stack version`, `stack -v`, `stack -V`, and `stack --version` print the same Cargo package version. Help and version output use standard output and exit with status `0`. Invalid arguments and unknown commands use standard error and status `2`; close command typos include a suggested command and the relevant help invocation. @@ -61,6 +63,8 @@ stack render arch.stack -o arch.svg --notice arch.NOTICE.md `stack update` is implemented for future receipted direct installations, with `--check`, exact-version selection, authenticated release-manifest and archive verification, and rollback-aware atomic replacement. It refuses Homebrew, Aqua, Cargo, and unknown ownership. The published 0.3.0 binary does not contain this command and its manual installation has no receipt, so the self-update channel remains planned. See the [self-update contract](./docs/self-update.md). +`stack completions ` and `stack manpage` generate deterministic shell integration and an offline roff manual from the CLI command metadata. Future release archives carry the exact generated files; Homebrew installs them into its managed completion and manual paths, while direct, Aqua, and future Cargo users can generate them into user-owned locations without modifying shell startup files. See the [completion and manual guide](./docs/completions.md). + `stack icons list [PROVIDER] [QUERY]` searches the asset-free catalog by ID, product name, or category. The catalog currently contains 1,051 IDs: 305 AWS, 45 Google Cloud, 639 Azure, and 62 curated developer and collaboration tool icons. This command reads only metadata embedded in the CLI. `stack icons import --accept-terms` downloads the audited official archive set, verifies every complete SHA-256 before ZIP processing, reads allowlisted SVG entries with fixed size limits, sanitizes active and external content, preserves official colors and geometry, and writes the manifest, notice, and processed SVGs atomically. The default store is `$XDG_CONFIG_HOME/stack/icons`, falling back to `$HOME/.config/stack/icons`. `$XDG_CONFIG_HOME/stack/config.yaml` can set an absolute `default_icons_path`. Use `-o ` to put provider child directories below a project-local root. See [the provider icon guide](./docs/provider-icon-import.md) for configuration, project-local usage, sources, hashes, and rights. @@ -83,9 +87,10 @@ The CLI requires Rust 1.85 or newer. cargo run -- check arch.stack cargo test --locked cargo clippy --all-targets --all-features --locked -- -D warnings +python3 scripts/generate_cli_assets.py --binary target/debug/stack --check ``` -CI validates formatting, unit and process-level integration tests, release metadata and workflow security policies, at least 90% line/region coverage and 95% function coverage, Clippy, documentation, a release build, `--help`, and `--version` on stable Rust. Tests and Clippy also run on Rust 1.85. +CI validates formatting, unit and process-level integration tests, generated completion and manual drift, release metadata and workflow security policies, at least 90% line/region coverage and 95% function coverage, Clippy, documentation, a release build, `--help`, and `--version` on stable Rust. Tests and Clippy also run on Rust 1.85. Canonical formatter behavior is checked against the pinned `stack-sh/specification` fixture revision recorded in `tests/specification-revision`. diff --git a/distribution/distribution-contract.json b/distribution/distribution-contract.json index ed5eaca..95ffb3c 100644 --- a/distribution/distribution-contract.json +++ b/distribution/distribution-contract.json @@ -6,7 +6,7 @@ "sourceCargoPackage": "stack-cli", "publishedCargoPackage": null, "sourceVersionFile": "Cargo.toml", - "currentSourceVersion": "0.3.0", + "currentSourceVersion": "0.4.0", "minimumRustVersion": "1.85" }, "availability": { @@ -30,8 +30,18 @@ "stack", "LICENSE", "NOTICE", - "THIRD_PARTY_LICENSES.md" + "THIRD_PARTY_LICENSES.md", + "share/bash-completion/completions/stack", + "share/zsh/site-functions/_stack", + "share/fish/vendor_completions.d/stack.fish", + "share/man/man1/stack.1" ], + "completionPaths": { + "bash": "share/bash-completion/completions/stack", + "zsh": "share/zsh/site-functions/_stack", + "fish": "share/fish/vendor_completions.d/stack.fish" + }, + "manpagePath": "share/man/man1/stack.1", "releaseManifestNameTemplate": "stack-v{version}-release-manifest.json", "installReceiptSchema": "distribution/install-receipt.schema.json", "checksumNameTemplate": "stack-v{version}-checksums.txt", @@ -114,7 +124,7 @@ "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu" ], - "owns": "canonical immutable binary archives, release manifest, checksums, signature bundle, SBOMs, and provenance", + "owns": "canonical immutable binary archives with generated shell completions and manual page, release manifest, checksums, signature bundle, SBOMs, and provenance", "source": "tagged stack-sh/cli source", "updatePolicy": "stable releases only unless the user requests an exact prerelease" }, @@ -126,7 +136,7 @@ "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu" ], - "owns": "formula metadata, GitHub archive URL and SHA-256 mapping, install, upgrade, and uninstall lifecycle on current Homebrew tier-1 hosts", + "owns": "formula metadata, GitHub archive URL and SHA-256 mapping, completion and manual placement, install, upgrade, and uninstall lifecycle on current Homebrew tier-1 hosts", "source": "github-release", "updatePolicy": "Homebrew owns upgrades; stack self-update must refuse replacement" }, @@ -178,6 +188,8 @@ "all archive contents and metadata names match this contract", "all checksums, Sigstore bundles, SBOMs, and provenance verify", "stack --version, help, init, check, and render pass on each target", + "generated shell completion source matches the archived bash, zsh, and fish files", + "generated manual page matches the archived stack.1 file", "the release manifest records minimumSupportedCliVersion and each verified channel" ], "selfUpdateActivation": [ diff --git a/distribution/distribution-contract.schema.json b/distribution/distribution-contract.schema.json index 692566c..a65fca2 100644 --- a/distribution/distribution-contract.schema.json +++ b/distribution/distribution-contract.schema.json @@ -71,6 +71,8 @@ "archiveNameTemplate", "archiveRootTemplate", "requiredEntries", + "completionPaths", + "manpagePath", "releaseManifestNameTemplate", "installReceiptSchema", "checksumNameTemplate", @@ -86,10 +88,21 @@ "archiveRootTemplate": { "$ref": "#/$defs/template" }, "requiredEntries": { "type": "array", - "minItems": 4, + "minItems": 8, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }, + "completionPaths": { + "type": "object", + "additionalProperties": false, + "required": ["bash", "zsh", "fish"], + "properties": { + "bash": { "const": "share/bash-completion/completions/stack" }, + "zsh": { "const": "share/zsh/site-functions/_stack" }, + "fish": { "const": "share/fish/vendor_completions.d/stack.fish" } + } + }, + "manpagePath": { "const": "share/man/man1/stack.1" }, "releaseManifestNameTemplate": { "$ref": "#/$defs/template" }, "installReceiptSchema": { "const": "distribution/install-receipt.schema.json" }, "checksumNameTemplate": { "$ref": "#/$defs/template" }, diff --git a/distribution/generated/share/bash-completion/completions/stack b/distribution/generated/share/bash-completion/completions/stack new file mode 100644 index 0000000..fb2268b --- /dev/null +++ b/distribution/generated/share/bash-completion/completions/stack @@ -0,0 +1,43 @@ +# Generated by `stack completions bash`; do not edit. +_stack_completion() { +local current previous context words +current="${COMP_WORDS[COMP_CWORD]-}" +previous="${COMP_WORDS[COMP_CWORD-1]-}" +context="${COMP_WORDS[1]-}" +if [[ "$context" == icons && $COMP_CWORD -ge 3 ]]; then +context="icons ${COMP_WORDS[2]-}" +fi +case "$previous" in +--template) words="hello-stack application-and-data groups-and-layout commerce-platform aws-serverless-checkout gcp-data-service azure-event-platform github-delivery-workflow mixed-provider-platform" ;; +--provider-pack|-o|--output|--notice) compopt -o default 2>/dev/null || true; return ;; +--version) COMPREPLY=(); return ;; +*) words="" ;; +esac +if [[ -z "$words" ]]; then +if (( COMP_CWORD == 1 )); then +words="init check fmt render update lsp icons completions manpage help version -h --help -v -V --version" + else + case "$context" in + "init") words="--template -o --output --force -h --help" ;; + "check") words="-h --help" ;; + "fmt") words="- --check -h --help" ;; + "render") words="--provider-pack -o --notice -h --help" ;; + "update") words="--check --version -h --help" ;; + "lsp") words="-h --help" ;; + "icons") words="list import help -h --help" ;; + "icons list") words="aws gcp azure simple-icons -h --help" ;; + "icons import") words="aws gcp azure simple-icons --accept-terms -o -h --help" ;; + "completions") words="bash zsh fish -h --help" ;; + "manpage") words="-h --help" ;; + "help") words="init check fmt render update lsp icons completions manpage help version -h --help" ;; + "version") words="-h --help" ;; + *) words="" ;; +esac +fi +fi +COMPREPLY=( $(compgen -W "$words" -- "$current") ) +if (( ${#COMPREPLY[@]} == 0 )); then +compopt -o default 2>/dev/null || true +fi +} +complete -F _stack_completion stack diff --git a/distribution/generated/share/fish/vendor_completions.d/stack.fish b/distribution/generated/share/fish/vendor_completions.d/stack.fish new file mode 100644 index 0000000..6dac9fc --- /dev/null +++ b/distribution/generated/share/fish/vendor_completions.d/stack.fish @@ -0,0 +1,85 @@ +# Generated by `stack completions fish`; do not edit. +function __stack_needs_command +set -l tokens (commandline -opc) +test (count $tokens) -eq 1 +end +complete -c stack -n __stack_needs_command -a 'init' -d 'Create a Stack file from a versioned starter template' +complete -c stack -n __stack_needs_command -a 'check' -d 'Validate a Stack source file without modifying it' +complete -c stack -n __stack_needs_command -a 'fmt' -d 'Format a file in place or read from standard input' +complete -c stack -n __stack_needs_command -a 'render' -d 'Render standalone SVG to standard output or a file' +complete -c stack -n __stack_needs_command -a 'update' -d 'Check for or install a verified direct-install update' +complete -c stack -n __stack_needs_command -a 'lsp' -d 'Run the Stack language server over standard input and output' +complete -c stack -n __stack_needs_command -a 'icons' -d 'List catalogs and import audited provider icon archives' +complete -c stack -n __stack_needs_command -a 'completions' -d 'Generate bash, zsh, or fish completion source' +complete -c stack -n __stack_needs_command -a 'manpage' -d 'Print the offline Stack CLI manual page' +complete -c stack -n __stack_needs_command -a 'help' -d 'Print this message or the help of a subcommand' +complete -c stack -n __stack_needs_command -a 'version' -d 'Print version information' +complete -c stack -n __stack_needs_command -s 'h' +complete -c stack -n __stack_needs_command -l 'help' +complete -c stack -n __stack_needs_command -s 'v' +complete -c stack -n __stack_needs_command -s 'V' +complete -c stack -n __stack_needs_command -l 'version' +complete -c stack -n '__fish_seen_subcommand_from init' -l 'template' -r +complete -c stack -n '__fish_seen_subcommand_from init' -s 'o' +complete -c stack -n '__fish_seen_subcommand_from init' -l 'output' -r +complete -c stack -n '__fish_seen_subcommand_from init' -l 'force' +complete -c stack -n '__fish_seen_subcommand_from init' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from init' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from check' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from check' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from fmt' -a '-' +complete -c stack -n '__fish_seen_subcommand_from fmt' -l 'check' +complete -c stack -n '__fish_seen_subcommand_from fmt' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from fmt' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from render' -l 'provider-pack' -r +complete -c stack -n '__fish_seen_subcommand_from render' -s 'o' +complete -c stack -n '__fish_seen_subcommand_from render' -l 'notice' -r +complete -c stack -n '__fish_seen_subcommand_from render' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from render' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from update' -l 'check' +complete -c stack -n '__fish_seen_subcommand_from update' -l 'version' -r +complete -c stack -n '__fish_seen_subcommand_from update' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from update' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from lsp' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from lsp' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from icons' -a 'list' +complete -c stack -n '__fish_seen_subcommand_from icons' -a 'import' +complete -c stack -n '__fish_seen_subcommand_from icons' -a 'help' +complete -c stack -n '__fish_seen_subcommand_from icons' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from icons' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from list' -a 'aws' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from list' -a 'gcp' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from list' -a 'azure' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from list' -a 'simple-icons' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from list' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from list' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from import' -a 'aws' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from import' -a 'gcp' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from import' -a 'azure' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from import' -a 'simple-icons' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from import' -l 'accept-terms' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from import' -s 'o' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from import' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from icons; and __fish_seen_subcommand_from import' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from completions' -a 'bash' +complete -c stack -n '__fish_seen_subcommand_from completions' -a 'zsh' +complete -c stack -n '__fish_seen_subcommand_from completions' -a 'fish' +complete -c stack -n '__fish_seen_subcommand_from completions' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from completions' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from manpage' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from manpage' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'init' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'check' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'fmt' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'render' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'update' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'lsp' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'icons' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'completions' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'manpage' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'help' +complete -c stack -n '__fish_seen_subcommand_from help' -a 'version' +complete -c stack -n '__fish_seen_subcommand_from help' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from help' -l 'help' +complete -c stack -n '__fish_seen_subcommand_from version' -s 'h' +complete -c stack -n '__fish_seen_subcommand_from version' -l 'help' diff --git a/distribution/generated/share/man/man1/stack.1 b/distribution/generated/share/man/man1/stack.1 new file mode 100644 index 0000000..62f409c --- /dev/null +++ b/distribution/generated/share/man/man1/stack.1 @@ -0,0 +1,323 @@ +.TH STACK 1 "" "Stack CLI 0.4.0" "Stack CLI Manual" +.SH NAME +stack \- Stack diagram toolchain +.SH SYNOPSIS +.B stack +.RI " [OPTIONS]" +.SH DESCRIPTION +Stack validates, formats, renders, and develops Stack architecture diagrams. +.SH COMMAND REFERENCE +.SS "stack" +.nf +Stack diagram toolchain + +Usage: + stack [OPTIONS] + stack help [COMMAND] + +Commands: + init Create a Stack file from a versioned starter template + check Validate a Stack source file without modifying it + fmt Format a file in place or read from standard input + render Render standalone SVG to standard output or a file + update Check for or install a verified direct\-install update + lsp Run the Stack language server over standard input and output + icons List catalogs and import audited provider icon archives + completions Generate bash, zsh, or fish completion source + manpage Print the offline Stack CLI manual page + help Print this message or the help of a subcommand + version Print version information + +Options: + \-h, \-\-help Print help + \-v, \-V, \-\-version Print version + +Examples: + stack init + stack init \-\-template application\-and\-data \-o architecture.stack + stack check arch.stack + stack fmt \-\-check arch.stack + stack render arch.stack \-o arch.svg + stack update \-\-check + stack lsp + stack icons list aws s3 + stack completions zsh > _stack + stack manpage > stack.1 +.fi +.SS "stack init" +.nf +Create a Stack file from a versioned starter template + +Usage: + stack init [\-\-template