Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ jobs:
./target/release/stack check --help
./target/release/stack fmt --help
./target/release/stack render --help
./target/release/stack update --help
./target/release/stack lsp --help
./target/release/stack icons --help
./target/release/stack icons list
Expand All @@ -139,6 +140,7 @@ jobs:
test -s CONTRIBUTING.md
test -s SECURITY.md
test -s docs/language-server.md
test -s docs/self-update.md
test -s docs/publication-audit.md
test -s docs/provider-icon-import.md
test -s docs/distribution.md
Expand All @@ -147,11 +149,15 @@ jobs:
test -s aqua/registry.yaml
test -s distribution/distribution-contract.json
test -s distribution/distribution-contract.schema.json
test -s distribution/install-receipt.schema.json
test -s distribution/release-manifest.schema.json
test -s Cargo.toml
test -s Cargo.lock
test -s src/config.rs
test -s src/lsp.rs
test -s src/update.rs
test -s src/update/install.rs
test -s src/update/tests.rs
test -s src/main.rs
test -s src/templates.rs
test -s src/provider.rs
Expand Down Expand Up @@ -190,6 +196,8 @@ jobs:
test -s tests/specification-revision
test -s tests/fixtures/render.stack
test -s tests/snapshots/lsp-help.txt
test -s tests/snapshots/update-help.txt
test -s tests/update.rs

