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
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/stack-sh/theme"
rust-version = "1.85"
version = "0.4.0"
version = "0.5.0"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
Expand Down
12 changes: 7 additions & 5 deletions PROVIDER_PACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Status and boundary

This document defines the draft `1.0` manifest for provider icons that a user imports from an official archive. It is separate from the core theme catalog because current AWS, Google Cloud, and Azure terms permit architecture-diagram use but do not provide a clear license for Stack to redistribute the asset bytes in Cargo, npm, Web, WebAssembly, or native binary packages.
This document defines the backward-compatible `1.0` and `1.1` manifests for provider icons that a user imports from official archives. It is separate from the core theme catalog because current vendor terms may permit diagram use without providing a clear license for Stack to redistribute the asset bytes in Cargo, npm, Web, WebAssembly, or native binary packages.

The Stack repository and packages contain only the Apache-2.0 manifest schema, types, and validation logic. They never contain, mirror, proxy, automatically download, or upload provider asset bytes. A host application must ask the user to select a local archive and accept the linked provider terms before creating a pack.

Expand All @@ -13,20 +13,22 @@ Every manifest requires:
- `schemaVersion` and a semantically versioned `packVersion`;
- one lowercase provider `id`, human-readable name, and matching icon namespace;
- `distributionMode: "user-imported"`;
- the official source page, archive URL, archive SHA-256, upstream release, retrieval date, terms URL, terms-review date, copyright statement, and `LicenseRef-*` identifier;
- the primary official source page, archive URL, archive SHA-256, upstream release, retrieval date, terms URL, terms-review date, copyright statement, and `LicenseRef-*` identifier;
- a rights record that fixes package redistribution, automatic download, and server upload to `false`, generated diagram embedding to `true`, and artwork preservation to `true`;
- user-visible attribution, terms summary, and non-endorsement notice;
- one or more provider-prefixed icon records.

The schema records `archiveUrl` as provenance. It is not permission for a runtime consumer to fetch the URL. Import remains an explicit user-selected local operation.

Version `1.1` additionally supports `additionalSources` for a provider catalog split across more than one official archive. Each additional source has a stable pack-local `id` and the same immutable provenance fields as the primary `source`. An icon from an additional archive records that `id` in `asset.sourceId`; omitting `sourceId` means the primary source. Source IDs must be unique and every reference must resolve. Optional `brandSourceUrl` and `brandGuidelinesUrl` fields preserve the rights-owner references for a curated multi-brand archive. Version `1.0` single-source packs remain valid without changes.

## Icon records

An icon ID has the form `<provider>:<slug>`, such as `aws:s3`, `gcp:cloud-run`, or `azure:storage-accounts`. The prefix must equal `provider.id`. Each icon preserves a stable subject, official product name, recommended Stack node kind, upstream archive path, processed local SVG path, integer view box, original and processed SHA-256 hashes, and an ordered transformation log.
An icon ID has the form `<provider>:<slug>`, such as `aws:s3`, `gcp:cloud-run`, or `azure:storage-accounts`. The prefix must equal `provider.id`. Each icon preserves a stable subject, official product name, recommended Stack node kind, source reference, optional brand source and guideline URLs, upstream archive path, processed local SVG path, integer view box, original and processed SHA-256 hashes, and an ordered transformation log.

The importer may perform only visual-preservation transformations needed for safe standalone SVG, such as removing metadata, converting stylesheet declarations to equivalent presentation attributes, removing unused identifiers, namespacing referenced gradient identifiers to prevent collisions, or normalizing XML. Recoloring, cropping, flipping, rotation, distortion, product substitution, or aspect-ratio changes are outside the contract.
The importer may perform only visual-preservation transformations needed for safe standalone SVG, such as removing metadata, converting stylesheet declarations to equivalent presentation attributes, removing unused identifiers, namespacing referenced local resource identifiers to prevent collisions, scaling a finite decimal view box and all coordinates by the same power of ten to obtain the contract's integer view box, or normalizing XML. Recoloring, cropping, flipping, rotation, distortion, product substitution, or aspect-ratio changes are outside the contract.

An empty transformation list requires identical original and processed hashes. A changed hash requires at least one declared transformation. The processed SVG must pass the same script, event-handler, external-reference, executable URL, and viewport safety checks as core assets. Gradients may use only locally declared, `stack-`-namespaced identifiers; stylesheets and external references remain forbidden.
An empty transformation list requires identical original and processed hashes. A changed hash requires at least one declared transformation. The processed SVG must pass the same script, event-handler, external-reference, executable URL, and viewport safety checks as core assets. Gradients, clip paths, and masks may use only locally declared, `stack-`-namespaced identifiers; stylesheets and external references remain forbidden.

## Terms and output

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

