diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index ad17b29..c303e74 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -68,6 +68,7 @@ type Labels = { introduction: string gettingStarted: string playground: string + providerIcons: string language: string syntax: string nodesAndGroups: string @@ -98,6 +99,7 @@ function sidebar(prefix: string, labels: Labels): DefaultTheme.SidebarItem[] { { text: labels.introduction, link: `${prefix}/guide/what-is-stack` }, { text: labels.gettingStarted, link: `${prefix}/guide/getting-started` }, { text: labels.playground, link: `${prefix}/guide/playground` }, + { text: labels.providerIcons, link: `${prefix}/guide/provider-icons` }, ], }, { @@ -155,6 +157,7 @@ const en: Labels = { introduction: "What is Stack?", gettingStarted: "Getting started", playground: "Using the Playground", + providerIcons: "Provider icons", language: "Language", syntax: "Document and syntax", nodesAndGroups: "Nodes and groups", @@ -182,6 +185,7 @@ const ja: Labels = { introduction: "Stackとは", gettingStarted: "はじめる", playground: "Playgroundの使い方", + providerIcons: "Provider icon", language: "言語", syntax: "Documentとsyntax", nodesAndGroups: "Nodeとgroup", @@ -209,6 +213,7 @@ const zh: Labels = { introduction: "什么是 Stack?", gettingStarted: "快速开始", playground: "使用 Playground", + providerIcons: "云服务商图标", language: "语言", syntax: "文档与语法", nodesAndGroups: "节点与分组", @@ -236,6 +241,7 @@ const ko: Labels = { introduction: "Stack이란?", gettingStarted: "시작하기", playground: "Playground 사용법", + providerIcons: "클라우드 제공자 아이콘", language: "언어", syntax: "문서와 문법", nodesAndGroups: "노드와 그룹", diff --git a/docs/guide/provider-icons.md b/docs/guide/provider-icons.md new file mode 100644 index 0000000..353be1f --- /dev/null +++ b/docs/guide/provider-icons.md @@ -0,0 +1,67 @@ +# Provider icons + +Stack supports a provider-neutral core icon catalog without setup. AWS, Google Cloud, and Azure artwork is available through user-imported provider packs. The audited catalog below is public, but Stack does not host or redistribute the vendor SVG files. + +## Available provider catalog + +| Provider | Audited release | Available IDs | Official source and terms | +| ------------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| AWS | `Icon-package_07312026` | `aws:s3`, `aws:sqs`, `aws:lambda`, `aws:ec2`, `aws:rds`, `aws:dynamodb`, `aws:eks` | [AWS Architecture Icons](https://aws.amazon.com/architecture/icons/) and [AWS Trademark Guidelines](https://aws.amazon.com/trademark-guidelines/) | +| Google Cloud | Core product icons from the May 2026 guide | `gcp:cloud-run`, `gcp:cloud-storage`, `gcp:compute-engine`, `gcp:gke`, `gcp:bigquery`, `gcp:cloud-sql` | [Google Cloud Icon Library](https://cloud.google.com/icons) and [Google Brand Resource Center](https://about.google/brand-resource-center/) | +| Azure | `Azure_Public_Service_Icons_V24` | `azure:virtual-machines`, `azure:storage-accounts`, `azure:azure-sql-database`, `azure:aks`, `azure:app-service` | [Azure Architecture Icons](https://learn.microsoft.com/azure/architecture/icons/) and the terms included in the official archive | + +Every imported manifest records the official product name, source release, complete archive hash, terms URL, review date, permitted output categories, and non-endorsement notice. A provider pack adds only artwork; the authored node `kind` still controls its semantic styling and layout. + +## Why artwork is not hosted + +The reviewed vendor guidance permits particular diagram and documentation uses, but does not clearly grant Stack permission to repackage the SVG bytes in its website, npm package, WebAssembly module, or native binary. Static Documentation therefore shows the catalog, provenance, and exact IDs without copying vendor artwork. + +After you load a pack, the Playground shows its actual icons from your selected local files. It creates browser-local image URLs only after Engine validation and does not inject the SVG as page HTML. Review the linked provider terms before using or distributing a generated diagram. + +## Create a local pack + +Download the official archive yourself, then use the public Stack CLI to process that local file: + +```sh +stack icons import aws ~/Downloads/aws-icons.zip \ + --accept-terms \ + -o .stack-icons/aws +``` + +Replace `aws` with `gcp` or `azure` for the other audited profiles. The importer makes no network request or upload. It verifies the complete archive, reads only reviewed paths, removes active content, preserves colors and geometry, and creates `manifest.json`, `NOTICE.md`, and `assets/*.svg`. + +## Use a pack in Playground + +Open **Icons**, choose one pack's `manifest.json` and every declared file in `assets/`, then copy an ID from the local catalog into source: + +```stack +stack 1.0 + +diagram "Storage" { + node files "Amazon S3" { + kind storage + icon "aws:s3" + } +} +``` + +The selected files stay in the current browser tab. The Playground does not upload, fetch, or persist the pack, so reloads require another selection. Use **Notice** beside the SVG download when a rendered diagram embeds provider artwork. + +The Playground accepts an already processed pack, not a raw provider ZIP. Raw-archive verification and safe SVG processing remain in the CLI so browser code does not duplicate that security boundary. + +## Use a pack offline in CLI + +```sh +stack render architecture.stack \ + --provider-pack .stack-icons/aws \ + -o architecture.svg \ + --notice architecture.NOTICE.md +``` + +`--provider-pack` is repeatable. `stack fmt`, `stack check`, `stack render`, and `stack icons import` perform no network request; importing requires only that the official archive already exists locally. The CLI validates bounded pack inputs before rendering and records the exact used icons in the notice sidecar. + +## Offline behavior + +CLI authoring and SVG generation work fully offline after the CLI and any desired provider archives are on the device. Installing the CLI or obtaining a new official archive may require a connection. + +The Web Playground also formats, checks, and renders locally after its JavaScript and WebAssembly have loaded. It has no server rendering dependency and provider files never leave the browser. The current website is not an installed offline app, however, so a cold start without a network connection is not guaranteed. diff --git a/docs/ja/guide/provider-icons.md b/docs/ja/guide/provider-icons.md new file mode 100644 index 0000000..beaa353 --- /dev/null +++ b/docs/ja/guide/provider-icons.md @@ -0,0 +1,67 @@ +# Provider icon + +Stackは設定不要でprovider-neutralなcore icon catalogを利用できます。AWS、Google Cloud、Azureのartworkはuser-imported provider packとして利用します。監査済みcatalogは以下で公開しますが、Stackがvendor SVG fileをhost・再配布することはありません。 + +## 利用可能なprovider catalog + +| Provider | 監査済みrelease | 利用可能なID | 公式sourceとterms | +| ------------ | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| AWS | `Icon-package_07312026` | `aws:s3`, `aws:sqs`, `aws:lambda`, `aws:ec2`, `aws:rds`, `aws:dynamodb`, `aws:eks` | [AWS Architecture Icons](https://aws.amazon.com/architecture/icons/)と[AWS Trademark Guidelines](https://aws.amazon.com/trademark-guidelines/) | +| Google Cloud | 2026年5月guideのcore product icons | `gcp:cloud-run`, `gcp:cloud-storage`, `gcp:compute-engine`, `gcp:gke`, `gcp:bigquery`, `gcp:cloud-sql` | [Google Cloud Icon Library](https://cloud.google.com/icons)と[Google Brand Resource Center](https://about.google/brand-resource-center/) | +| Azure | `Azure_Public_Service_Icons_V24` | `azure:virtual-machines`, `azure:storage-accounts`, `azure:azure-sql-database`, `azure:aks`, `azure:app-service` | [Azure Architecture Icons](https://learn.microsoft.com/azure/architecture/icons/)と公式archive内のterms | + +Import済みmanifestには、公式product name、source release、archive全体のhash、terms URL、review date、許可されたoutput category、non-endorsement noticeが記録されます。Provider packが追加するのはartworkだけで、semantic stylingとlayoutはsourceに書いたnode `kind`が引き続き決めます。 + +## Artworkをhostしない理由 + +確認したvendor guidanceは特定のdiagramやdocumentationでの利用を認めていますが、Stackのwebsite、npm package、WebAssembly module、native binaryへSVG byteを再packageする明確な許諾は確認できません。そのため静的Documentationではvendor artworkを複製せず、catalog、provenance、正確なIDを表示します。 + +Packをloadした後のPlaygroundでは、選択したlocal fileから実際のiconを表示できます。Engine validation後にbrowser-localなimage URLを作り、SVGをpage HTMLとしてinjectしません。生成diagramの利用・配布前に、link先のprovider termsを確認してください。 + +## Local packを作る + +公式archiveを自分でdownloadし、publicなStack CLIでlocal fileを処理します。 + +```sh +stack icons import aws ~/Downloads/aws-icons.zip \ + --accept-terms \ + -o .stack-icons/aws +``` + +他の監査済みprofileでは`aws`を`gcp`または`azure`に置き換えます。Importerはnetwork requestやuploadを行いません。Archive全体を検証し、review済みpathだけを読み、active contentを除去してcolorとgeometryを保持し、`manifest.json`、`NOTICE.md`、`assets/*.svg`を生成します。 + +## Playgroundでpackを使う + +**Icons**を開き、1つのpackにある`manifest.json`と`assets/`内の宣言済みfileをすべて選びます。Local catalogからIDをcopyし、sourceで使います。 + +```stack +stack 1.0 + +diagram "Storage" { + node files "Amazon S3" { + kind storage + icon "aws:s3" + } +} +``` + +選択fileは現在のbrowser tab内だけに留まります。Playgroundはpackをupload、fetch、永続化しないため、reload後は再選択が必要です。Provider artworkを埋め込んだSVGでは、SVG download横の**Notice**も保存してください。 + +Playgroundが受け取るのは処理済みpackで、providerのraw ZIPではありません。Raw archiveの検証と安全なSVG処理はCLIに集約し、browser codeにsecurity boundaryを重複実装しません。 + +## CLIでpackをoffline利用する + +```sh +stack render architecture.stack \ + --provider-pack .stack-icons/aws \ + -o architecture.svg \ + --notice architecture.NOTICE.md +``` + +`--provider-pack`は繰り返し指定できます。`stack fmt`、`stack check`、`stack render`、`stack icons import`はnetwork requestを行いません。Importに必要なのは、公式archiveがlocalに存在することだけです。CLIはbounded pack inputをrender前に検証し、実際に使ったiconをnotice sidecarへ記録します。 + +## Offline behavior + +CLIと必要なprovider archiveがdeviceにあれば、source作成からSVG生成まで完全にofflineで動作します。CLIのinstallや新しい公式archiveの取得にはnetwork接続が必要な場合があります。 + +Web PlaygroundもJavaScriptとWebAssemblyをloadした後は、format、check、renderをbrowser内で実行します。Server-side renderへの依存はなく、provider fileはbrowser外へ出ません。ただし現在のwebsiteはinstall済みoffline appではないため、networkなしのcold startは保証しません。 diff --git a/docs/ja/language/themes-and-icons.md b/docs/ja/language/themes-and-icons.md index cc898c9..e4a3cee 100644 --- a/docs/ja/language/themes-and-icons.md +++ b/docs/ja/language/themes-and-icons.md @@ -89,7 +89,7 @@ Free core catalogは、次のprovider-neutralな明示iconを`default`、`light` | `identity` | Identity and access | Authentication / authorization | | `observability` | Observability system | Metrics / logs / traces | -下のpreviewをlight / darkへ切り替えると、実際のoutputを比較できます。各imageは公開`@stack-sh/engine@0.3.0`がlocalでrenderし、DocumentationにTheme SVG assetを複製していません。Syntaxの行を選ぶとコピーできます。 +下のpreviewをlight / darkへ切り替えると、実際のoutputを比較できます。各imageは公開`@stack-sh/engine@0.4.0`がlocalでrenderし、DocumentationにTheme SVG assetを複製していません。Syntaxの行を選ぶとコピーできます。 diff --git a/docs/ko/guide/provider-icons.md b/docs/ko/guide/provider-icons.md new file mode 100644 index 0000000..6491188 --- /dev/null +++ b/docs/ko/guide/provider-icons.md @@ -0,0 +1,67 @@ +# 클라우드 제공자 아이콘 + +Stack은 설정 없이 제공자 중립적인 core icon catalog를 사용할 수 있습니다. AWS, Google Cloud, Azure 그림은 사용자가 가져온 provider pack으로 사용합니다. 아래 감사 완료 catalog는 공개하지만 Stack은 vendor SVG 파일을 호스팅하거나 재배포하지 않습니다. + +## 사용 가능한 제공자 catalog + +| 제공자 | 감사 완료 release | 사용 가능한 ID | 공식 source 및 terms | +| ------------ | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| AWS | `Icon-package_07312026` | `aws:s3`, `aws:sqs`, `aws:lambda`, `aws:ec2`, `aws:rds`, `aws:dynamodb`, `aws:eks` | [AWS Architecture Icons](https://aws.amazon.com/architecture/icons/) 및 [AWS Trademark Guidelines](https://aws.amazon.com/trademark-guidelines/) | +| Google Cloud | 2026년 5월 가이드의 core product icons | `gcp:cloud-run`, `gcp:cloud-storage`, `gcp:compute-engine`, `gcp:gke`, `gcp:bigquery`, `gcp:cloud-sql` | [Google Cloud Icon Library](https://cloud.google.com/icons) 및 [Google Brand Resource Center](https://about.google/brand-resource-center/) | +| Azure | `Azure_Public_Service_Icons_V24` | `azure:virtual-machines`, `azure:storage-accounts`, `azure:azure-sql-database`, `azure:aks`, `azure:app-service` | [Azure Architecture Icons](https://learn.microsoft.com/azure/architecture/icons/) 및 공식 archive의 terms | + +가져온 manifest는 공식 제품명, source release, 전체 archive hash, terms URL, 검토 날짜, 허용된 output category, 비보증 안내를 기록합니다. Provider pack은 그림만 추가하며 source의 node `kind`가 계속 의미적 스타일과 레이아웃을 결정합니다. + +## 그림을 호스팅하지 않는 이유 + +검토한 vendor 지침은 특정 다이어그램과 문서 용도를 허용하지만 Stack website, npm package, WebAssembly module 또는 native binary에 SVG byte를 다시 패키징할 명확한 허가는 제공하지 않습니다. 따라서 정적 문서는 vendor 그림을 복사하지 않고 catalog, provenance 및 정확한 ID를 표시합니다. + +Pack을 불러온 뒤에는 Playground가 사용자가 선택한 로컬 파일에서 실제 아이콘을 표시합니다. Engine 검증 후에만 브라우저 로컬 이미지 URL을 만들며 SVG를 페이지 HTML로 주입하지 않습니다. 생성한 다이어그램을 사용하거나 배포하기 전에 연결된 제공자 terms를 확인하세요. + +## 로컬 pack 만들기 + +공식 archive를 직접 다운로드한 뒤 공개 Stack CLI로 로컬 파일을 처리합니다. + +```sh +stack icons import aws ~/Downloads/aws-icons.zip \ + --accept-terms \ + -o .stack-icons/aws +``` + +다른 감사 완료 profile은 `aws`를 `gcp` 또는 `azure`로 바꿉니다. Importer는 네트워크 요청이나 업로드를 하지 않습니다. 전체 archive를 검증하고 검토된 경로만 읽으며 active content를 제거하고 색상과 기하를 보존한 뒤 `manifest.json`, `NOTICE.md`, `assets/*.svg`를 생성합니다. + +## Playground에서 pack 사용하기 + +**Icons**를 열고 하나의 pack에 있는 `manifest.json`과 `assets/`의 선언된 파일을 모두 선택한 뒤 로컬 catalog에서 ID를 source로 복사합니다. + +```stack +stack 1.0 + +diagram "Storage" { + node files "Amazon S3" { + kind storage + icon "aws:s3" + } +} +``` + +선택한 파일은 현재 브라우저 탭에만 남습니다. Playground는 pack을 업로드, fetch 또는 영구 저장하지 않으므로 새로고침하면 다시 선택해야 합니다. 생성된 SVG에 provider 그림이 포함되면 SVG 다운로드 옆의 **Notice**도 저장하세요. + +Playground는 이미 처리된 pack을 받으며 provider의 raw ZIP을 직접 받지 않습니다. Raw archive 검증과 안전한 SVG 처리는 CLI에 유지하여 브라우저 코드가 보안 경계를 중복 구현하지 않습니다. + +## CLI에서 pack을 오프라인으로 사용하기 + +```sh +stack render architecture.stack \ + --provider-pack .stack-icons/aws \ + -o architecture.svg \ + --notice architecture.NOTICE.md +``` + +`--provider-pack`은 반복할 수 있습니다. `stack fmt`, `stack check`, `stack render`, `stack icons import`는 네트워크 요청을 하지 않으며 가져오기에는 공식 archive가 이미 로컬에 있으면 됩니다. CLI는 크기가 제한된 pack input을 렌더링 전에 검증하고 실제 사용 아이콘을 notice sidecar에 기록합니다. + +## 오프라인 동작 + +CLI와 필요한 provider archive가 기기에 있으면 source 작성부터 SVG 생성까지 완전히 오프라인으로 동작합니다. CLI 설치나 새 공식 archive 다운로드에는 네트워크가 필요할 수 있습니다. + +Web Playground도 JavaScript와 WebAssembly가 로드된 뒤에는 format, check, render를 브라우저 안에서 실행합니다. 서버 렌더링 의존성이 없고 provider 파일은 브라우저 밖으로 나가지 않습니다. 다만 현재 website는 설치된 offline app이 아니므로 네트워크 없는 cold start를 보장하지 않습니다. diff --git a/docs/ko/language/themes-and-icons.md b/docs/ko/language/themes-and-icons.md index 7b2524e..8e50233 100644 --- a/docs/ko/language/themes-and-icons.md +++ b/docs/ko/language/themes-and-icons.md @@ -89,7 +89,7 @@ diagram "Explicit icon" { | `identity` | Identity and access | 인증 또는 권한 부여 | | `observability` | Observability system | 메트릭, 로그 또는 트레이스 | -아래 미리보기를 라이트와 다크로 전환하여 실제 출력을 비교할 수 있습니다. 각 이미지는 공개된 `@stack-sh/engine@0.3.0`이 로컬에서 렌더링하며 문서에 Theme SVG 에셋을 복제하지 않습니다. 문법 행을 선택하면 복사됩니다. +아래 미리보기를 라이트와 다크로 전환하여 실제 출력을 비교할 수 있습니다. 각 이미지는 공개된 `@stack-sh/engine@0.4.0`이 로컬에서 렌더링하며 문서에 Theme SVG 에셋을 복제하지 않습니다. 문법 행을 선택하면 복사됩니다. diff --git a/docs/language/themes-and-icons.md b/docs/language/themes-and-icons.md index 122a705..2f29a5f 100644 --- a/docs/language/themes-and-icons.md +++ b/docs/language/themes-and-icons.md @@ -89,7 +89,7 @@ The free core catalog includes these provider-neutral explicit icons in `default | `identity` | Identity and access | Authentication or authorization | | `observability` | Observability system | Metrics, logs, or traces | -Switch the preview below between light and dark to inspect the actual output. The published `@stack-sh/engine@0.3.0` renders every image locally; the Documentation does not copy Theme SVG assets. Select a syntax row to copy it. +Switch the preview below between light and dark to inspect the actual output. The published `@stack-sh/engine@0.4.0` renders every image locally; the Documentation does not copy Theme SVG assets. Select a syntax row to copy it. diff --git a/docs/public/llms.txt b/docs/public/llms.txt index ac0865a..da69483 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -9,6 +9,7 @@ These Markdown pages are the public user-facing reference. Examples marked as `s - [What is Stack?](https://stack-diagram.com/docs/guide/what-is-stack.md): Goals, conceptual model, processing pipeline, and deliberate non-goals. - [Getting started](https://stack-diagram.com/docs/guide/getting-started.md): Package installation, first diagram, engine operations, and browser Playground workflow. - [Using the Playground](https://stack-diagram.com/docs/guide/playground.md): Editor, formatter, diagnostics, preview, color mode, and SVG download behavior. +- [Provider icons](https://stack-diagram.com/docs/guide/provider-icons.md): Local AWS, Google Cloud, and Azure pack import, catalog, terms, and offline boundaries. ## Language diff --git a/docs/zh/guide/provider-icons.md b/docs/zh/guide/provider-icons.md new file mode 100644 index 0000000..2892879 --- /dev/null +++ b/docs/zh/guide/provider-icons.md @@ -0,0 +1,67 @@ +# 云服务商图标 + +Stack 无需配置即可使用与服务商无关的核心图标目录。AWS、Google Cloud 和 Azure 图标通过用户导入的 provider pack 使用。下表公开已审核目录,但 Stack 不托管或重新分发服务商 SVG 文件。 + +## 可用的服务商目录 + +| 服务商 | 已审核版本 | 可用 ID | 官方来源与条款 | +| ------------ | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| AWS | `Icon-package_07312026` | `aws:s3`, `aws:sqs`, `aws:lambda`, `aws:ec2`, `aws:rds`, `aws:dynamodb`, `aws:eks` | [AWS Architecture Icons](https://aws.amazon.com/architecture/icons/) 与 [AWS Trademark Guidelines](https://aws.amazon.com/trademark-guidelines/) | +| Google Cloud | 2026 年 5 月指南中的核心产品图标 | `gcp:cloud-run`, `gcp:cloud-storage`, `gcp:compute-engine`, `gcp:gke`, `gcp:bigquery`, `gcp:cloud-sql` | [Google Cloud Icon Library](https://cloud.google.com/icons) 与 [Google Brand Resource Center](https://about.google/brand-resource-center/) | +| Azure | `Azure_Public_Service_Icons_V24` | `azure:virtual-machines`, `azure:storage-accounts`, `azure:azure-sql-database`, `azure:aks`, `azure:app-service` | [Azure Architecture Icons](https://learn.microsoft.com/azure/architecture/icons/) 与官方压缩包内的条款 | + +每个导入的 manifest 都记录官方产品名称、来源版本、完整压缩包 hash、条款 URL、审核日期、允许的输出类别和非背书声明。Provider pack 只增加图形;源代码中的节点 `kind` 仍决定语义样式和布局。 + +## 为什么不托管图形 + +已审核的服务商指南允许特定的架构图与文档用途,但没有明确授权 Stack 将 SVG 字节重新打包到网站、npm 包、WebAssembly 模块或原生二进制中。因此静态文档只展示目录、来源和准确 ID,不复制服务商图形。 + +加载 pack 后,Playground 会从用户选择的本地文件显示真实图标。Engine 验证完成后才创建浏览器本地图片 URL,也不会把 SVG 注入页面 HTML。使用或分发生成图之前,请检查对应的服务商条款。 + +## 创建本地 pack + +自行下载官方压缩包,再使用公开的 Stack CLI 处理本地文件: + +```sh +stack icons import aws ~/Downloads/aws-icons.zip \ + --accept-terms \ + -o .stack-icons/aws +``` + +其他已审核 profile 可将 `aws` 换成 `gcp` 或 `azure`。Importer 不发起网络请求或上传;它验证完整压缩包,只读取审核过的路径,移除活动内容,保留颜色与几何,并生成 `manifest.json`、`NOTICE.md` 和 `assets/*.svg`。 + +## 在 Playground 使用 pack + +打开 **Icons**,选择一个 pack 的 `manifest.json` 和 `assets/` 中所有已声明文件,再从本地目录复制 ID 到源代码: + +```stack +stack 1.0 + +diagram "Storage" { + node files "Amazon S3" { + kind storage + icon "aws:s3" + } +} +``` + +所选文件只保留在当前浏览器标签页。Playground 不上传、获取或持久化 pack,因此刷新后需要重新选择。生成的 SVG 使用服务商图形时,也请下载 SVG 旁边的 **Notice**。 + +Playground 接受已经处理的 pack,而不是服务商原始 ZIP。原始压缩包验证和安全 SVG 处理仍由 CLI 负责,避免浏览器代码重复安全边界。 + +## 在 CLI 中离线使用 pack + +```sh +stack render architecture.stack \ + --provider-pack .stack-icons/aws \ + -o architecture.svg \ + --notice architecture.NOTICE.md +``` + +`--provider-pack` 可以重复指定。`stack fmt`、`stack check`、`stack render` 与 `stack icons import` 都不会发起网络请求;导入只要求官方压缩包已经存在于本地。CLI 在渲染前验证有大小限制的 pack,并把实际使用的图标写入 notice sidecar。 + +## 离线行为 + +设备上已有 CLI 和所需服务商压缩包后,从编写源码到生成 SVG 都可完全离线。安装 CLI 或获取新的官方压缩包可能需要网络。 + +Web Playground 的 JavaScript 与 WebAssembly 加载完成后,format、check 和 render 也都在浏览器本地运行,不依赖服务端渲染,服务商文件不会离开浏览器。但当前网站并不是已安装的离线应用,因此无法保证断网冷启动。 diff --git a/docs/zh/language/themes-and-icons.md b/docs/zh/language/themes-and-icons.md index ccad362..9ca395e 100644 --- a/docs/zh/language/themes-and-icons.md +++ b/docs/zh/language/themes-and-icons.md @@ -89,7 +89,7 @@ diagram "Explicit icon" { | `identity` | Identity and access | 身份验证或授权 | | `observability` | Observability system | 指标、日志或链路追踪 | -切换下方预览的浅色与深色模式即可比较真实输出。每张图片都由已发布的 `@stack-sh/engine@0.3.0` 在本地渲染;文档没有复制 Theme SVG 资源。选择语法行即可复制。 +切换下方预览的浅色与深色模式即可比较真实输出。每张图片都由已发布的 `@stack-sh/engine@0.4.0` 在本地渲染;文档没有复制 Theme SVG 资源。选择语法行即可复制。 diff --git a/package-lock.json b/package-lock.json index 05a7398..c8c6285 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@fontsource/ibm-plex-mono": "^5.3.0", "@shikijs/core": "4.4.3", "@shikijs/engine-javascript": "4.4.3", - "@stack-sh/engine": "0.3.0", + "@stack-sh/engine": "0.4.0", "@stack-sh/language": "0.1.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -4869,9 +4869,9 @@ "license": "CC0-1.0" }, "node_modules/@stack-sh/engine": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@stack-sh/engine/-/engine-0.3.0.tgz", - "integrity": "sha512-+D2vjK2w9dpUIY5Pw0WJ05zs50xdq2uhoisXt0a105IM3BUO5z5znra3cU79fFfDOQqyFLgqlpF8J1Ob1PJIhQ==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@stack-sh/engine/-/engine-0.4.0.tgz", + "integrity": "sha512-AbweDV/5uPFr/rFTwq0uL9Z4OzPM/+j4MjoiVHt36HKFocLRql4EdqvsXebhOHpSVzqx+yiVZUcPvTeAZmuGnA==", "license": "Apache-2.0" }, "node_modules/@stack-sh/language": { diff --git a/package.json b/package.json index 86d03d6..cff4175 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@fontsource/ibm-plex-mono": "^5.3.0", "@shikijs/core": "4.4.3", "@shikijs/engine-javascript": "4.4.3", - "@stack-sh/engine": "0.3.0", + "@stack-sh/engine": "0.4.0", "@stack-sh/language": "0.1.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/scripts/validate-docs-output.mjs b/scripts/validate-docs-output.mjs index 40c3124..2f5568c 100644 --- a/scripts/validate-docs-output.mjs +++ b/scripts/validate-docs-output.mjs @@ -95,8 +95,8 @@ if (!documentationAssets.some((asset) => /^stack_engine_bg\..+\.wasm$/.test(asse const sitemap = await readFile(path.join(outputRoot, "sitemap.xml"), "utf8") const locations = [...sitemap.matchAll(/(.*?)<\/loc>/g)].map((match) => match[1]) -if (locations.length !== 44) - throw new Error(`Expected 44 sitemap locations, found ${locations.length}`) +if (locations.length !== 48) + throw new Error(`Expected 48 sitemap locations, found ${locations.length}`) for (const location of locations) { if (!location.startsWith("https://stack-diagram.com/docs/")) @@ -135,5 +135,5 @@ for (const requiredContent of [ } console.log( - "Validated site metadata, agent discovery files, four locale entry points, and all 44 sitemap locations.", + "Validated site metadata, agent discovery files, four locale entry points, and all 48 sitemap locations.", ) diff --git a/scripts/validate-docs.mjs b/scripts/validate-docs.mjs index f26e3c2..f2f32d0 100644 --- a/scripts/validate-docs.mjs +++ b/scripts/validate-docs.mjs @@ -6,6 +6,7 @@ const pages = [ "guide/what-is-stack.md", "guide/getting-started.md", "guide/playground.md", + "guide/provider-icons.md", "language/syntax.md", "language/nodes-and-groups.md", "language/edges-and-layout.md", @@ -19,8 +20,8 @@ const locales = ["ja", "zh", "ko"] const docsRoot = path.resolve("docs") const packageMetadata = JSON.parse(await readFile(path.resolve("package.json"), "utf8")) -if (packageMetadata.dependencies["@stack-sh/engine"] !== "0.3.0") { - throw new Error("Documentation must use the exact @stack-sh/engine 0.3.0 release") +if (packageMetadata.dependencies["@stack-sh/engine"] !== "0.4.0") { + throw new Error("Documentation must use the exact @stack-sh/engine 0.4.0 release") } const [ diff --git a/src/App.test.tsx b/src/App.test.tsx index cb62ce6..3820f0c 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -4,22 +4,26 @@ import { beforeEach, describe, expect, it, vi } from "vitest" const engine = vi.hoisted(() => ({ check: vi.fn(), + checkWithProviderPacks: vi.fn(), format: vi.fn(), initialize: vi.fn(), render: vi.fn(), + renderWithProviderPacks: vi.fn(), })) vi.mock("@stack-sh/engine", () => ({ check: engine.check, + checkWithProviderPacks: engine.checkWithProviderPacks, default: engine.initialize, format: engine.format, render: engine.render, + renderWithProviderPacks: engine.renderWithProviderPacks, })) import App from "./App" const metadata = { - engineVersion: "0.3.0", + engineVersion: "0.4.0", languageVersion: { major: 1, minor: 0 }, themeCatalogRevision: "sha256:test", themeCatalogVersion: "0.2.0", @@ -45,10 +49,32 @@ describe("Stack Playground", () => { document.documentElement.style.colorScheme = "" engine.initialize.mockResolvedValue({}) engine.check.mockReturnValue({ diagnostics: [], metadata }) + engine.checkWithProviderPacks.mockReturnValue({ diagnostics: [], metadata }) engine.format.mockReturnValue({ diagnostics: [], formattedSource: "stack 1.0\n", metadata }) engine.render.mockReturnValue({ diagnostics: [], metadata, + providerNotices: [], + svg: '', + }) + engine.renderWithProviderPacks.mockReturnValue({ + diagnostics: [], + metadata, + providerNotices: [ + { + archiveSha256: "sha256:test", + attribution: "AWS icons are owned by Amazon Web Services.", + icons: [{ id: "aws:s3", productName: "Amazon S3" }], + nonEndorsement: "AWS does not endorse Stack.", + packRevision: "sha256:pack", + packVersion: "0.1.0", + providerId: "aws", + providerName: "Amazon Web Services", + sourceRelease: "fixture-1", + termsSummary: "Use in architecture diagrams.", + termsUrl: "https://example.com/terms", + }, + ], svg: '', }) }) @@ -174,4 +200,70 @@ describe("Stack Playground", () => { expect(screen.queryByRole("dialog")).not.toBeInTheDocument() expect(screen.getByRole("button", { name: "Expand rendered diagram" })).toHaveFocus() }) + + it("loads a provider pack locally and renders with it", async () => { + const user = userEvent.setup() + render() + await screen.findByAltText("Rendered Stack architecture diagram") + + await user.click(await screen.findByRole("button", { name: "Provider icons" })) + + const manifest = new File( + [ + JSON.stringify({ + packVersion: "0.1.0", + provider: { id: "aws", name: "Amazon Web Services" }, + source: { + pageUrl: "https://example.com/icons", + release: "fixture-1", + reviewAfter: "2026-12-03", + termsUrl: "https://example.com/terms", + }, + icons: [ + { + asset: { path: "assets/s3.svg" }, + id: "aws:s3", + productName: "Amazon Simple Storage Service (Amazon S3)", + }, + ], + }), + ], + "manifest.json", + { type: "application/json" }, + ) + const asset = new File( + [''], + "s3.svg", + { type: "image/svg+xml" }, + ) + + await user.upload(screen.getByLabelText("Provider pack files"), [manifest, asset]) + + expect(await screen.findByText("Amazon Web Services")).toBeInTheDocument() + expect(screen.getByText("aws:s3")).toBeInTheDocument() + expect( + screen.getByAltText("Amazon Simple Storage Service (Amazon S3) icon"), + ).toBeInTheDocument() + expect(engine.checkWithProviderPacks).toHaveBeenCalledOnce() + expect(engine.renderWithProviderPacks).toHaveBeenCalledOnce() + await user.click(screen.getByRole("button", { name: "Close" })) + expect(screen.getByRole("button", { name: "Notice" })).toBeInTheDocument() + }) + + it("shows an import error without sending provider files anywhere", async () => { + const user = userEvent.setup() + render() + await screen.findByAltText("Rendered Stack architecture diagram") + await user.click(await screen.findByRole("button", { name: "Provider icons" })) + + await user.upload( + screen.getByLabelText("Provider pack files"), + new File(["not json"], "manifest.json", { type: "application/json" }), + ) + + expect(await screen.findByRole("alert")).toHaveTextContent( + "Provider manifest is not valid JSON", + ) + expect(engine.renderWithProviderPacks).not.toHaveBeenCalled() + }) }) diff --git a/src/App.tsx b/src/App.tsx index b5a9dee..740a521 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ -import { useCallback, useEffect, useState } from "react" -import type { Diagnostic, EngineMetadata } from "@stack-sh/engine" +import { lazy, Suspense, useCallback, useEffect, useState } from "react" +import type { Diagnostic, EngineMetadata, ProviderNotice } from "@stack-sh/engine" import { ColorModeToggle } from "@/components/color-mode-toggle" import { EditorPane } from "@/components/editor-pane" @@ -7,7 +7,16 @@ import { PreviewPane } from "@/components/preview-pane" import { TooltipProvider } from "@/components/ui/tooltip" import { applyColorMode, initialColorMode, saveColorMode, type ColorMode } from "@/lib/color-mode" import { EXAMPLE_SOURCE } from "@/lib/example" -import { checkStack, formatStack, initializeStackEngine, renderStack } from "@/lib/stack-engine" +import type { LoadedProviderPack } from "@/lib/provider-pack" +import { + checkStack, + formatStack, + initializeStackEngine, + renderStack, + validateProviderPack, +} from "@/lib/stack-engine" + +const ProviderIcons = lazy(() => import("@/components/provider-icons")) function hasErrors(diagnostics: readonly Diagnostic[]) { return diagnostics.some((diagnostic) => diagnostic.severity === "error") @@ -31,6 +40,8 @@ export default function App() { const [diagnostics, setDiagnostics] = useState([]) const [svg, setSvg] = useState(null) const [metadata, setMetadata] = useState(null) + const [providerNotices, setProviderNotices] = useState([]) + const [providerPacks, setProviderPacks] = useState([]) const [status, setStatus] = useState("Loading engine…") const [isReady, setIsReady] = useState(false) @@ -40,14 +51,19 @@ export default function App() { const reportFailure = useCallback((error: unknown) => { setSvg(null) + setProviderNotices([]) setStatus(errorMessage(error)) }, []) - const runRender = useCallback((nextSource: string) => { - const result = renderStack(nextSource) + const runRender = useCallback((nextSource: string, packs: readonly LoadedProviderPack[]) => { + const result = renderStack( + nextSource, + packs.map((pack) => pack.input), + ) setDiagnostics(result.diagnostics) setMetadata(result.metadata) setSvg(result.svg) + setProviderNotices(result.providerNotices) setStatus(resultStatus("Render", result.diagnostics)) }, []) @@ -58,7 +74,7 @@ export default function App() { .then(() => { if (!active) return setIsReady(true) - runRender(EXAMPLE_SOURCE) + runRender(EXAMPLE_SOURCE, []) }) .catch((error: unknown) => { if (active) reportFailure(error) @@ -71,7 +87,7 @@ export default function App() { function handleRender() { try { - runRender(source) + runRender(source, providerPacks) } catch (error) { reportFailure(error) } @@ -85,7 +101,10 @@ export default function App() { function handleCheck() { try { - const result = checkStack(source) + const result = checkStack( + source, + providerPacks.map((pack) => pack.input), + ) setDiagnostics(result.diagnostics) setMetadata(result.metadata) setStatus(resultStatus("Check", result.diagnostics)) @@ -103,8 +122,12 @@ export default function App() { if (result.formattedSource !== null) { setSource(result.formattedSource) - const rendered = renderStack(result.formattedSource) + const rendered = renderStack( + result.formattedSource, + providerPacks.map((pack) => pack.input), + ) setSvg(rendered.svg) + setProviderNotices(rendered.providerNotices) } } catch (error) { reportFailure(error) @@ -116,6 +139,25 @@ export default function App() { saveColorMode(nextColorMode) } + function handleProviderPackImport(pack: LoadedProviderPack) { + validateProviderPack(pack.input) + if ( + !providerPacks.some((item) => item.providerId === pack.providerId) && + providerPacks.length >= 32 + ) { + throw new Error("At most 32 provider packs may be loaded in one tab.") + } + const nextPacks = [...providerPacks.filter((item) => item.providerId !== pack.providerId), pack] + setProviderPacks(nextPacks) + runRender(source, nextPacks) + } + + function handleProviderPackRemove(providerId: string) { + const nextPacks = providerPacks.filter((pack) => pack.providerId !== providerId) + setProviderPacks(nextPacks) + runRender(source, nextPacks) + } + return (
@@ -139,7 +181,7 @@ export default function App() { Playground
-