Skip to content

Commit 187b4c3

Browse files
authored
Port website dev docs generated content scripts from JS to Rust to avoid intermediate parsing (#3909)
* Remove the crate dependency graph website generation intermediate generation step * Remove the message system tree website generation intermediate generation step * Code cleanup * Remove cache system * Fix Windows comment URL error * Fix incorrect artifact download URLs * Add Flatpak to comment * Make Flatpak use debug/release mode choice instead of always release mode
1 parent d9214c7 commit 187b4c3

File tree

12 files changed

+344
-267
lines changed

12 files changed

+344
-267
lines changed

.github/workflows/build.yml

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ jobs:
145145
gh api \
146146
-X POST \
147147
-H "Accept: application/vnd.github+json" \
148-
/repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \
148+
repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \
149149
-f body="$COMMENT_BODY"
150150
else
151151
# Comment on the PR (use provided PR number from !build, or look it up by branch name)
@@ -172,41 +172,16 @@ jobs:
172172
name: graphite-web-bundle
173173
path: frontend/dist
174174

175-
- name: 📃 Generate code documentation info for website
175+
- name: 📃 Trigger website rebuild if auto-generated code docs are stale
176176
if: github.event_name == 'push'
177-
run: |
178-
mkdir -p website/generated-new
179-
cargo run -p crate-hierarchy-viz -- website/generated-new/crate_hierarchy.dot
180-
cargo run -p editor-message-tree -- website/generated-new/hierarchical_message_system_tree.txt
181-
182-
- name: 💿 Obtain cache of auto-generated code docs artifacts, to check if they've changed
183-
if: github.event_name == 'push'
184-
id: cache-website-code-docs
185-
uses: actions/cache/restore@v5
186-
with:
187-
path: website/generated
188-
key: website-code-docs
189-
190-
- name: 🔍 Check if auto-generated code docs artifacts changed
191-
if: github.event_name == 'push'
192-
id: website-code-docs-changed
193-
run: |
194-
diff --brief --recursive website/generated-new website/generated || echo "changed=true" >> $GITHUB_OUTPUT
195-
rm -rf website/generated
196-
mv website/generated-new website/generated
197-
198-
- name: 💾 Save cache of auto-generated code docs artifacts
199-
if: github.event_name == 'push' && steps.website-code-docs-changed.outputs.changed == 'true'
200-
uses: actions/cache/save@v5
201-
with:
202-
path: website/generated
203-
key: ${{ steps.cache-website-code-docs.outputs.cache-primary-key }}
204-
205-
- name: ♻️ Trigger website rebuild if the auto-generated code docs artifacts have changed
206-
if: github.event_name == 'push' && steps.website-code-docs-changed.outputs.changed == 'true'
207177
env:
208178
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
209-
run: gh workflow run website.yml --ref master
179+
run: |
180+
cargo run -p editor-message-tree -- website/generated
181+
TREE=volunteer/guide/codebase-overview/hierarchical-message-system-tree
182+
curl -sf "https://graphite.art/$TREE.txt" -o "website/static/$TREE.live.txt" \
183+
&& diff -q "website/static/$TREE.txt" "website/static/$TREE.live.txt" > /dev/null \
184+
|| gh workflow run website.yml --ref master
210185
211186
windows:
212187
if: github.event_name == 'push' || inputs.windows
@@ -302,16 +277,18 @@ jobs:
302277
env:
303278
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
304279
run: |
305-
ARTIFACT_URL=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-windows-bundle") | .archive_download_url')
280+
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-windows-bundle") | .id')
281+
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID"
306282
PR_NUMBER="${{ inputs.pr_number }}"
307283
if [ -z "$PR_NUMBER" ]; then
308284
BRANCH=$(git rev-parse --abbrev-ref HEAD)
309285
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
310286
fi
311-
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_URL" ]; then
312-
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "| 📦 **Windows Build Complete for** $(git rev-parse HEAD) |
313-
|-|
314-
| [Download artifact]($ARTIFACT_URL) |"
287+
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_ID" ]; then
288+
BODY="| 📦 **Windows Build Complete for** $(git rev-parse HEAD) |"$'\n'
289+
BODY+="|-|"$'\n'
290+
BODY+="| [Download binary]($ARTIFACT_URL) |"
291+
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "$BODY"
315292
fi
316293
317294
- name: 🔑 Azure login
@@ -488,16 +465,18 @@ jobs:
488465
env:
489466
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
490467
run: |
491-
ARTIFACT_URL=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-mac-bundle") | .archive_download_url')
468+
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-mac-bundle") | .id')
469+
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID"
492470
PR_NUMBER="${{ inputs.pr_number }}"
493471
if [ -z "$PR_NUMBER" ]; then
494472
BRANCH=$(git rev-parse --abbrev-ref HEAD)
495473
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
496474
fi
497-
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_URL" ]; then
498-
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "| 📦 **Mac Build Complete for** $(git rev-parse HEAD) |
499-
|-|
500-
| [Download artifact]($ARTIFACT_URL) |"
475+
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_ID" ]; then
476+
BODY="| 📦 **Mac Build Complete for** $(git rev-parse HEAD) |"$'\n'
477+
BODY+="|-|"$'\n'
478+
BODY+="| [Download binary]($ARTIFACT_URL) |"
479+
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "$BODY"
501480
fi
502481
503482
- name: 🔏 Sign and notarize (preparation)
@@ -616,20 +595,24 @@ jobs:
616595
compression-level: 0
617596

618597
- name: 💬 Comment artifact link on PR
598+
id: linux-comment
619599
if: github.event_name != 'push'
620600
env:
621601
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
622602
run: |
623-
ARTIFACT_URL=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-linux-bundle") | .archive_download_url')
603+
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-linux-bundle") | .id')
604+
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID"
624605
PR_NUMBER="${{ inputs.pr_number }}"
625606
if [ -z "$PR_NUMBER" ]; then
626607
BRANCH=$(git rev-parse --abbrev-ref HEAD)
627608
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
628609
fi
629-
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_URL" ]; then
630-
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "| 📦 **Linux Build Complete for** $(git rev-parse HEAD) |
631-
|-|
632-
| [Download artifact]($ARTIFACT_URL) |"
610+
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_ID" ]; then
611+
BODY="| 📦 **Linux Build Complete for** $(git rev-parse HEAD) |"$'\n'
612+
BODY+="|-|"$'\n'
613+
BODY+="| [Download binary]($ARTIFACT_URL) |"
614+
COMMENT_ID=$(gh api repos/${{ github.repository }}/issues/$PR_NUMBER/comments -f body="$BODY" --jq '.id')
615+
echo "comment_id=$COMMENT_ID" >> "$GITHUB_OUTPUT"
633616
fi
634617
635618
- name: 🔧 Install Flatpak tooling
@@ -640,7 +623,7 @@ jobs:
640623
641624
- name: 🏗 Build Flatpak
642625
run: |
643-
nix build .#graphite-flatpak-manifest
626+
nix build .#graphite${{ inputs.debug && '-dev' || '' }}-flatpak-manifest
644627
645628
rm -rf .flatpak
646629
mkdir -p .flatpak
@@ -660,3 +643,18 @@ jobs:
660643
name: graphite-flatpak
661644
path: .flatpak/Graphite.flatpak
662645
compression-level: 0
646+
647+
- name: 💬 Update PR comment with Flatpak artifact link
648+
if: github.event_name != 'push' && steps.linux-comment.outputs.comment_id
649+
env:
650+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
651+
run: |
652+
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-flatpak") | .id')
653+
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID"
654+
COMMENT_ID="${{ steps.linux-comment.outputs.comment_id }}"
655+
if [ -n "$ARTIFACT_ID" ]; then
656+
EXISTING_BODY=$(gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID --jq '.body')
657+
BODY="$EXISTING_BODY"$'\n'
658+
BODY+="| [Download Flatpak]($ARTIFACT_URL) |"
659+
gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID -X PATCH -f body="$BODY"
660+
fi

.github/workflows/website.yml

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,30 +49,16 @@ jobs:
4949
# Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)
5050
git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_INCLUSION=""
5151
52-
- name: 💿 Obtain cache of auto-generated code docs artifacts
53-
id: cache-website-code-docs
54-
uses: actions/cache/restore@v5
55-
with:
56-
path: website/generated
57-
key: website-code-docs
58-
59-
- name: 📁 Fallback in case auto-generated code docs artifacts weren't cached
60-
if: steps.cache-website-code-docs.outputs.cache-hit != 'true'
52+
- name: 🦀 Produce auto-generated code docs data
6153
run: |
62-
echo "🦀 Initial system version of Rust:"
63-
rustc --version
6454
rustup update stable
65-
echo "🦀 Latest updated version of Rust:"
66-
rustc --version
67-
cargo run -p crate-hierarchy-viz -- website/generated/crate_hierarchy.dot
68-
cargo run -p editor-message-tree -- website/generated/hierarchical_message_system_tree.txt
55+
cargo run -p crate-hierarchy-viz -- website/generated
56+
cargo run -p editor-message-tree -- website/generated
6957
70-
- name: 🔧 Build auto-generated code docs artifacts into HTML/SVG
58+
- name: 🔧 Install website npm dependencies
7159
run: |
7260
cd website
7361
npm ci
74-
npm run generate-editor-structure
75-
npm run generate-crate-hierarchy
7662
7763
- name: 📃 Generate node catalog documentation
7864
run: cargo run -p node-docs -- website/content/learn/node-catalog

.nix/default.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ in
6161
graphite-branding = lib.call ./pkgs/graphite-branding.nix;
6262
graphite-bundle = (lib.call ./pkgs/graphite-bundle.nix) { };
6363
graphite-dev-bundle = (lib.call ./pkgs/graphite-bundle.nix) { graphite = graphite-dev; };
64-
graphite-flatpak-manifest = lib.call ./pkgs/graphite-flatpak-manifest.nix;
64+
graphite-flatpak-manifest = (lib.call ./pkgs/graphite-flatpak-manifest.nix) { };
65+
graphite-dev-flatpak-manifest = (lib.call ./pkgs/graphite-flatpak-manifest.nix) { graphite-bundle = graphite-dev-bundle; };
6566

6667
# TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix;
6768

.nix/pkgs/graphite-flatpak-manifest.nix

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
system,
55
...
66
}:
7+
{
8+
graphite-bundle ? self.packages.${system}.graphite-bundle,
9+
}:
710