The draft core catalog contract is defined by [`CONTRACT.md`](./CONTRACT.md) and [`schemas/catalog.schema.json`](./schemas/catalog.schema.json). The separate local-only provider-pack contract is defined by [`PROVIDER_PACKS.md`](./PROVIDER_PACKS.md) and [`schemas/provider-pack.schema.json`](./schemas/provider-pack.schema.json). The canonical core source is [`catalog/catalog.json`](./catalog/catalog.json); Cargo and npm artifacts are generated from that source with one content revision.

The current `0.4.0` catalog contains the core `default`, `light`, and `dark` themes, repository-authored fallbacks for every Stack 1.0 node kind, 30 provider-neutral explicit icons, and versioned host-independent font metrics. The explicit icon catalog covers clients and compute, networking and delivery, data and events, development workflows, security, general collaboration tools, and AI systems. Its identifiers are `api`, `web`, `mobile`, `desktop`, `server`, `container`, `cluster`, `cloud`, `scheduler`, `webhook`, `identity`, `observability`, `gateway`, `load-balancer`, `dns`, `cdn`, `firewall`, `network`, `event`, `stream`, `search`, `analytics`, `repository`, `pipeline`, `secret`, `document`, `task`, `chat`, `email`, and `ai`. Neither package has been released to a registry yet.
The current `0.5.0` catalog contains the core `default`, `light`, and `dark` themes, repository-authored fallbacks for every Stack 1.0 node kind, 30 provider-neutral explicit icons, and versioned host-independent font metrics. The explicit icon catalog covers clients and compute, networking and delivery, data and events, development workflows, security, general collaboration tools, and AI systems. Its identifiers are `api`, `web`, `mobile`, `desktop`, `server`, `container`, `cluster`, `cloud`, `scheduler`, `webhook`, `identity`, `observability`, `gateway`, `load-balancer`, `dns`, `cdn`, `firewall`, `network`, `event`, `stream`, `search`, `analytics`, `repository`, `pipeline`, `secret`, `document`, `task`, `chat`, `email`, and `ai`.

Provider-specific assets are not bundled. The provider-pack contract lets a CLI or browser validate an archive that the user explicitly selected from the provider's official source, keep it local, preserve the artwork, and carry source and terms notices into diagram output.