msrv:
name: Minimum supported Rust
Expand Down
28 changes: 26 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
contents: read
outputs:
publish: ${{ steps.release.outputs.publish }}
minimum-supported-cli-version: ${{ steps.release.outputs.minimum-supported-cli-version }}
source-date-epoch: ${{ steps.release.outputs.source-date-epoch }}
source-ref: ${{ steps.release.outputs.source-ref }}
tag: ${{ steps.release.outputs.tag }}
Expand Down Expand Up @@ -341,7 +342,7 @@ jobs:
permissions:
contents: read
id-token: write
attestations: read
attestations: write
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -356,6 +357,7 @@ jobs:
- name: Generate release manifest and checksums
env:
SOURCE_DATE_EPOCH: ${{ needs.context.outputs.source-date-epoch }}
MINIMUM_SUPPORTED_CLI_VERSION: ${{ needs.context.outputs.minimum-supported-cli-version }}
VERIFIED_CHANNELS: ${{ needs.context.outputs.verified-channels }}
VERSION: ${{ needs.context.outputs.version }}
shell: bash
Expand All @@ -365,10 +367,14 @@ jobs:
--directory dist/release \
--version "$VERSION" \
--commit "$GITHUB_SHA" \
--minimum-supported-version "$VERSION" \
--minimum-supported-version "$MINIMUM_SUPPORTED_CLI_VERSION" \
--source-date-epoch "$SOURCE_DATE_EPOCH" \
--builder-workflow stack-sh/cli/.github/workflows/release.yaml \
--verified-channels "$VERIFIED_CHANNELS"
- name: Attest release manifest
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: dist/release/stack-v${{ needs.context.outputs.version }}-release-manifest.json
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
Expand Down Expand Up @@ -399,6 +405,12 @@ jobs:
--certificate-identity "$identity" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
"dist/release/stack-v${VERSION}-checksums.txt"
gh attestation verify "dist/release/stack-v${VERSION}-release-manifest.json" \
--repo stack-sh/cli \
--signer-workflow stack-sh/cli/.github/workflows/release.yaml \
--source-ref "$SOURCE_REF" \
--predicate-type https://slsa.dev/provenance/v1 \
--deny-self-hosted-runners
(
cd dist/release
sha256sum --check "stack-v${VERSION}-checksums.txt"
Expand Down Expand Up @@ -474,6 +486,12 @@ jobs:
--certificate-identity "$identity" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
"dist/release/stack-v${VERSION}-checksums.txt"
gh attestation verify "dist/release/stack-v${VERSION}-release-manifest.json" \
--repo stack-sh/cli \
--signer-workflow stack-sh/cli/.github/workflows/release.yaml \
--source-ref "$SOURCE_REF" \
--predicate-type https://slsa.dev/provenance/v1 \
--deny-self-hosted-runners
for target in \
aarch64-apple-darwin \
x86_64-apple-darwin \
Expand Down Expand Up @@ -552,6 +570,12 @@ jobs:
--certificate-identity "$identity" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
"$downloaded/stack-v${VERSION}-checksums.txt"
gh attestation verify "$downloaded/stack-v${VERSION}-release-manifest.json" \
--repo stack-sh/cli \
--signer-workflow stack-sh/cli/.github/workflows/release.yaml \
--source-ref "$SOURCE_REF" \
--predicate-type https://slsa.dev/provenance/v1 \
--deny-self-hosted-runners
for target in \
aarch64-apple-darwin \
x86_64-apple-darwin \
Expand Down
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ name = "stack"
path = "src/main.rs"

[dependencies]
flate2 = { version = "=1.1.10", default-features = false, features = ["zlib-rs"] }
roxmltree = "=0.21.1"
semver = "=1.0.28"
serde = { version = "=1.0.229", features = ["derive"] }
serde_json = "=1.0.151"
serde_yaml_ng = "=0.10.0"
sha2 = "=0.11.0"
stack-compiler = { git = "https://github.com/stack-sh/compiler.git", rev = "84ab5663a7f7c5b7dc0b5e9e2f04c8894ed02820" }
stack-engine = { git = "https://github.com/stack-sh/engine.git", rev = "9af727aea79233b8389e0ed6fdbae7d3f388dc29" }
stack-theme = { git = "https://github.com/stack-sh/theme.git", rev = "7e208d6a3c90d255799f390a4e8b86248c73caee" }
tar = { version = "=0.4.46", default-features = false }
ureq = { version = "=3.4.0", default-features = false, features = ["rustls"] }
zip = { version = "=6.0.0", default-features = false, features = ["deflate-flate2-zlib-rs"] }

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ stack fmt --check arch.stack
stack fmt -
stack render arch.stack
stack render arch.stack -o arch.svg
stack update --check
stack lsp
stack icons list
stack icons list aws s3
Expand All @@ -58,6 +59,8 @@ stack render arch.stack -o arch.svg --notice arch.NOTICE.md

`stack lsp` runs a native [Language Server Protocol 3.18 adapter](./docs/language-server.md) over standard input and output. It provides incremental document synchronization, versioned diagnostics, completion, hover, hierarchical document symbols, and whole-document formatting for `.stack` files. The adapter negotiates UTF-8, UTF-16, or UTF-32 positions and delegates language semantics and formatting to the pinned compiler and engine rather than reimplementing them. Standard output is reserved for framed JSON-RPC messages.

`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 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 <PROVIDER> --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 <DIRECTORY>` 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.
Expand Down
4 changes: 3 additions & 1 deletion THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ Audit date: 2026-09-05
| `roxmltree` | `0.21.1` | MIT OR Apache-2.0 | <https://github.com/RazrFalcon/roxmltree> | Parses untrusted local SVG into a read-only tree before allowlisted serialization. |
| `sha2`, `digest`, `block-buffer`, `crypto-common`, `hybrid-array`, `const-oid`, `typenum` | `0.11.0`, `0.11.3`, `0.12.1`, `0.2.2`, `0.4.14`, `0.10.2`, `1.20.1` | MIT OR Apache-2.0 | <https://github.com/RustCrypto> | Computes complete archive and per-asset SHA-256 identities. |
| `zip` | `6.0.0` | MIT | <https://github.com/zip-rs/zip2> | Reads audited, allowlisted entries from verified official ZIP archives. |
| `flate2` / `zlib-rs` / `crc32fast` | `1.1.10`, `0.6.7`, `1.5.1` | MIT OR Apache-2.0 / Zlib / MIT OR Apache-2.0 | <https://github.com/rust-lang/flate2-rs>, <https://github.com/trifectatechfoundation/zlib-rs>, <https://github.com/srijs/rust-crc32fast> | Pure Rust DEFLATE decoding and integrity checks for ZIP entries. |
| `flate2` / `zlib-rs` / `crc32fast` | `1.1.10`, `0.6.7`, `1.5.1` | MIT OR Apache-2.0 / Zlib / MIT OR Apache-2.0 | <https://github.com/rust-lang/flate2-rs>, <https://github.com/trifectatechfoundation/zlib-rs>, <https://github.com/srijs/rust-crc32fast> | Pure Rust DEFLATE decoding and integrity checks for provider ZIPs and release tarballs. |
| `tar` / `filetime` | `0.4.46`, `0.2.29` | MIT OR Apache-2.0 | <https://github.com/composefs/tar-rs>, <https://github.com/alexcrichton/filetime> | Reads the authenticated release archive and validates its exact entry metadata before replacement. |
| `semver` | `1.0.28` | MIT OR Apache-2.0 | <https://github.com/dtolnay/semver> | Parses and orders exact stable and release-candidate update versions. |
| `indexmap` / `hashbrown` / `equivalent` | `2.14.1`, `0.17.1`, `1.0.2` | Apache-2.0 OR MIT | <https://github.com/indexmap-rs/indexmap>, <https://github.com/rust-lang/hashbrown>, <https://github.com/indexmap-rs/equivalent> | ZIP archive entry index. |
| `cfg-if` / `cpufeatures` / `libc` | `1.0.4`, `0.3.1`, `0.2.189` | MIT OR Apache-2.0 | <https://github.com/rust-lang/cfg-if>, <https://github.com/RustCrypto/utils>, <https://github.com/rust-lang/libc> | Target selection and SHA-256 acceleration support. |
| `serde` / `serde_core` | `1.0.229` | MIT OR Apache-2.0 | <https://github.com/serde-rs/serde> | Runtime catalog data types through `stack-theme`. |
Expand Down
13 changes: 10 additions & 3 deletions distribution/distribution-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"stableVersionRequirement": "MAJOR.MINOR.PATCH without a prerelease suffix",
"prereleaseVersionRequirement": "MAJOR.MINOR.PATCH-rc.N",
"prereleasePolicy": "GitHub prerelease only; never selected by default by package managers or self-update",
"minimumSupportedVersionSource": "The minimumSupportedCliVersion field in the stable release manifest",
"minimumSupportedVersionSource": "The self-update channel's minimumSupportedCliVersion floor, copied into each release manifest",
"preOneSupportWindow": "latest stable release only",
"stableSupportWindow": "latest two minor lines after 1.0.0"
},
Expand All @@ -33,6 +33,7 @@
"THIRD_PARTY_LICENSES.md"
],
"releaseManifestNameTemplate": "stack-v{version}-release-manifest.json",
"installReceiptSchema": "distribution/install-receipt.schema.json",
"checksumNameTemplate": "stack-v{version}-checksums.txt",
"signatureBundleNameTemplate": "stack-v{version}-checksums.txt.sigstore.json",
"sbomNameTemplate": "stack-v{version}-{target}.spdx.json",
Expand Down Expand Up @@ -164,9 +165,10 @@
"aarch64-unknown-linux-gnu",
"x86_64-unknown-linux-gnu"
],
"owns": "verified atomic replacement for direct GitHub installations carrying a Stack installation receipt",
"owns": "GitHub-attested release-manifest and archive verification, then atomic replacement for direct GitHub installations carrying a Stack installation receipt",
"source": "github-release",
"updatePolicy": "refuse without a direct-install receipt and print the owning package manager command"
"minimumSupportedCliVersion": null,
"updatePolicy": "refuse without a direct-install receipt matching the running binary and print the detected or possible owning package manager command"
}
],
"verification": {
Expand All @@ -178,6 +180,11 @@
"stack --version, help, init, check, and render pass on each target",
"the release manifest records minimumSupportedCliVersion and each verified channel"
],
"selfUpdateActivation": [
"the authenticated release manifest explicitly records the self-update channel",
"the direct installer writes a receipt matching the installed binary path, digest, target, version, and source commit",
"local update server, tampered material, package-manager ownership, permission failure, atomic replacement, and rollback tests pass"
],
"rollback": "Never replace a tag or asset. Mark a broken release as withdrawn, remove it from default update resolution, restore package-manager metadata to the last verified release, and publish a new patch version."
}
}
49 changes: 48 additions & 1 deletion distribution/distribution-contract.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"archiveRootTemplate",
"requiredEntries",
"releaseManifestNameTemplate",
"installReceiptSchema",
"checksumNameTemplate",
"signatureBundleNameTemplate",
"sbomNameTemplate",
Expand All @@ -90,6 +91,7 @@
"items": { "type": "string", "minLength": 1 }
},
"releaseManifestNameTemplate": { "$ref": "#/$defs/template" },
"installReceiptSchema": { "const": "distribution/install-receipt.schema.json" },
"checksumNameTemplate": { "$ref": "#/$defs/template" },
"signatureBundleNameTemplate": { "$ref": "#/$defs/template" },
"sbomNameTemplate": { "$ref": "#/$defs/template" },
Expand Down Expand Up @@ -135,14 +137,20 @@
"verification": {
"type": "object",
"additionalProperties": false,
"required": ["releaseActivation", "rollback"],
"required": ["releaseActivation", "selfUpdateActivation", "rollback"],
"properties": {
"releaseActivation": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 }
},
"selfUpdateActivation": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": { "type": "string", "minLength": 1 }
},
"rollback": { "type": "string", "minLength": 1 }
}
}
Expand Down Expand Up @@ -174,6 +182,42 @@
"type": "object",
"additionalProperties": false,
"required": ["id", "state", "targets", "owns", "source", "updatePolicy"],
"allOf": [
{
"if": {
"properties": { "id": { "const": "self-update" } },
"required": ["id"]
},
"then": { "required": ["minimumSupportedCliVersion"] },
"else": { "not": { "required": ["minimumSupportedCliVersion"] } }
},
{
"if": {
"properties": {
"id": { "const": "self-update" },
"state": { "const": "planned" }
},
"required": ["id", "state"]
},
"then": {
"properties": { "minimumSupportedCliVersion": { "type": "null" } }
}
},
{
"if": {
"properties": {
"id": { "const": "self-update" },
"state": { "const": "available" }
},
"required": ["id", "state"]
},
"then": {
"properties": {
"minimumSupportedCliVersion": { "$ref": "#/$defs/version" }
}
}
}
],
"properties": {
"id": { "enum": ["github-release", "homebrew", "cargo", "aqua", "self-update"] },
"state": { "enum": ["planned", "available"] },
Expand All @@ -184,6 +228,9 @@
},
"owns": { "type": "string", "minLength": 1 },
"source": { "type": "string", "minLength": 1 },
"minimumSupportedCliVersion": {
"anyOf": [{ "$ref": "#/$defs/version" }, { "type": "null" }]
},
"updatePolicy": { "type": "string", "minLength": 1 }
}
}
Expand Down
Loading