811
(pkgs.formats.json { }).generate "art.graphite.Graphite.json" {
912
app-id = "art.graphite.Graphite";
@@ -30,7 +33,7 @@
3033
sources = [
3134
{
3235
type = "archive";
33-
path = self.packages.${system}.graphite-bundle.tar;
36+
path = graphite-bundle.tar;
3437
strip-components = 0;
3538
}
3639
];

tools/crate-hierarchy-viz/src/main.rs

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use anyhow::{Context, Result};
22
use serde::Deserialize;
33
use std::collections::{HashMap, HashSet};
44
use std::fs;
5+
use std::io::Write;
56
use std::path::PathBuf;
7+
use std::process::Command;
68

79
#[derive(Debug, Deserialize)]
810
struct WorkspaceToml {
@@ -83,12 +85,13 @@ fn collect_all_dependencies(crate_name: &str, dep_map: &HashMap<String, HashSet<
8385
}
8486

8587
fn main() -> Result<()> {
86-
let output_path = std::env::args_os()
88+
let output_dir = std::env::args_os()
8789
.nth(1)
8890
.map(PathBuf::from)
89-
.ok_or_else(|| anyhow::anyhow!("Usage: crate-hierarchy-viz <output-file>"))?;
91+
.ok_or_else(|| anyhow::anyhow!("Usage: crate-hierarchy-viz <output-directory>"))?;
92+
let output_path = output_dir.join("crate-hierarchy.svg");
9093

91-
let workspace_root = std::env::current_dir().unwrap();
94+
let workspace_root = std::env::current_dir()?;
9295
let workspace_toml_path = workspace_root.join("Cargo.toml");
9396

9497
// Parse workspace Cargo.toml
@@ -173,17 +176,79 @@ fn main() -> Result<()> {
173176

174177
remove_transitive_dependencies(&mut crates);
175178

176-
// Generate DOT format and write to output file
179+
// Generate DOT format, convert to SVG, and write to output file
177180
let dot_content = generate_dot(&crates);
181+
let svg_content = dot_to_svg(&dot_content)?;
178182

179-
if let Some(parent) = output_path.parent() {
180-
fs::create_dir_all(parent).with_context(|| format!("Failed to create directory {:?}", parent))?;
181-
}
182-
fs::write(&output_path, &dot_content).with_context(|| format!("Failed to write to {:?}", output_path))?;
183+
fs::create_dir_all(&output_dir).with_context(|| format!("Failed to create directory {:?}", output_dir))?;
184+
fs::write(&output_path, &svg_content).with_context(|| format!("Failed to write to {:?}", output_path))?;
183185

184186
Ok(())
185187
}
186188

189+
/// Convert a DOT graph string to SVG by shelling out to @viz-js/viz via Node.js
190+
fn dot_to_svg(dot: &str) -> Result<String> {
191+
let temp_dir = std::env::temp_dir().join("crate-hierarchy-viz");
192+
fs::create_dir_all(&temp_dir).with_context(|| "Failed to create temp directory")?;
193+
194+
// Install @viz-js/viz into the temp directory if not already present
195+
let viz_package = temp_dir.join("node_modules").join("@viz-js").join("viz");
196+
if !viz_package.exists() {
197+
let npm = if cfg!(target_os = "windows") { "npm.cmd" } else { "npm" };
198+
let status = Command::new(npm)
199+
.args(["install", "--prefix", &temp_dir.to_string_lossy(), "@viz-js/viz"])
200+
.stdout(std::process::Stdio::null())
201+
.stderr(std::process::Stdio::piped())
202+
.status()
203+
.with_context(|| "Failed to run `npm install`. Is Node.js installed?")?;
204+
if !status.success() {
205+
anyhow::bail!("Executing `npm install @viz-js/viz` failed");
206+
}
207+
}
208+
209+
// Write a small script that reads DOT from stdin and outputs SVG
210+
let script_path = temp_dir.join("convert.mjs");
211+
fs::write(
212+
&script_path,
213+
r#"
214+
import { instance } from "@viz-js/viz";
215+
let dot = "";
216+
for await (const chunk of process.stdin) dot += chunk;
217+
const viz = await instance();
218+
process.stdout.write(viz.renderString(dot, { format: "svg" }));
219+
"#
220+
.trim(),
221+
)?;
222+
223+
let mut child = Command::new("node")
224+
.arg(&script_path)
225+
.stdin(std::process::Stdio::piped())
226+
.stdout(std::process::Stdio::piped())
227+
.stderr(std::process::Stdio::piped())
228+
.spawn()
229+
.with_context(|| "Failed to spawn `node`. Is Node.js installed?")?;
230+
231+
// Write DOT content to stdin then close the pipe
232+
child
233+
.stdin
234+
.take()
235+
.context("Failed to get stdin handle for node process")?
236+
.write_all(dot.as_bytes())
237+
.with_context(|| "Failed to write DOT content to stdin")?;
238+
239+
let output = child.wait_with_output().with_context(|| "Failed to wait for `node` process")?;
240+
241+
// Clean up the temp script (node_modules is intentionally kept as a cache)
242+
let _ = fs::remove_file(&script_path);
243+
244+
if !output.status.success() {
245+
let stderr = String::from_utf8_lossy(&output.stderr);
246+
anyhow::bail!("DOT to SVG conversion failed (exit code {:?}):\n{}", output.status.code(), stderr);
247+
}
248+
249+
String::from_utf8(output.stdout).with_context(|| "SVG output was not valid UTF-8")
250+
}
251+
187252
fn generate_dot(crates: &[CrateInfo]) -> String {
188253
let mut out = String::new();
189254
out.push_str("digraph CrateHierarchy {\n");

0 commit comments

Comments
 (0)