Expand Down
2 changes: 1 addition & 1 deletion catalog/catalog.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/stack-sh/theme/main/schemas/catalog.schema.json",
"schemaVersion": "1.0",
"catalogVersion": "0.4.0",
"catalogVersion": "0.5.0",
"reservedThemeIds": [],
"fallbacks": {
"missingThemeId": "default",
Expand Down
36 changes: 34 additions & 2 deletions crates/stack-theme/schema/provider-pack.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@
],
"properties": {
"$schema": { "type": "string" },
"schemaVersion": { "const": "1.0" },
"schemaVersion": { "enum": ["1.0", "1.1"] },
"packVersion": { "$ref": "#/$defs/semanticVersion" },
"provider": { "$ref": "#/$defs/provider" },
"distributionMode": { "const": "user-imported" },
"source": { "$ref": "#/$defs/source" },
"additionalSources": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": { "$ref": "#/$defs/additionalSource" }
},
"rights": { "$ref": "#/$defs/rights" },
"notice": { "$ref": "#/$defs/notice" },
"icons": {
Expand All @@ -41,6 +47,10 @@
"type": "string",
"pattern": "^[a-z][a-z0-9-]{1,31}$"
},
"sourceId": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]{1,31}$"
},
"namespacedIconId": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]{1,31}:[a-z0-9][a-z0-9-]{0,63}$"
Expand Down Expand Up @@ -83,7 +93,11 @@
},
"source": {
"type": "object",
"additionalProperties": false,
"allOf": [{ "$ref": "#/$defs/sourceData" }],
"unevaluatedProperties": false
},
"sourceData": {
"type": "object",
"required": [
"pageUrl",
"archiveUrl",
Expand Down Expand Up @@ -118,6 +132,20 @@
"archiveLicenseIncluded": { "type": "boolean" }
}
},
"additionalSource": {
"type": "object",
"allOf": [
{ "$ref": "#/$defs/sourceData" },
{
"type": "object",
"required": ["id"],
"properties": {
"id": { "$ref": "#/$defs/sourceId" }
}
}
],
"unevaluatedProperties": false
},
"redistribution": {
"type": "object",
"additionalProperties": false,
Expand Down Expand Up @@ -233,6 +261,7 @@
"inline-styles",
"remove-unused-identifiers",
"namespace-identifiers",
"scale-view-box-to-integers",
"normalize-xml"
]
},
Expand All @@ -248,6 +277,7 @@
"transformations"
],
"properties": {
"sourceId": { "$ref": "#/$defs/sourceId" },
"path": { "$ref": "#/$defs/relativePath" },
"originalPath": { "$ref": "#/$defs/relativePath" },
"viewBox": { "$ref": "#/$defs/viewBox" },
Expand Down Expand Up @@ -282,6 +312,8 @@
"minLength": 1,
"maxLength": 160
},
"brandSourceUrl": { "$ref": "#/$defs/httpsUrl" },
"brandGuidelinesUrl": { "$ref": "#/$defs/httpsUrl" },
"recommendedNodeKind": { "$ref": "#/$defs/nodeKind" },
"asset": { "$ref": "#/$defs/iconAsset" }
}
Expand Down
2 changes: 1 addition & 1 deletion crates/stack-theme/src/generated/catalog.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/stack-sh/theme/main/schemas/catalog.schema.json",
"schemaVersion": "1.0",
"catalogVersion": "0.4.0",
"catalogVersion": "0.5.0",
"reservedThemeIds": [],
"fallbacks": {
"missingThemeId": "default",
Expand Down
4 changes: 2 additions & 2 deletions crates/stack-theme/src/generated/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by scripts/generate.mjs. Do not edit.
pub const CATALOG_VERSION: &str = "0.4.0";
pub const CATALOG_REVISION: &str = "sha256:9cb3de8b504acbf22c93cea5fbea66be50f38734dc1dee18b9cab7084082cc1f";
pub const CATALOG_VERSION: &str = "0.5.0";
pub const CATALOG_REVISION: &str = "sha256:3bfd66e1a96628b29b95b7273b54373bcce952f7285aefa506b4255a629eaf53";

pub fn icon_svg(asset_path: &str) -> Option<&'static str> {
match asset_path {
Expand Down
37 changes: 37 additions & 0 deletions crates/stack-theme/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ pub struct ProviderPack {
pub provider: ProviderPackIdentity,
pub distribution_mode: ProviderPackDistributionMode,
pub source: ProviderPackSource,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub additional_sources: Vec<ProviderPackAdditionalSource>,
pub rights: ProviderPackRights,
pub notice: ProviderPackNotice,
pub icons: Vec<ProviderIcon>,
Expand Down Expand Up @@ -335,6 +337,15 @@ pub struct ProviderPackSource {
pub archive_license_included: bool,
}

/// An additional audited archive used by a multi-source provider pack.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProviderPackAdditionalSource {
pub id: String,
#[serde(flatten)]
pub source: ProviderPackSource,
}

/// Provider-specific usage boundary retained with every imported pack.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -406,6 +417,10 @@ pub struct ProviderIcon {
pub id: String,
pub subject: String,
pub product_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub brand_source_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub brand_guidelines_url: Option<String>,
pub recommended_node_kind: ProviderNodeKind,
pub asset: ProviderIconAsset,
}
Expand All @@ -430,6 +445,8 @@ pub enum ProviderNodeKind {
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProviderIconAsset {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_id: Option<String>,
pub path: String,
pub original_path: String,
pub view_box: [i32; 4],
Expand All @@ -446,6 +463,7 @@ pub enum ProviderPackTransformation {
InlineStyles,
RemoveUnusedIdentifiers,
NamespaceIdentifiers,
ScaleViewBoxToIntegers,
NormalizeXml,
}

Expand Down Expand Up @@ -498,4 +516,23 @@ mod tests {
"https://raw.githubusercontent.com/stack-sh/theme/main/schemas/provider-pack.schema.json"
);
}

#[test]
fn multi_source_provider_pack_round_trips_semantically() {
let source = include_str!("../../../tests/fixtures/provider-pack/multi-source.json");
let pack: ProviderPack = serde_json::from_str(source).unwrap();

assert_eq!(pack.schema_version, "1.1");
assert_eq!(pack.additional_sources.len(), 1);
assert_eq!(pack.additional_sources[0].id, "categories");
assert_eq!(pack.icons[0].asset.source_id.as_deref(), Some("categories"));
assert_eq!(
pack.icons[0].brand_guidelines_url.as_deref(),
Some("https://example.com/acme/brand-guidelines")
);
assert_eq!(
serde_json::to_value(&pack).unwrap(),
serde_json::from_str::<serde_json::Value>(source).unwrap()
);
}
}
6 changes: 3 additions & 3 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "stack-theme-workspace",
"private": true,
"version": "0.4.0",
"version": "0.5.0",
"workspaces": [
"packages/theme"
],
Expand Down
4 changes: 2 additions & 2 deletions packages/theme/catalog-metadata.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"catalogVersion": "0.4.0",
"catalogRevision": "sha256:9cb3de8b504acbf22c93cea5fbea66be50f38734dc1dee18b9cab7084082cc1f"
"catalogVersion": "0.5.0",
"catalogRevision": "sha256:3bfd66e1a96628b29b95b7273b54373bcce952f7285aefa506b4255a629eaf53"
}
Loading