Repository: GraphiteEditor/Graphite
Branch: master
Commit: 0c7b5cd534f1
Files: 862
Total size: 11.1 MB
Directory structure:
gitextract_ahenmele/
├── .branding
├── .cargo/
│ └── config.toml
├── .devcontainer/
│ └── devcontainer.json
├── .editorconfig
├── .envrc
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── build.yml
│ ├── cargo-deny.yml
│ ├── check.yml
│ ├── comment-!build-commands.yml
│ ├── comment-clippy-warnings.yaml
│ ├── comment-profiling-changes.yaml
│ ├── library-rawkit.yml
│ ├── provide-shaders.yml
│ ├── scripts/
│ │ └── artifact-upload.bash
│ └── website.yml
├── .gitignore
├── .nix/
│ ├── default.nix
│ ├── deps/
│ │ ├── cef.nix
│ │ ├── crane.nix
│ │ └── rust-gpu.nix
│ ├── dev.nix
│ └── pkgs/
│ ├── graphite-branding.nix
│ ├── graphite-bundle.nix
│ ├── graphite-flatpak-manifest.nix
│ ├── graphite-raster-nodes-shaders.nix
│ ├── graphite.nix
│ └── tools/
│ └── third-party-licenses.nix
├── .nvmrc
├── .prettierrc
├── .vscode/
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── Cargo.toml
├── LICENSE.txt
├── README.md
├── about.toml
├── demo-artwork/
│ ├── changing-seasons.graphite
│ ├── isometric-fountain.graphite
│ ├── marbled-mandelbrot.graphite
│ ├── painted-dreams.graphite
│ ├── parametric-dunescape.graphite
│ ├── procedural-string-lights.graphite
│ ├── red-dress.graphite
│ └── valley-of-spires.graphite
├── deny.toml
├── desktop/
│ ├── Cargo.toml
│ ├── assets/
│ │ └── art.graphite.Graphite.desktop
│ ├── bundle/
│ │ ├── Cargo.toml
│ │ ├── build.rs
│ │ └── src/
│ │ ├── common.rs
│ │ ├── linux.rs
│ │ ├── mac.rs
│ │ ├── main.rs
│ │ └── win.rs
│ ├── embedded-resources/
│ │ ├── Cargo.toml
│ │ ├── build.rs
│ │ └── src/
│ │ └── lib.rs
│ ├── platform/
│ │ ├── linux/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ └── main.rs
│ │ ├── mac/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── helper.rs
│ │ │ └── main.rs
│ │ └── win/
│ │ ├── Cargo.toml
│ │ ├── build.rs
│ │ └── src/
│ │ └── main.rs
│ ├── src/
│ │ ├── app.rs
│ │ ├── cef/
│ │ │ ├── consts.rs
│ │ │ ├── context/
│ │ │ │ ├── builder.rs
│ │ │ │ ├── multithreaded.rs
│ │ │ │ └── singlethreaded.rs
│ │ │ ├── context.rs
│ │ │ ├── dirs.rs
│ │ │ ├── input/
│ │ │ │ ├── keymap.rs
│ │ │ │ └── state.rs
│ │ │ ├── input.rs
│ │ │ ├── internal/
│ │ │ │ ├── browser_process_app.rs
│ │ │ │ ├── browser_process_client.rs
│ │ │ │ ├── browser_process_handler.rs
│ │ │ │ ├── context_menu_handler.rs
│ │ │ │ ├── display_handler.rs
│ │ │ │ ├── life_span_handler.rs
│ │ │ │ ├── load_handler.rs
│ │ │ │ ├── render_handler.rs
│ │ │ │ ├── render_process_app.rs
│ │ │ │ ├── render_process_handler.rs
│ │ │ │ ├── render_process_v8_handler.rs
│ │ │ │ ├── resource_handler.rs
│ │ │ │ ├── scheme_handler_factory.rs
│ │ │ │ └── task.rs
│ │ │ ├── internal.rs
│ │ │ ├── ipc.rs
│ │ │ ├── platform.rs
│ │ │ └── utility.rs
│ │ ├── cef.rs
│ │ ├── cli.rs
│ │ ├── consts.rs
│ │ ├── dirs.rs
│ │ ├── event.rs
│ │ ├── gpu_context.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ ├── persist.rs
│ │ ├── preferences.rs
│ │ ├── render/
│ │ │ ├── composite_shader.wgsl
│ │ │ ├── frame_buffer_ref.rs
│ │ │ └── state.rs
│ │ ├── render.rs
│ │ ├── window/
│ │ │ ├── linux.rs
│ │ │ ├── mac/
│ │ │ │ ├── app.rs
│ │ │ │ └── menu.rs
│ │ │ ├── mac.rs
│ │ │ ├── win/
│ │ │ │ └── native_handle.rs
│ │ │ └── win.rs
│ │ └── window.rs
│ └── wrapper/
│ ├── Cargo.toml
│ └── src/
│ ├── handle_desktop_wrapper_message.rs
│ ├── intercept_editor_message.rs
│ ├── intercept_frontend_message.rs
│ ├── lib.rs
│ ├── message_dispatcher.rs
│ ├── messages.rs
│ └── utils.rs
├── editor/
│ ├── Cargo.toml
│ ├── build.rs
│ └── src/
│ ├── application.rs
│ ├── consts.rs
│ ├── dispatcher.rs
│ ├── lib.rs
│ ├── macros.rs
│ ├── messages/
│ │ ├── animation/
│ │ │ ├── animation_message.rs
│ │ │ ├── animation_message_handler.rs
│ │ │ └── mod.rs
│ │ ├── app_window/
│ │ │ ├── app_window_message.rs
│ │ │ ├── app_window_message_handler.rs
│ │ │ └── mod.rs
│ │ ├── broadcast/
│ │ │ ├── broadcast_message.rs
│ │ │ ├── broadcast_message_handler.rs
│ │ │ ├── event/
│ │ │ │ ├── event_message.rs
│ │ │ │ ├── event_message_handler.rs
│ │ │ │ └── mod.rs
│ │ │ └── mod.rs
│ │ ├── clipboard/
│ │ │ ├── clipboard_message.rs
│ │ │ ├── clipboard_message_handler.rs
│ │ │ ├── mod.rs
│ │ │ └── utility_types.rs
│ │ ├── debug/
│ │ │ ├── debug_message.rs
│ │ │ ├── debug_message_handler.rs
│ │ │ ├── mod.rs
│ │ │ └── utility_types.rs
│ │ ├── defer/
│ │ │ ├── defer_message.rs
│ │ │ ├── defer_message_handler.rs
│ │ │ └── mod.rs
│ │ ├── dialog/
│ │ │ ├── dialog_message.rs
│ │ │ ├── dialog_message_handler.rs
│ │ │ ├── export_dialog/
│ │ │ │ ├── export_dialog_message.rs
│ │ │ │ ├── export_dialog_message_handler.rs
│ │ │ │ └── mod.rs
│ │ │ ├── mod.rs
│ │ │ ├── new_document_dialog/
│ │ │ │ ├── mod.rs
│ │ │ │ ├── new_document_dialog_message.rs
│ │ │ │ └── new_document_dialog_message_handler.rs
│ │ │ ├── preferences_dialog/
│ │ │ │ ├── mod.rs
│ │ │ │ ├── preferences_dialog_message.rs
│ │ │ │ └── preferences_dialog_message_handler.rs
│ │ │ └── simple_dialogs/
│ │ │ ├── about_graphite_dialog.rs
│ │ │ ├── close_all_documents_dialog.rs
│ │ │ ├── close_document_dialog.rs
│ │ │ ├── confirm_restart_dialog.rs
│ │ │ ├── demo_artwork_dialog.rs
│ │ │ ├── error_dialog.rs
│ │ │ ├── licenses_dialog.rs
│ │ │ ├── licenses_third_party_dialog.rs
│ │ │ └── mod.rs
│ │ ├── frontend/
│ │ │ ├── frontend_message.rs
│ │ │ ├── mod.rs
│ │ │ └── utility_types.rs
│ │ ├── input_mapper/
│ │ │ ├── input_mapper_message.rs
│ │ │ ├── input_mapper_message_handler.rs
│ │ │ ├── input_mappings.rs
│ │ │ ├── key_mapping/
│ │ │ │ ├── key_mapping_message.rs
│ │ │ │ ├── key_mapping_message_handler.rs
│ │ │ │ └── mod.rs
│ │ │ ├── mod.rs
│ │ │ └── utility_types/
│ │ │ ├── input_keyboard.rs
│ │ │ ├── input_mouse.rs
│ │ │ ├── macros.rs
│ │ │ ├── misc.rs
│ │ │ └── mod.rs
│ │ ├── input_preprocessor/
│ │ │ ├── input_preprocessor_message.rs
│ │ │ ├── input_preprocessor_message_handler.rs
│ │ │ └── mod.rs
│ │ ├── layout/
│ │ │ ├── layout_message.rs
│ │ │ ├── layout_message_handler.rs
│ │ │ ├── mod.rs
│ │ │ └── utility_types/
│ │ │ ├── layout_widget.rs
│ │ │ ├── mod.rs
│ │ │ └── widgets/
│ │ │ ├── button_widgets.rs
│ │ │ ├── input_widgets.rs
│ │ │ ├── label_widgets.rs
│ │ │ └── mod.rs
│ │ ├── menu_bar/
│ │ │ ├── menu_bar_message.rs
│ │ │ ├── menu_bar_message_handler.rs
│ │ │ └── mod.rs
│ │ ├── message.rs
│ │ ├── mod.rs
│ │ ├── portfolio/
│ │ │ ├── document/
│ │ │ │ ├── data_panel/
│ │ │ │ │ ├── data_panel_message.rs
│ │ │ │ │ ├── data_panel_message_handler.rs
│ │ │ │ │ └── mod.rs
│ │ │ │ ├── document_message.rs
│ │ │ │ ├── document_message_handler.rs
│ │ │ │ ├── graph_operation/
│ │ │ │ │ ├── graph_operation_message.rs
│ │ │ │ │ ├── graph_operation_message_handler.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── transform_utils.rs
│ │ │ │ │ └── utility_types.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── navigation/
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── navigation_message.rs
│ │ │ │ │ ├── navigation_message_handler.rs
│ │ │ │ │ └── utility_types.rs
│ │ │ │ ├── node_graph/
│ │ │ │ │ ├── document_node_definitions/
│ │ │ │ │ │ └── document_node_derive.rs
│ │ │ │ │ ├── document_node_definitions.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── node_graph_message.rs
│ │ │ │ │ ├── node_graph_message_handler.rs
│ │ │ │ │ ├── node_properties.rs
│ │ │ │ │ └── utility_types.rs
│ │ │ │ ├── overlays/
│ │ │ │ │ ├── grid_overlays.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── overlays_message.rs
│ │ │ │ │ ├── overlays_message_handler.rs
│ │ │ │ │ ├── utility_functions.rs
│ │ │ │ │ ├── utility_types_native.rs
│ │ │ │ │ └── utility_types_web.rs
│ │ │ │ ├── properties_panel/
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── properties_panel_message.rs
│ │ │ │ │ └── properties_panel_message_handler.rs
│ │ │ │ └── utility_types/
│ │ │ │ ├── clipboards.rs
│ │ │ │ ├── document_metadata.rs
│ │ │ │ ├── error.rs
│ │ │ │ ├── misc.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── network_interface/
│ │ │ │ │ ├── deserialization.rs
│ │ │ │ │ ├── memo_network.rs
│ │ │ │ │ └── resolved_types.rs
│ │ │ │ ├── network_interface.rs
│ │ │ │ ├── nodes.rs
│ │ │ │ ├── transformation.rs
│ │ │ │ └── wires.rs
│ │ │ ├── document_migration.rs
│ │ │ ├── mod.rs
│ │ │ ├── portfolio_message.rs
│ │ │ ├── portfolio_message_handler.rs
│ │ │ └── utility_types.rs
│ │ ├── preferences/
│ │ │ ├── mod.rs
│ │ │ ├── preferences_message.rs
│ │ │ ├── preferences_message_handler.rs
│ │ │ └── utility_types.rs
│ │ ├── prelude.rs
│ │ ├── tool/
│ │ │ ├── common_functionality/
│ │ │ │ ├── auto_panning.rs
│ │ │ │ ├── color_selector.rs
│ │ │ │ ├── compass_rose.rs
│ │ │ │ ├── gizmos/
│ │ │ │ │ ├── gizmo_manager.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ └── shape_gizmos/
│ │ │ │ │ ├── circle_arc_radius_handle.rs
│ │ │ │ │ ├── grid_rows_columns_gizmo.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── number_of_points_dial.rs
│ │ │ │ │ ├── point_radius_handle.rs
│ │ │ │ │ ├── spiral_turns_handle.rs
│ │ │ │ │ └── sweep_angle_gizmo.rs
│ │ │ │ ├── graph_modification_utils.rs
│ │ │ │ ├── layer_origin_cross.rs
│ │ │ │ ├── measure.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── pivot.rs
│ │ │ │ ├── resize.rs
│ │ │ │ ├── shape_editor.rs
│ │ │ │ ├── shapes/
│ │ │ │ │ ├── arc_shape.rs
│ │ │ │ │ ├── arrow_shape.rs
│ │ │ │ │ ├── circle_shape.rs
│ │ │ │ │ ├── ellipse_shape.rs
│ │ │ │ │ ├── grid_shape.rs
│ │ │ │ │ ├── line_shape.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── polygon_shape.rs
│ │ │ │ │ ├── rectangle_shape.rs
│ │ │ │ │ ├── shape_utility.rs
│ │ │ │ │ ├── spiral_shape.rs
│ │ │ │ │ └── star_shape.rs
│ │ │ │ ├── snapping/
│ │ │ │ │ ├── alignment_snapper.rs
│ │ │ │ │ ├── distribution_snapper.rs
│ │ │ │ │ ├── grid_snapper.rs
│ │ │ │ │ ├── layer_snapper.rs
│ │ │ │ │ └── snap_results.rs
│ │ │ │ ├── snapping.rs
│ │ │ │ ├── transformation_cage.rs
│ │ │ │ └── utility_functions.rs
│ │ │ ├── mod.rs
│ │ │ ├── tool_message.rs
│ │ │ ├── tool_message_handler.rs
│ │ │ ├── tool_messages/
│ │ │ │ ├── artboard_tool.rs
│ │ │ │ ├── brush_tool.rs
│ │ │ │ ├── eyedropper_tool.rs
│ │ │ │ ├── fill_tool.rs
│ │ │ │ ├── freehand_tool.rs
│ │ │ │ ├── gradient_tool.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── navigate_tool.rs
│ │ │ │ ├── path_tool.rs
│ │ │ │ ├── pen_tool.rs
│ │ │ │ ├── select_tool.rs
│ │ │ │ ├── shape_tool.rs
│ │ │ │ ├── spline_tool.rs
│ │ │ │ └── text_tool.rs
│ │ │ ├── transform_layer/
│ │ │ │ ├── mod.rs
│ │ │ │ ├── transform_layer_message.rs
│ │ │ │ └── transform_layer_message_handler.rs
│ │ │ └── utility_types.rs
│ │ └── viewport/
│ │ ├── mod.rs
│ │ ├── viewport_message.rs
│ │ └── viewport_message_handler.rs
│ ├── node_graph_executor/
│ │ ├── runtime.rs
│ │ └── runtime_io.rs
│ ├── node_graph_executor.rs
│ ├── test_utils.rs
│ ├── utility_traits.rs
│ └── utility_types.rs
├── flake.nix
├── frontend/
│ ├── .gitignore
│ ├── .prettierrc
│ ├── README.md
│ ├── branding-installer.js
│ ├── eslint.config.js
│ ├── index.html
│ ├── package-installer.js
│ ├── package.json
│ ├── src/
│ │ ├── App.svelte
│ │ ├── README.md
│ │ ├── components/
│ │ │ ├── Editor.svelte
│ │ │ ├── README.md
│ │ │ ├── floating-menus/
│ │ │ │ ├── ColorPicker.svelte
│ │ │ │ ├── Dialog.svelte
│ │ │ │ ├── EyedropperPreview.svelte
│ │ │ │ ├── MenuList.svelte
│ │ │ │ ├── NodeCatalog.svelte
│ │ │ │ └── Tooltip.svelte
│ │ │ ├── layout/
│ │ │ │ ├── ConditionalWrapper.svelte
│ │ │ │ ├── FloatingMenu.svelte
│ │ │ │ ├── LayoutCol.svelte
│ │ │ │ └── LayoutRow.svelte
│ │ │ ├── panels/
│ │ │ │ ├── Data.svelte
│ │ │ │ ├── Document.svelte
│ │ │ │ ├── Layers.svelte
│ │ │ │ ├── Properties.svelte
│ │ │ │ └── Welcome.svelte
│ │ │ ├── views/
│ │ │ │ └── Graph.svelte
│ │ │ ├── widgets/
│ │ │ │ ├── WidgetLayout.svelte
│ │ │ │ ├── WidgetSection.svelte
│ │ │ │ ├── WidgetSpan.svelte
│ │ │ │ ├── WidgetTable.svelte
│ │ │ │ ├── buttons/
│ │ │ │ │ ├── BreadcrumbTrailButtons.svelte
│ │ │ │ │ ├── IconButton.svelte
│ │ │ │ │ ├── ImageButton.svelte
│ │ │ │ │ ├── ParameterExposeButton.svelte
│ │ │ │ │ ├── PopoverButton.svelte
│ │ │ │ │ └── TextButton.svelte
│ │ │ │ ├── inputs/
│ │ │ │ │ ├── CheckboxInput.svelte
│ │ │ │ │ ├── ColorInput.svelte
│ │ │ │ │ ├── CurveInput.svelte
│ │ │ │ │ ├── DropdownInput.svelte
│ │ │ │ │ ├── FieldInput.svelte
│ │ │ │ │ ├── NumberInput.svelte
│ │ │ │ │ ├── RadioInput.svelte
│ │ │ │ │ ├── ReferencePointInput.svelte
│ │ │ │ │ ├── RulerInput.svelte
│ │ │ │ │ ├── ScrollbarInput.svelte
│ │ │ │ │ ├── SpectrumInput.svelte
│ │ │ │ │ ├── TextAreaInput.svelte
│ │ │ │ │ ├── TextInput.svelte
│ │ │ │ │ └── WorkingColorsInput.svelte
│ │ │ │ └── labels/
│ │ │ │ ├── IconLabel.svelte
│ │ │ │ ├── ImageLabel.svelte
│ │ │ │ ├── Separator.svelte
│ │ │ │ ├── ShortcutLabel.svelte
│ │ │ │ └── TextLabel.svelte
│ │ │ └── window/
│ │ │ ├── MainWindow.svelte
│ │ │ ├── Panel.svelte
│ │ │ ├── StatusBar.svelte
│ │ │ ├── TitleBar.svelte
│ │ │ └── Workspace.svelte
│ │ ├── editor.ts
│ │ ├── global.d.ts
│ │ ├── icons.ts
│ │ ├── main.ts
│ │ ├── managers/
│ │ │ ├── clipboard.ts
│ │ │ ├── fonts.ts
│ │ │ ├── hyperlink.ts
│ │ │ ├── input.ts
│ │ │ ├── localization.ts
│ │ │ ├── panic.ts
│ │ │ └── persistence.ts
│ │ ├── stores/
│ │ │ ├── app-window.ts
│ │ │ ├── dialog.ts
│ │ │ ├── document.ts
│ │ │ ├── fullscreen.ts
│ │ │ ├── node-graph.ts
│ │ │ ├── portfolio.ts
│ │ │ └── tooltip.ts
│ │ ├── subscription-router.ts
│ │ ├── utility-functions/
│ │ │ ├── colors.ts
│ │ │ ├── crash-report.ts
│ │ │ ├── escape.ts
│ │ │ ├── files.ts
│ │ │ ├── images.ts
│ │ │ ├── keyboard-entry.ts
│ │ │ ├── network.ts
│ │ │ ├── panic-proxy.ts
│ │ │ ├── platform.ts
│ │ │ ├── rasterization.ts
│ │ │ ├── strip-indents.ts
│ │ │ ├── viewports.ts
│ │ │ └── widgets.ts
│ │ ├── vite-env-override.d.ts
│ │ └── vite-env.d.ts
│ ├── svelte.config.js
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── wasm/
│ ├── Cargo.toml
│ ├── README.md
│ └── src/
│ ├── editor_api.rs
│ ├── helpers.rs
│ ├── lib.rs
│ └── native_communication.rs
├── libraries/
│ ├── dyn-any/
│ │ ├── Cargo.toml
│ │ ├── LICENSE-APACHE
│ │ ├── LICENSE-MIT
│ │ ├── README.md
│ │ ├── derive/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ └── lib.rs
│ │ └── src/
│ │ └── lib.rs
│ ├── math-parser/
│ │ ├── Cargo.toml
│ │ ├── benches/
│ │ │ └── bench.rs
│ │ └── src/
│ │ ├── ast.rs
│ │ ├── constants.rs
│ │ ├── context.rs
│ │ ├── executer.rs
│ │ ├── grammer.pest
│ │ ├── lib.rs
│ │ ├── parser.rs
│ │ └── value.rs
│ └── rawkit/
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── LICENSE-APACHE
│ ├── LICENSE-MIT
│ ├── README.md
│ ├── camera_data/
│ │ └── Sony/
│ │ ├── DSLR-A100.toml
│ │ ├── DSLR-A200.toml
│ │ ├── DSLR-A300.toml
│ │ ├── DSLR-A330.toml
│ │ ├── DSLR-A350.toml
│ │ ├── DSLR-A550.toml
│ │ ├── DSLR-A700.toml
│ │ ├── DSLR-A850.toml
│ │ ├── DSLR-A900.toml
│ │ ├── ILCA-68.toml
│ │ ├── ILCA-99M2.toml
│ │ ├── ILCE-1.toml
│ │ ├── ILCE-5100.toml
│ │ ├── ILCE-6000.toml
│ │ ├── ILCE-6100.toml
│ │ ├── ILCE-6300.toml
│ │ ├── ILCE-6400.toml
│ │ ├── ILCE-6500.toml
│ │ ├── ILCE-6600.toml
│ │ ├── ILCE-6700.toml
│ │ ├── ILCE-7CM2.toml
│ │ ├── ILCE-7CR.toml
│ │ ├── ILCE-7M2.toml
│ │ ├── ILCE-7M3.toml
│ │ ├── ILCE-7RM3.toml
│ │ ├── ILCE-7RM4.toml
│ │ ├── ILCE-7RM5.toml
│ │ ├── ILCE-7SM2.toml
│ │ ├── ILCE-9.toml
│ │ ├── ILCE-9M2.toml
│ │ ├── ILCE-9M3.toml
│ │ ├── NEX-3.toml
│ │ ├── NEX-3N.toml
│ │ ├── NEX-5R.toml
│ │ ├── NEX-6.toml
│ │ ├── NEX-7.toml
│ │ ├── ZV-1.toml
│ │ ├── ZV-1M2.toml
│ │ ├── ZV-E1.toml
│ │ └── ZV-E10.toml
│ ├── rawkit-proc-macros/
│ │ ├── Cargo.toml
│ │ ├── LICENSE-APACHE
│ │ ├── LICENSE-MIT
│ │ ├── README.md
│ │ └── src/
│ │ ├── build_camera_data.rs
│ │ ├── lib.rs
│ │ └── tag_derive.rs
│ ├── src/
│ │ ├── decoder/
│ │ │ ├── arw1.rs
│ │ │ ├── arw2.rs
│ │ │ ├── mod.rs
│ │ │ └── uncompressed.rs
│ │ ├── demosaicing/
│ │ │ ├── linear_demosaicing.rs
│ │ │ └── mod.rs
│ │ ├── lib.rs
│ │ ├── metadata/
│ │ │ ├── camera_data.rs
│ │ │ ├── identify.rs
│ │ │ └── mod.rs
│ │ ├── postprocessing/
│ │ │ ├── convert_to_rgb.rs
│ │ │ ├── gamma_correction.rs
│ │ │ ├── mod.rs
│ │ │ ├── record_histogram.rs
│ │ │ └── transform.rs
│ │ ├── preprocessing/
│ │ │ ├── mod.rs
│ │ │ ├── scale_to_16bit.rs
│ │ │ ├── scale_white_balance.rs
│ │ │ └── subtract_black.rs
│ │ ├── processing.rs
│ │ └── tiff/
│ │ ├── file.rs
│ │ ├── mod.rs
│ │ ├── tags.rs
│ │ ├── types.rs
│ │ └── values.rs
│ └── tests/
│ ├── images/
│ │ └── .gitkeep
│ └── tests.rs
├── node-graph/
│ ├── LICENSE
│ ├── README.md
│ ├── graph-craft/
│ │ ├── Cargo.toml
│ │ ├── benches/
│ │ │ ├── compile_demo_art_criterion.rs
│ │ │ └── compile_demo_art_iai.rs
│ │ └── src/
│ │ ├── document/
│ │ │ └── value.rs
│ │ ├── document.rs
│ │ ├── graphene_compiler.rs
│ │ ├── lib.rs
│ │ ├── proto.rs
│ │ ├── util.rs
│ │ └── wasm_application_io.rs
│ ├── graphene-cli/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── export.rs
│ │ └── main.rs
│ ├── interpreted-executor/
│ │ ├── Cargo.toml
│ │ ├── benches/
│ │ │ ├── benchmark_util.rs
│ │ │ ├── run_cached.rs
│ │ │ ├── run_cached_iai.rs
│ │ │ ├── run_demo_art_criterion.rs
│ │ │ ├── run_once.rs
│ │ │ ├── run_once_iai.rs
│ │ │ ├── update_executor.rs
│ │ │ └── update_executor_iai.rs
│ │ └── src/
│ │ ├── dynamic_executor.rs
│ │ ├── lib.rs
│ │ ├── node_registry.rs
│ │ └── util.rs
│ ├── libraries/
│ │ ├── application-io/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ └── lib.rs
│ │ ├── core-types/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── bounds.rs
│ │ │ ├── consts.rs
│ │ │ ├── context.rs
│ │ │ ├── generic.rs
│ │ │ ├── lib.rs
│ │ │ ├── math/
│ │ │ │ ├── bbox.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── polynomial.rs
│ │ │ │ ├── quad.rs
│ │ │ │ └── rect.rs
│ │ │ ├── memo.rs
│ │ │ ├── misc.rs
│ │ │ ├── ops.rs
│ │ │ ├── registry.rs
│ │ │ ├── render_complexity.rs
│ │ │ ├── table.rs
│ │ │ ├── text.rs
│ │ │ ├── transform.rs
│ │ │ ├── types.rs
│ │ │ ├── uuid.rs
│ │ │ └── value.rs
│ │ ├── graphic-types/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── artboard.rs
│ │ │ ├── graphic.rs
│ │ │ └── lib.rs
│ │ ├── no-std-types/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── blending.rs
│ │ │ ├── choice_type.rs
│ │ │ ├── color/
│ │ │ │ ├── color_traits.rs
│ │ │ │ ├── color_types.rs
│ │ │ │ ├── discrete_srgb.rs
│ │ │ │ └── mod.rs
│ │ │ ├── context.rs
│ │ │ ├── lib.rs
│ │ │ ├── registry.rs
│ │ │ └── shaders/
│ │ │ ├── buffer_struct/
│ │ │ │ ├── glam.rs
│ │ │ │ ├── mod.rs
│ │ │ │ └── primitive.rs
│ │ │ └── mod.rs
│ │ ├── raster-types/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── image.rs
│ │ │ ├── lib.rs
│ │ │ └── raster_types.rs
│ │ ├── rendering/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── convert_usvg_path.rs
│ │ │ ├── lib.rs
│ │ │ ├── render_ext.rs
│ │ │ ├── renderer.rs
│ │ │ └── to_peniko.rs
│ │ ├── vector-types/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── gradient.rs
│ │ │ ├── lib.rs
│ │ │ ├── math/
│ │ │ │ └── mod.rs
│ │ │ ├── subpath/
│ │ │ │ ├── consts.rs
│ │ │ │ ├── core.rs
│ │ │ │ ├── lookup.rs
│ │ │ │ ├── manipulators.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── solvers.rs
│ │ │ │ ├── structs.rs
│ │ │ │ └── transform.rs
│ │ │ └── vector/
│ │ │ ├── algorithms/
│ │ │ │ ├── bezpath_algorithms.rs
│ │ │ │ ├── contants.rs
│ │ │ │ ├── intersection.rs
│ │ │ │ ├── merge_by_distance.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── offset_subpath.rs
│ │ │ │ ├── poisson_disk.rs
│ │ │ │ ├── spline.rs
│ │ │ │ └── util.rs
│ │ │ ├── click_target.rs
│ │ │ ├── misc.rs
│ │ │ ├── mod.rs
│ │ │ ├── reference_point.rs
│ │ │ ├── style.rs
│ │ │ ├── vector_attributes.rs
│ │ │ ├── vector_modification.rs
│ │ │ └── vector_types.rs
│ │ └── wgpu-executor/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── context.rs
│ │ ├── lib.rs
│ │ ├── resample.rs
│ │ ├── resample_shader.wgsl
│ │ ├── shader_runtime/
│ │ │ ├── mod.rs
│ │ │ └── per_pixel_adjust_runtime.rs
│ │ └── texture_conversion.rs
│ ├── node-macro/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── buffer_struct.rs
│ │ ├── codegen.rs
│ │ ├── crate_ident.rs
│ │ ├── derive_choice_type.rs
│ │ ├── lib.rs
│ │ ├── parsing.rs
│ │ ├── shader_nodes/
│ │ │ ├── mod.rs
│ │ │ └── per_pixel_adjust.rs
│ │ └── validation.rs
│ ├── nodes/
│ │ ├── blending/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ └── lib.rs
│ │ ├── brush/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── brush.rs
│ │ │ ├── brush_cache.rs
│ │ │ ├── brush_stroke.rs
│ │ │ └── lib.rs
│ │ ├── gcore/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── animation.rs
│ │ │ ├── context.rs
│ │ │ ├── context_modification.rs
│ │ │ ├── debug.rs
│ │ │ ├── extract_xy.rs
│ │ │ ├── lib.rs
│ │ │ ├── logic.rs
│ │ │ ├── memo.rs
│ │ │ └── ops.rs
│ │ ├── graphic/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── artboard.rs
│ │ │ ├── graphic.rs
│ │ │ └── lib.rs
│ │ ├── gstd/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── any.rs
│ │ │ ├── lib.rs
│ │ │ ├── pixel_preview.rs
│ │ │ ├── render_cache.rs
│ │ │ ├── render_node.rs
│ │ │ ├── text.rs
│ │ │ └── wasm_application_io.rs
│ │ ├── math/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ └── lib.rs
│ │ ├── path-bool/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ └── lib.rs
│ │ ├── raster/
│ │ │ ├── Cargo.toml
│ │ │ ├── shaders/
│ │ │ │ ├── Cargo.toml
│ │ │ │ ├── build.rs
│ │ │ │ ├── entrypoint/
│ │ │ │ │ ├── Cargo.toml
│ │ │ │ │ └── src/
│ │ │ │ │ └── lib.rs
│ │ │ │ ├── spirv-unknown-naga-wgsl.json
│ │ │ │ └── src/
│ │ │ │ └── lib.rs
│ │ │ └── src/
│ │ │ ├── adjust.rs
│ │ │ ├── adjustments.rs
│ │ │ ├── blending_nodes.rs
│ │ │ ├── cubic_spline.rs
│ │ │ ├── curve.rs
│ │ │ ├── dehaze.rs
│ │ │ ├── filter.rs
│ │ │ ├── fullscreen_vertex.rs
│ │ │ ├── generate_curves.rs
│ │ │ ├── gradient_map.rs
│ │ │ ├── image_color_palette.rs
│ │ │ ├── lib.rs
│ │ │ └── std_nodes.rs
│ │ ├── repeat/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── lib.rs
│ │ │ └── repeat_nodes.rs
│ │ ├── text/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── font_cache.rs
│ │ │ ├── lib.rs
│ │ │ ├── path_builder.rs
│ │ │ ├── text_context.rs
│ │ │ └── to_path.rs
│ │ ├── transform/
│ │ │ ├── Cargo.toml
│ │ │ └── src/
│ │ │ ├── lib.rs
│ │ │ └── transform_nodes.rs
│ │ └── vector/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── generator_nodes.rs
│ │ ├── lib.rs
│ │ ├── merge_qr_squares.rs
│ │ ├── vector_modification_nodes.rs
│ │ └── vector_nodes.rs
│ ├── preprocessor/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── lib.rs
│ └── rfcs/
│ └── fine-grained-context-caching.md
├── proc-macros/
│ ├── Cargo.toml
│ └── src/
│ ├── as_message.rs
│ ├── combined_message_attrs.rs
│ ├── discriminant.rs
│ ├── extract_fields.rs
│ ├── helper_structs.rs
│ ├── helpers.rs
│ ├── hierarchical_tree.rs
│ ├── hint.rs
│ ├── lib.rs
│ ├── message_handler_data_attr.rs
│ ├── transitive_child.rs
│ └── widget_builder.rs
├── rustfmt.toml
├── tools/
│ ├── cargo-run/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ └── requirements.rs
│ ├── crate-hierarchy-viz/
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── main.rs
│ ├── editor-message-tree/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ └── main.rs
│ ├── node-docs/
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── main.rs
│ │ ├── page_catalog.rs
│ │ ├── page_category.rs
│ │ ├── page_node.rs
│ │ └── utility.rs
│ └── third-party-licenses/
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── build.rs
│ └── src/
│ ├── cargo.rs
│ ├── cef.rs
│ ├── main.rs
│ └── npm.rs
└── website/
├── .build-scripts/
│ └── install.ts
├── .gitignore
├── config.toml
├── content/
│ ├── _index.md
│ ├── about.md
│ ├── blog/
│ │ ├── 2022-02-12-announcing-graphite-alpha.md
│ │ ├── 2022-03-12-graphite-a-vision-for-the-future-of-2d-content-creation.md
│ │ ├── 2022-05-12-distributed-computing-in-the-graphene-runtime.md
│ │ ├── 2024-01-01-looking-back-on-2023-and-what's-next.md
│ │ ├── 2024-02-22-graphite-internships-announcing-participation-in-gsoc-2024.md
│ │ ├── 2024-05-09-graphite-progress-report-q1-2024.md
│ │ ├── 2024-07-31-graphite-progress-report-q2-2024.md
│ │ ├── 2024-10-15-graphite-progress-report-q3-2024.md
│ │ ├── 2025-01-16-year-in-review-2024-highlights-and-a-peek-at-2025.md
│ │ ├── 2025-03-31-graphite-progress-report-q4-2024.md
│ │ ├── 2025-04-02-internships-for-a-rust-graphics-engine-gsoc-2025.md
│ │ ├── 2025-09-19-graphite-community-meetup-in-germany.md
│ │ └── _index.md
│ ├── contact.md
│ ├── donate.md
│ ├── features.md
│ ├── learn/
│ │ ├── _index.md
│ │ ├── _outline_draft.md
│ │ ├── interface/
│ │ │ ├── _index.md
│ │ │ ├── document-panel.md
│ │ │ └── menu-bar.md
│ │ └── introduction/
│ │ ├── _index.md
│ │ └── features-and-limitations.md
│ ├── license.md
│ ├── logo.md
│ ├── press.md
│ ├── privacy-policy.md
│ └── volunteer/
│ ├── _index.md
│ └── guide/
│ ├── _index.md
│ ├── codebase-overview/
│ │ ├── _index.md
│ │ ├── debugging-tips.md
│ │ └── editor-structure.md
│ ├── graphene/
│ │ ├── _index.md
│ │ └── networks-and-nodes.md
│ ├── project-setup/
│ │ └── _index.md
│ ├── starting-a-task/
│ │ ├── _index.md
│ │ ├── ai-contribution-policy.md
│ │ ├── code-quality-guidelines.md
│ │ └── submitting-a-contribution.md
│ └── student-projects/
│ ├── _index.md
│ └── completed-projects.md
├── eslint.config.js
├── package.json
├── sass/
│ ├── base.scss
│ ├── component/
│ │ ├── carousel.scss
│ │ ├── code-snippet.scss
│ │ ├── demo-artwork.scss
│ │ ├── feature-box.scss
│ │ ├── feature-icons.scss
│ │ ├── image-comparison.scss
│ │ └── youtube-embed.scss
│ ├── layout/
│ │ └── reading-material.scss
│ ├── page/
│ │ ├── about.scss
│ │ ├── blog.scss
│ │ ├── contributor-guide/
│ │ │ ├── bisect-tool.scss
│ │ │ ├── crate-hierarchy.scss
│ │ │ └── editor-structure.scss
│ │ ├── donate.scss
│ │ ├── features.scss
│ │ ├── index.scss
│ │ ├── logo.scss
│ │ ├── user-manual/
│ │ │ ├── node-catalog.scss
│ │ │ ├── node-category.scss
│ │ │ └── node.scss
│ │ └── volunteer.scss
│ └── template/
│ ├── article.scss
│ └── book.scss
├── static/
│ └── js/
│ ├── component/
│ │ ├── carousel.js
│ │ ├── image-comparison.js
│ │ ├── video-autoplay.js
│ │ └── youtube-embed.js
│ ├── navbar.js
│ ├── page/
│ │ └── contributor-guide/
│ │ ├── bisect-tool.js
│ │ ├── crate-hierarchy.js
│ │ └── editor-structure.js
│ ├── template/
│ │ └── book.js
│ └── text-justification.js
├── templates/
│ ├── 404.html
│ ├── article.html
│ ├── base.html
│ ├── blog.html
│ ├── book.html
│ ├── macros/
│ │ ├── book-outline.html
│ │ └── replacements.html
│ ├── page.html
│ └── section.html
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .branding
================================================
https://github.com/Keavon/graphite-branded-assets/archive/8ae15dc9c51a3855475d8cab1d0f29d9d9bc622c.tar.gz
c19abe4ac848f3c835e43dc065c59e20e60233ae023ea0a064c5fed442be2d3d
================================================
FILE: .cargo/config.toml
================================================
[target.wasm32-unknown-unknown]
rustflags = [
# Currently disabled because of https://github.com/GraphiteEditor/Graphite/issues/1262
# The current simd implementation leads to undefined behavior
#"-C",
#"target-feature=+simd128",
"-C",
"target-feature=+bulk-memory",
"-C",
"link-arg=--max-memory=4294967296",
"--cfg=web_sys_unstable_apis",
]
[env]
CARGO_WORKSPACE_DIR = { value = "", relative = true }
================================================
FILE: .devcontainer/devcontainer.json
================================================
{
"image": "mcr.microsoft.com/devcontainers/base:debian",
"features": {
"ghcr.io/devcontainers/features/rust:1": {
"profile": "default"
},
"ghcr.io/devcontainers/features/node:1": {}
},
"onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.100",
"customizations": {
"vscode": {
// NOTE: Keep this in sync with `.vscode/extensions.json`
"extensions": [
// Rust
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
// Web
"dbaeumer.vscode-eslint",
"svelte.svelte-vscode",
"vitaliymaz.vscode-svg-previewer",
// Code quality
"wayou.vscode-todo-highlight",
"streetsidesoftware.code-spell-checker",
// Helpful
"mhutchie.git-graph",
"qezhu.gitlink",
"wmaurer.change-case"
]
}
}
}
================================================
FILE: .editorconfig
================================================
[*.{rs,js,ts,svelte,json,toml,svg,html,css,scss}]
indent_style = tab
indent_size = 4
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 200
================================================
FILE: .envrc
================================================
use flake
================================================
FILE: .gitattributes
================================================
# Requires Git to check out files with the LF (not CRLF) line endings for files it automatically recognizes as being text-based
# The `*` targets all files
# The `text=auto` makes it apply a conversion only to files detected as text-based
# The `eol=lf` sets the conversion to an LF line ending
# https://git-scm.com/docs/gitattributes
* text=auto eol=lf
# Adds syntax highlighting to Graphite files on GitHub and minimizes diffs both locally and on GitHub
*.graphite binary linguist-generated linguist-language=JSON
/node-graph/graphene-cli/test_files/*.graphite text diff -linguist-generated
================================================
FILE: .github/FUNDING.yml
================================================
github: [GraphiteEditor]
================================================
FILE: .github/pull_request_template.md
================================================
================================================
FILE: .github/workflows/build.yml
================================================
name: "Build"
on:
push:
branches:
- master
tags:
- latest-stable
workflow_dispatch:
inputs:
web:
description: "Web"
type: boolean
windows:
description: "Windows"
type: boolean
mac:
description: "Mac"
type: boolean
linux:
description: "Linux"
type: boolean
push_to_nix_cache:
description: "Linux: push to Nix cache"
type: boolean
debug:
description: "Debug build"
type: boolean
workflow_call:
inputs:
web:
type: boolean
windows:
type: boolean
mac:
type: boolean
linux:
type: boolean
push_to_nix_cache:
type: boolean
debug:
type: boolean
checkout_repo:
type: string
checkout_ref:
type: string
pr_number:
type: string
jobs:
web:
if: github.event_name == 'push' || inputs.web
runs-on: [self-hosted, target/wasm]
permissions:
contents: write
deployments: write
pull-requests: write
actions: write
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: /usr/bin/sccache
CARGO_INCREMENTAL: 0
SCCACHE_DIR: /var/lib/github-actions/.cache
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
with:
repository: ${{ inputs.checkout_repo || github.repository }}
ref: ${{ inputs.checkout_ref || '' }}
- name: 🗑 Clear wasm-bindgen cache
run: rm -r ~/.cache/.wasm-pack || true
- name: 🟢 Install Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: 🚧 Install build dependencies
run: |
cd frontend
npm run setup
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
target: wasm32-unknown-unknown
- name: 🔀 Choose production deployment environment and insert template
id: production-env
if: github.event_name == 'push'
run: |
if [[ "${{ github.ref }}" == "refs/tags/latest-stable" ]]; then
echo "cf_project=graphite-editor" >> $GITHUB_OUTPUT
DOMAIN="editor.graphite.art"
else
echo "cf_project=graphite-dev" >> $GITHUB_OUTPUT
DOMAIN="dev.graphite.art"
fi
TEMPLATE=""
echo "template=$TEMPLATE" >> $GITHUB_OUTPUT
sed -i "s||$TEMPLATE|" frontend/index.html
- name: 🌐 Build Graphite web code
env:
NODE_ENV: production
run: mold -run cargo run build web${{ inputs.debug && ' debug' || '' }}
- name: 📤 Publish to Cloudflare Pages
id: cloudflare
continue-on-error: ${{ github.event_name != 'push' }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
if [ -z "$CLOUDFLARE_API_TOKEN" ]; then
echo "No Cloudflare API token available (fork PR), skipping deploy."
exit 0
fi
MAX_ATTEMPTS=8
DELAY=15
for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
echo "Attempt $ATTEMPT of $MAX_ATTEMPTS..."
npx wrangler@3 pages deploy "frontend/dist" --project-name="${{ steps.production-env.outputs.cf_project || 'graphite-dev' }}" --commit-dirty=true 2>&1 | tee /tmp/wrangler_output
if [ ${PIPESTATUS[0]} -eq 0 ]; then
URL=$(grep -oP 'https://[^\s]+\.pages\.dev' /tmp/wrangler_output | head -1)
echo "url=$URL" >> "$GITHUB_OUTPUT"
echo "Published successfully: $URL"
exit 0
fi
echo "Attempt $ATTEMPT failed."
if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
echo "Retrying in ${DELAY}s..."
sleep $DELAY
DELAY=$((DELAY * 2))
fi
done
echo "All $MAX_ATTEMPTS Cloudflare Pages publish attempts failed."
exit 1
- name: 🚀 Create a GitHub environment deployment
if: inputs.checkout_repo == '' || inputs.checkout_repo == github.repository
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CF_URL: ${{ steps.cloudflare.outputs.url }}
run: |
if [ -z "$CF_URL" ]; then
echo "No Cloudflare URL available, skipping deployment."
exit 0
fi
if [ "${{ github.ref }}" = "refs/tags/latest-stable" ]; then
REF="latest-stable"
ENVIRONMENT="graphite-editor (Production)"
elif [ "${{ github.event_name }}" = "push" ]; then
REF="master"
ENVIRONMENT="graphite-dev (Production)"
else
REF="${{ inputs.checkout_ref || github.head_ref || github.ref_name }}"
ENVIRONMENT="graphite-dev (Preview)"
fi
DEPLOY_ID=$(gh api \
-X POST \
-H "Accept: application/vnd.github+json" \
repos/${{ github.repository }}/deployments \
--input - \
--jq '.id' </dev/null || true)
fi
if [ -n "$PR_NUMBER" ]; then
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "$COMMENT_BODY"
else
echo "No open PR found, skipping comment."
fi
fi
- name: ✂ Strip template from completed build for a clean artifact
if: github.event_name == 'push'
env:
TEMPLATE: ${{ steps.production-env.outputs.template }}
run: sed -i "s|$TEMPLATE||" frontend/dist/index.html
- name: 📦 Upload web bundle artifact
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v6
with:
name: graphite-web-bundle
path: frontend/dist
- name: 👕 Lint Graphite web formatting
env:
NODE_ENV: production
run: |
cd frontend
npm run check
- name: 📃 Trigger website rebuild if auto-generated code docs are stale
if: github.event_name == 'push' && github.ref != 'refs/tags/latest-stable'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cargo run -p editor-message-tree -- website/generated
TREE=volunteer/guide/codebase-overview/hierarchical-message-system-tree
curl -sf "https://graphite.art/$TREE.txt" -o "website/static/$TREE.live.txt" \
&& diff -q "website/static/$TREE.txt" "website/static/$TREE.live.txt" > /dev/null \
|| gh workflow run website.yml --ref master
windows:
if: (github.event_name == 'push' && github.ref != 'refs/tags/latest-stable') || inputs.windows
runs-on: windows-latest
permissions:
contents: read
id-token: write
pull-requests: write
env:
WASM_BINDGEN_CLI_VERSION: "0.2.100"
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
with:
repository: ${{ inputs.checkout_repo || github.repository }}
ref: ${{ inputs.checkout_ref || '' }}
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
target: wasm32-unknown-unknown
- name: 💾 Set up Cargo cache
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: 🟢 Install Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: |
package-lock.json
frontend/package-lock.json
- name: 📦 Install Cargo-binstall
uses: cargo-bins/cargo-binstall@main
- name: 🚧 Install native dependencies
shell: pwsh
env:
GITHUB_TOKEN: ${{ github.token }}
BINSTALL_DISABLE_TELEMETRY: "true"
run: |
winget install --id LLVM.LLVM -e --accept-package-agreements --accept-source-agreements
winget install --id Kitware.CMake -e --accept-package-agreements --accept-source-agreements
winget install --id OpenSSL.OpenSSL -e --accept-package-agreements --accept-source-agreements
winget install --id WebAssembly.Binaryen -e --accept-package-agreements --accept-source-agreements
winget install --id GnuWin32.PkgConfig -e --accept-package-agreements --accept-source-agreements
"OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" | Out-File -FilePath $env:GITHUB_ENV -Append
"PKG_CONFIG_PATH=C:\Program Files\OpenSSL-Win64\lib\pkgconfig" | Out-File -FilePath $env:GITHUB_ENV -Append
cargo binstall --no-confirm --force wasm-pack
cargo binstall --no-confirm --force cargo-about
cargo binstall --no-confirm --force "wasm-bindgen-cli@$env:WASM_BINDGEN_CLI_VERSION"
- name: 🏗 Build Windows bundle
shell: bash # `cargo-about` refuses to run in powershell
env:
CARGO_TERM_COLOR: always
run: cargo run build desktop${{ inputs.debug && ' debug' || '' }}
- name: 📁 Stage artifacts
shell: bash
run: |
PROFILE=${{ inputs.debug && 'debug' || 'release' }}
rm -rf target/artifacts
mkdir -p target/artifacts
cp -R target/$PROFILE/Graphite target/artifacts/Graphite
- name: 📦 Upload Windows bundle
if: github.event_name != 'push'
uses: actions/upload-artifact@v6
with:
name: graphite-windows-bundle
path: target/artifacts
- name: 💬 Comment artifact link on PR
if: github.event_name != 'push'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-windows-bundle") | .id')
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID"
PR_NUMBER="${{ inputs.pr_number }}"
if [ -z "$PR_NUMBER" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
fi
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_ID" ]; then
BODY="| 📦 **Windows Build Complete for** $(git rev-parse HEAD) |"$'\n'
BODY+="|-|"$'\n'
BODY+="| [Download binary]($ARTIFACT_URL) |"
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "$BODY"
fi
- name: 🔑 Azure login
if: github.event_name == 'push'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
enable-AzPSSession: true
- name: 🔏 Sign
if: github.event_name == 'push'
uses: azure/artifact-signing-action@v1
with:
endpoint: https://eus.codesigning.azure.net/
signing-account-name: Graphite
certificate-profile-name: Graphite
files: |
${{ github.workspace }}\target\artifacts\Graphite\Graphite.exe
${{ github.workspace }}\target\artifacts\Graphite\libcef.dll
${{ github.workspace }}\target\artifacts\Graphite\chrome_elf.dll
${{ github.workspace }}\target\artifacts\Graphite\vulkan-1.dll
${{ github.workspace }}\target\artifacts\Graphite\dxcompiler.dll
${{ github.workspace }}\target\artifacts\Graphite\libEGL.dll
${{ github.workspace }}\target\artifacts\Graphite\libGLESv2.dll
${{ github.workspace }}\target\artifacts\Graphite\vk_swiftshader.dll
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
correlation-id: ${{ github.sha }}
- name: ✅ Verify signatures
if: github.event_name == 'push'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$TargetDir = "target\artifacts\Graphite"
if (-not (Test-Path $TargetDir)) {
throw "TargetDir not found: $TargetDir"
}
$UnsignedOrBad = @()
Get-ChildItem -Path $TargetDir -Recurse -File -Include *.exe,*.dll | ForEach-Object {
$sig = Get-AuthenticodeSignature -FilePath $_.FullName
if ($sig.Status -ne 'Valid') {
$UnsignedOrBad += "$($_.FullName) (Status=$($sig.Status))"
}
}
if ($UnsignedOrBad.Count -gt 0) {
Write-Host "Unsigned or invalid binaries detected:"
$UnsignedOrBad | ForEach-Object {
Write-Host "::error::$_"
}
if ($env:GITHUB_STEP_SUMMARY) {
"### ❌ Unsigned or invalid binaries detected" |
Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
"" | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
$UnsignedOrBad | ForEach-Object {
"* `$_" | Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
}
exit 1
}
Write-Host "All binaries are signed and valid."
if ($env:GITHUB_STEP_SUMMARY) {
"### ✅ All binaries are signed and valid" |
Out-File $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8
}
- name: 📦 Upload signed Windows bundle
if: github.event_name == 'push'
uses: actions/upload-artifact@v6
with:
name: graphite-windows-bundle-signed
path: target/artifacts
mac:
if: (github.event_name == 'push' && github.ref != 'refs/tags/latest-stable') || inputs.mac
runs-on: macos-latest
permissions:
contents: read
pull-requests: write
env:
WASM_BINDGEN_CLI_VERSION: "0.2.100"
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
with:
repository: ${{ inputs.checkout_repo || github.repository }}
ref: ${{ inputs.checkout_ref || '' }}
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
target: wasm32-unknown-unknown
- name: 💾 Set up Cargo cache
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: 🟢 Install Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: |
package-lock.json
frontend/package-lock.json
- name: 🚧 Install native dependencies
env:
GITHUB_TOKEN: ${{ github.token }}
BINSTALL_DISABLE_TELEMETRY: "true"
run: |
brew update
brew install \
pkg-config \
openssl@3 \
binaryen \
llvm \
cargo-binstall
echo "OPENSSL_DIR=$(brew --prefix openssl@3)" >> $GITHUB_ENV
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH
cargo binstall --no-confirm --force wasm-pack
cargo binstall --no-confirm --force cargo-about
cargo binstall --no-confirm --force "wasm-bindgen-cli@${WASM_BINDGEN_CLI_VERSION}"
- name: 🏗 Build Mac bundle
env:
CARGO_TERM_COLOR: always
run: cargo run build desktop${{ inputs.debug && ' debug' || '' }}
- name: 📁 Stage artifacts
shell: bash
run: |
PROFILE=${{ inputs.debug && 'debug' || 'release' }}
rm -rf target/artifacts
mkdir -p target/artifacts
cp -R target/$PROFILE/Graphite.app target/artifacts/Graphite.app
- name: 📦 Upload Mac bundle
if: github.event_name != 'push'
uses: actions/upload-artifact@v6
with:
name: graphite-mac-bundle
path: target/artifacts
- name: 💬 Comment artifact link on PR
if: github.event_name != 'push'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-mac-bundle") | .id')
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID"
PR_NUMBER="${{ inputs.pr_number }}"
if [ -z "$PR_NUMBER" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
fi
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_ID" ]; then
BODY="| 📦 **Mac Build Complete for** $(git rev-parse HEAD) |"$'\n'
BODY+="|-|"$'\n'
BODY+="| [Download binary]($ARTIFACT_URL) |"
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "$BODY"
fi
- name: 🔏 Sign and notarize (preparation)
if: github.event_name == 'push'
env:
APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }}
APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}
run: |
mkdir -p .sign
echo "$APPLE_CERT_BASE64" | base64 --decode > .sign/certificate.p12
security create-keychain -p "" .sign/main.keychain
security default-keychain -s .sign/main.keychain
security unlock-keychain -p "" .sign/main.keychain
security set-keychain-settings -t 3600 -u .sign/main.keychain
security import .sign/certificate.p12 -k .sign/main.keychain -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign
security set-key-partition-list -S apple-tool:,apple: -s -k "" .sign/main.keychain
cat > .sign/entitlements.plist <<'EOF'
com.apple.security.cs.allow-jit
com.apple.security.cs.allow-unsigned-executable-memory
com.apple.security.cs.disable-executable-page-protection
com.apple.security.cs.disable-library-validation
EOF
- name: 🔏 Sign and notarize
if: github.event_name == 'push'
env:
APPLE_EMAIL: ${{ secrets.APPLE_EMAIL }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_CERT_NAME: ${{ secrets.APPLE_CERT_NAME }}
run: |
CERTIFICATE="$APPLE_CERT_NAME"
ENTITLEMENTS=".sign/entitlements.plist"
APP_PATH="target/artifacts/Graphite.app"
ZIP_PATH=".sign/Graphite.zip"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper.app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper (GPU).app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Graphite Helper (Renderer).app"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libcef_sandbox.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libEGL.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libGLESv2.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/libvk_swiftshader.dylib"
codesign --force --options runtime --entitlements "$ENTITLEMENTS" --sign "$CERTIFICATE" "$APP_PATH" --deep
codesign --verify --deep --strict --verbose=4 "$APP_PATH"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
xcrun notarytool submit "$ZIP_PATH" --wait --apple-id "$APPLE_EMAIL" --team-id "$APPLE_TEAM_ID" --password "$APPLE_PASSWORD"
rm "$ZIP_PATH"
xcrun stapler staple -v "$APP_PATH"
spctl -a -vv "$APP_PATH"
- name: 📦 Upload signed Mac bundle
if: github.event_name == 'push'
uses: actions/upload-artifact@v6
with:
name: graphite-mac-bundle-signed
path: target/artifacts
linux:
if: (github.event_name == 'push' && github.ref != 'refs/tags/latest-stable') || inputs.linux
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
with:
repository: ${{ inputs.checkout_repo || github.repository }}
ref: ${{ inputs.checkout_ref || '' }}
- name: ❄ Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: 🗑 Free disk space
run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
- name: 📦 Build Nix package
run: nix build .#graphite${{ inputs.debug && '-dev' || '' }} --no-link --print-out-paths
- name: 📤 Push to Nix cache
if: (github.event_name == 'push' || inputs.push_to_nix_cache) && !inputs.debug
env:
NIX_CACHE_AUTH_TOKEN: ${{ secrets.NIX_CACHE_AUTH_TOKEN }}
run: |
nix run nixpkgs#cachix -- authtoken $NIX_CACHE_AUTH_TOKEN
nix build --no-link --print-out-paths | nix run nixpkgs#cachix -- push graphite
- name: 🏗 Build Linux bundle
run: nix build .#graphite${{ inputs.debug && '-dev' || '' }}-bundle.tar.xz && cp ./result ./graphite-linux-bundle.tar.xz
- name: 📦 Upload Linux bundle
uses: actions/upload-artifact@v6
with:
name: graphite-linux-bundle
path: graphite-linux-bundle.tar.xz
compression-level: 0
- name: 💬 Comment artifact link on PR
id: linux-comment
if: github.event_name != 'push'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-linux-bundle") | .id')
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID"
PR_NUMBER="${{ inputs.pr_number }}"
if [ -z "$PR_NUMBER" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
fi
if [ -n "$PR_NUMBER" ] && [ -n "$ARTIFACT_ID" ]; then
BODY="| 📦 **Linux Build Complete for** $(git rev-parse HEAD) |"$'\n'
BODY+="|-|"$'\n'
BODY+="| [Download binary]($ARTIFACT_URL) |"
COMMENT_ID=$(gh api repos/${{ github.repository }}/issues/$PR_NUMBER/comments -f body="$BODY" --jq '.id')
echo "comment_id=$COMMENT_ID" >> "$GITHUB_OUTPUT"
fi
- name: 🔧 Install Flatpak tooling
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- name: 🏗 Build Flatpak
run: |
nix build .#graphite${{ inputs.debug && '-dev' || '' }}-flatpak-manifest
rm -rf .flatpak
mkdir -p .flatpak
cp ./result .flatpak/manifest.json
cd .flatpak
mkdir -p repo
flatpak-builder --user --force-clean --install-deps-from=flathub --repo=repo build ./manifest.json
flatpak build-bundle repo Graphite.flatpak art.graphite.Graphite --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
- name: 📦 Upload Flatpak package
uses: actions/upload-artifact@v6
with:
name: graphite-flatpak
path: .flatpak/Graphite.flatpak
compression-level: 0
- name: 💬 Update PR comment with Flatpak artifact link
if: github.event_name != 'push' && steps.linux-comment.outputs.comment_id
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[] | select(.name == "graphite-flatpak") | .id')
ARTIFACT_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID"
COMMENT_ID="${{ steps.linux-comment.outputs.comment_id }}"
if [ -n "$ARTIFACT_ID" ]; then
EXISTING_BODY=$(gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID --jq '.body')
BODY="$EXISTING_BODY"$'\n'
BODY+="| [Download Flatpak]($ARTIFACT_URL) |"
gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID -X PATCH -f body="$BODY"
fi
================================================
FILE: .github/workflows/cargo-deny.yml
================================================
name: "Audit Security Advisories"
on:
# Run once each week
schedule:
- cron: "0 0 * * 0"
jobs:
cargo-deny:
if: github.repository == 'GraphiteEditor/Graphite' # Don't run on forks by default
runs-on: ubuntu-latest
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 🔒 Check crate security advisories for root workspace
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check advisories
- name: 🔒 Check crate security advisories for /libraries/rawkit
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check advisories
manifest-path: libraries/rawkit/Cargo.toml
================================================
FILE: .github/workflows/check.yml
================================================
name: "Check"
on:
pull_request: {}
merge_group: {}
env:
CARGO_TERM_COLOR: always
jobs:
# Check if CI can be skipped (for merge queue deduplication)
skip-check:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check.outputs.skip-check }}
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 🚦 Check if CI can be skipped
id: check
uses: cariad-tech/merge-queue-ci-skipper@cf80db21fc70244e36487acc531b3f1118889b0a
# Build the web app via the shared build workflow
build:
needs: skip-check
if: needs.skip-check.outputs.skip != 'true'
uses: ./.github/workflows/build.yml
secrets: inherit
with:
web: true
# Run the Rust tests on the self-hosted native runner
test:
needs: skip-check
if: needs.skip-check.outputs.skip != 'true'
runs-on: [self-hosted, target/native]
env:
RUSTC_WRAPPER: /usr/bin/sccache
CARGO_INCREMENTAL: 0
SCCACHE_DIR: /var/lib/github-actions/.cache
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
- name: 🦀 Fetch Rust dependencies
run: cargo fetch --locked
- name: 🧪 Run Rust tests
env:
RUSTFLAGS: -Dwarnings
run: mold -run cargo test --all-features
# Rust format check on GitHub runner
rust-fmt:
needs: skip-check
if: needs.skip-check.outputs.skip != 'true'
runs-on: ubuntu-latest
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
components: rustfmt
- name: 🔬 Check Rust formatting
run: cargo fmt --all -- --check
# License compatibility check on GitHub runner
check-licenses:
needs: skip-check
if: needs.skip-check.outputs.skip != 'true'
runs-on: ubuntu-latest
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 📜 Check crate license compatibility for root workspace
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check bans licenses sources
- name: 📜 Check crate license compatibility for /libraries/rawkit
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check bans licenses sources
manifest-path: libraries/rawkit/Cargo.toml
================================================
FILE: .github/workflows/comment-!build-commands.yml
================================================
# USAGE:
# After reviewing the code, core team members may comment on a PR with `!build` followed by optional `` and `` arguments.
# This matches the syntax of the `cargo run build` CLI command, but allows platforms to be specified.
#
# ``: `web` (default), `desktop` (all platforms), or `desktop:` (subset of `windows+mac+linux`)
# ``: `release` (default) or `debug`
#
# Examples:
# - !build
# - !build debug
# - !build desktop
# - !build desktop:windows+mac
# - !build desktop:linux debug
name: "!build PR Command"
on:
issue_comment:
types:
- created
jobs:
setup:
# Command should be limited to core team members (those in the organization) for security.
# From the GitHub Actions docs:
# author_association = 'MEMBER': Author is a member of the organization that owns the repository.
if: >
github.event.issue.pull_request &&
github.event.comment.author_association == 'MEMBER' &&
startsWith(github.event.comment.body, '!build')
runs-on: ubuntu-latest
permissions:
pull-requests: write
outputs:
repo: ${{ steps.pr_info.outputs.repo }}
ref: ${{ steps.pr_info.outputs.ref }}
web: ${{ steps.pr_info.outputs.web }}
windows: ${{ steps.pr_info.outputs.windows }}
mac: ${{ steps.pr_info.outputs.mac }}
linux: ${{ steps.pr_info.outputs.linux }}
debug: ${{ steps.pr_info.outputs.debug }}
steps:
- name: 🔎 Parse command, find branch, and set build flags
id: pr_info
run: |
COMMENT="${{ github.event.comment.body }}"
# Split into space-separated words
read -ra WORDS <<< "$COMMENT"
# First word must be "!build"
if [[ "${WORDS[0]}" != "!build" ]]; then
echo "::error::Expected comment to start with !build"
exit 1
fi
# Initialize build flags (web defaults to true, matching the CLI default target)
WEB="true"
WINDOWS="false"
MAC="false"
LINUX="false"
DEBUG="false"
# Parse target (optional, defaults to `web` if omitted)
IDX=1
case "${WORDS[$IDX]:-}" in
# Target: `web` enables just the web build (already the default, but accepted explicitly)
"web")
((IDX++)) ;;
# Target: `desktop` enables all three desktop platforms
"desktop")
WEB="false"; WINDOWS="true"; MAC="true"; LINUX="true"; ((IDX++)) ;;
# Target: `desktop:` enables a subset of desktop platforms, split by `+`
desktop:*)
WEB="false"
PLATFORMS="${WORDS[$IDX]#desktop:}"
IFS='+' read -ra PARTS <<< "$PLATFORMS"
for PART in "${PARTS[@]}"; do
case "$PART" in
"windows") WINDOWS="true" ;;
"mac") MAC="true" ;;
"linux") LINUX="true" ;;
*) echo "::error::Unrecognized platform: $PART"; exit 1 ;;
esac
done
((IDX++))
;;
esac
# Parse profile (optional, defaults to `release` if omitted)
case "${WORDS[$IDX]:-}" in
"debug") DEBUG="true"; ((IDX++)) ;;
"release") ((IDX++)) ;;
esac
# Reject any unexpected trailing words
if [[ $IDX -lt ${#WORDS[@]} ]]; then
echo "::error::Unexpected argument: ${WORDS[$IDX]}"
exit 1
fi
# Write parsed build flags to job outputs
echo "web=$WEB" >> $GITHUB_OUTPUT
echo "windows=$WINDOWS" >> $GITHUB_OUTPUT
echo "mac=$MAC" >> $GITHUB_OUTPUT
echo "linux=$LINUX" >> $GITHUB_OUTPUT
echo "debug=$DEBUG" >> $GITHUB_OUTPUT
# Fetch the PR's head branch and repo (needed for forked PRs where the code lives in another repo)
RESPONSE=$(curl -L -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})
echo "repo=$(echo $RESPONSE | jq -r '.head.repo.full_name')" >> $GITHUB_OUTPUT
echo "ref=$(echo $RESPONSE | jq -r '.head.ref')" >> $GITHUB_OUTPUT
- name: 💬 Edit comment with workflow run link
uses: actions/github-script@v8
with:
script: |
github.rest.issues.updateComment({
comment_id: ${{ github.event.comment.id }},
owner: context.repo.owner,
repo: context.repo.repo,
body: '${{ github.event.comment.body }} ([Run ID ' + context.runId + '](https://github.com/GraphiteEditor/Graphite/actions/runs/' + context.runId + '))'
});
invoke-build:
needs: setup
uses: ./.github/workflows/build.yml
secrets: inherit
with:
web: ${{ needs.setup.outputs.web == 'true' }}
windows: ${{ needs.setup.outputs.windows == 'true' }}
mac: ${{ needs.setup.outputs.mac == 'true' }}
linux: ${{ needs.setup.outputs.linux == 'true' }}
debug: ${{ needs.setup.outputs.debug == 'true' }}
checkout_repo: ${{ needs.setup.outputs.repo }}
checkout_ref: ${{ needs.setup.outputs.ref }}
pr_number: ${{ github.event.issue.number }}
================================================
FILE: .github/workflows/comment-clippy-warnings.yaml
================================================
name: "Clippy Check"
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
jobs:
clippy:
name: Run Clippy
runs-on: ubuntu-latest
# TODO(Keavon): Find a workaround (passing the output text to a separate action with permission to read the secrets?) that allows this to work on fork PRs
if: false
# if: ${{ !github.event.pull_request.draft && !github.event.pull_request.head.repo.fork }}
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: clippy
- name: Install deps
run: |
sudo apt update
sudo apt install libgtk-3-dev libsoup2.4-dev libjavascriptcoregtk-4.0-dev libwebkit2gtk-4.0-dev
- name: Run Clippy
id: clippy
run: |
# Run Clippy and filter output for the root workspace
CLIPPY_OUTPUT=$(cargo clippy --all-targets --all-features -- -W clippy::all 2>&1 | grep -vE "^(\s*Updating|\s*Download|\s*Compiling|\s*Checking|Finished)")
# Run Clippy and filter output for /libraries/rawkit
cd libraries/rawkit
CLIPPY_OUTPUT+=$'\n\n'
CLIPPY_OUTPUT+=$(cargo clippy --all-targets --all-features -- -W clippy::all 2>&1 | grep -vE "^(\s*Updating|\s*Download|\s*Compiling|\s*Checking|Finished)")
cd ../..
# Escape special characters for JSON
ESCAPED_OUTPUT=$(echo "$CLIPPY_OUTPUT" | jq -sR .)
echo "CLIPPY_OUTPUT=$ESCAPED_OUTPUT" >> $GITHUB_OUTPUT
if echo "$CLIPPY_OUTPUT" | grep -qE "^(warning|error)"; then
echo "CLIPPY_ISSUES_FOUND=true" >> $GITHUB_OUTPUT
else
echo "CLIPPY_ISSUES_FOUND=false" >> $GITHUB_OUTPUT
fi
- name: Delete previous comments
uses: actions/github-script@v8
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const botComments = comments.filter((comment) =>
comment.user.type === 'Bot' && comment.body.includes('Clippy Warnings/Errors')
);
for (const comment of botComments) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
});
}
- name: Comment PR
if: steps.clippy.outputs.CLIPPY_ISSUES_FOUND == 'true'
uses: actions/github-script@v8
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const clippy_output = ${{ steps.clippy.outputs.CLIPPY_OUTPUT }};
const output = `
Found Clippy warnings
#### Clippy Warnings/Errors
\`\`\`
${clippy_output}
\`\`\`
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
================================================
FILE: .github/workflows/comment-profiling-changes.yaml
================================================
name: "Profiling Changes"
on:
pull_request:
paths:
- "node-graph/**"
- "Cargo.toml"
- "Cargo.lock"
env:
CARGO_TERM_COLOR: always
jobs:
profile:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install Valgrind
run: |
sudo apt update
sudo apt install -y valgrind
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
# Cache on Cargo.lock file
cache-on-failure: true
- name: Cache iai-callgrind binary
id: cache-iai
uses: actions/cache@v5
with:
path: ~/.cargo/bin/iai-callgrind-runner
key: ${{ runner.os }}-iai-callgrind-runner-0.16.1
- name: Install iai-callgrind
if: steps.cache-iai.outputs.cache-hit != 'true'
run: cargo install iai-callgrind-runner@0.16.1
- name: Checkout master branch
run: |
git fetch origin master:master
git checkout master
- name: Get master commit SHA
id: master-sha
run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- name: Get CPU info
id: cpu-info
run: |
# Get CPU model and create a short hash for cache key
CPU_MODEL=$(cat /proc/cpuinfo | grep "model name" | head -1 | cut -d: -f2 | xargs)
CPU_HASH=$(echo "$CPU_MODEL" | sha256sum | cut -c1-8)
echo "cpu-hash=$CPU_HASH" >> $GITHUB_OUTPUT
echo "CPU: $CPU_MODEL (hash: $CPU_HASH)"
- name: Cache benchmark baselines
id: cache-benchmark-baselines
uses: actions/cache@v5
with:
path: target/iai
key: ${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-info.outputs.cpu-hash }}-benchmark-baselines-master-${{ steps.master-sha.outputs.sha }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-info.outputs.cpu-hash }}-benchmark-baselines-master-
- name: Run baseline benchmarks
if: steps.cache-benchmark-baselines.outputs.cache-hit != 'true'
run: |
# Compile benchmarks
cargo bench --bench compile_demo_art_iai -- --save-baseline=master
# Runtime benchmarks
cargo bench --bench update_executor_iai -- --save-baseline=master
cargo bench --bench run_once_iai -- --save-baseline=master
cargo bench --bench run_cached_iai -- --save-baseline=master
- name: Checkout PR branch
run: git checkout ${{ github.event.pull_request.head.sha }}
- name: Run PR benchmarks
run: |
# Compile benchmarks
cargo bench --bench compile_demo_art_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g' > /tmp/compile_output.json
# Runtime benchmarks
cargo bench --bench update_executor_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g' > /tmp/update_output.json
cargo bench --bench run_once_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g' > /tmp/run_once_output.json
cargo bench --bench run_cached_iai -- --baseline=master --output-format=json | jq -sc | sed 's/\\"//g' > /tmp/run_cached_output.json
- name: Make old comments collapsed by default
# Only run if we have write permissions (not a fork)
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@v8
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const { data: comments } = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const botComments = comments.filter((comment) =>
comment.user.type === 'Bot' && comment.body.includes('Performance Benchmark Results') && comment.body.includes('')
);
for (const comment of botComments) {
// Edit the comment to remove the "open" attribute from the tag
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
body: comment.body.replace('', '')
});
}
- name: Analyze profiling changes
id: analyze
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
function isSignificantChange(diffPct, absoluteChange, benchmarkType) {
const meetsPercentageThreshold = Math.abs(diffPct) > 5;
const meetsAbsoluteThreshold = absoluteChange > 200000;
const isCachedExecution = benchmarkType === 'run_cached' ||
benchmarkType.includes('Cached Execution');
return isCachedExecution
? (meetsPercentageThreshold && meetsAbsoluteThreshold)
: meetsPercentageThreshold;
}
const allOutputs = [
JSON.parse(fs.readFileSync('/tmp/compile_output.json', 'utf8')),
JSON.parse(fs.readFileSync('/tmp/update_output.json', 'utf8')),
JSON.parse(fs.readFileSync('/tmp/run_once_output.json', 'utf8')),
JSON.parse(fs.readFileSync('/tmp/run_cached_output.json', 'utf8'))
];
const outputNames = ['compile', 'update', 'run_once', 'run_cached'];
const sectionTitles = ['Compilation', 'Update', 'Run Once', 'Cached Execution'];
let hasSignificantChanges = false;
let regressionDetails = [];
for (let i = 0; i < allOutputs.length; i++) {
const benchmarkOutput = allOutputs[i];
const outputName = outputNames[i];
const sectionTitle = sectionTitles[i];
for (const benchmark of benchmarkOutput) {
if (benchmark.profiles?.[0]?.summaries?.parts?.[0]?.metrics_summary?.Callgrind?.Ir?.diffs?.diff_pct) {
const diffPct = parseFloat(benchmark.profiles[0].summaries.parts[0].metrics_summary.Callgrind.Ir.diffs.diff_pct);
const oldValue = benchmark.profiles[0].summaries.parts[0].metrics_summary.Callgrind.Ir.metrics.Both[1].Int;
const newValue = benchmark.profiles[0].summaries.parts[0].metrics_summary.Callgrind.Ir.metrics.Both[0].Int;
const absoluteChange = Math.abs(newValue - oldValue);
if (isSignificantChange(diffPct, absoluteChange, outputName)) {
hasSignificantChanges = true;
regressionDetails.push({
module_path: benchmark.module_path,
id: benchmark.id,
diffPct,
absoluteChange,
sectionTitle
});
}
}
}
}
core.setOutput('has-significant-changes', hasSignificantChanges);
core.setOutput('regression-details', JSON.stringify(regressionDetails));
- name: Comment PR
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@v8
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const fs = require('fs');
const compileOutput = JSON.parse(fs.readFileSync('/tmp/compile_output.json', 'utf8'));
const updateOutput = JSON.parse(fs.readFileSync('/tmp/update_output.json', 'utf8'));
const runOnceOutput = JSON.parse(fs.readFileSync('/tmp/run_once_output.json', 'utf8'));
const runCachedOutput = JSON.parse(fs.readFileSync('/tmp/run_cached_output.json', 'utf8'));
const hasSignificantChanges = '${{ steps.analyze.outputs.has-significant-changes }}' === 'true';
let commentBody = "";
function formatNumber(num) {
return String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function formatPercentage(pct) {
const sign = pct >= 0 ? '+' : '';
return `${sign}${pct.toFixed(2)}%`;
}
function padRight(str, len) {
return str.padEnd(len);
}
function padLeft(str, len) {
return str.padStart(len);
}
function processBenchmarkOutput(benchmarkOutput, sectionTitle, isLast = false) {
let sectionBody = "";
let hasResults = false;
let hasSignificantChanges = false;
function isSignificantChange(diffPct, absoluteChange, benchmarkType) {
const meetsPercentageThreshold = Math.abs(diffPct) > 5;
const meetsAbsoluteThreshold = absoluteChange > 200000;
const isCachedExecution = benchmarkType === 'run_cached' ||
benchmarkType.includes('Cached Execution');
return isCachedExecution
? (meetsPercentageThreshold && meetsAbsoluteThreshold)
: meetsPercentageThreshold;
}
for (const benchmark of benchmarkOutput) {
if (benchmark.profiles && benchmark.profiles.length > 0) {
const profile = benchmark.profiles[0];
if (profile.summaries && profile.summaries.parts && profile.summaries.parts.length > 0) {
const part = profile.summaries.parts[0];
if (part.metrics_summary && part.metrics_summary.Callgrind && part.metrics_summary.Callgrind.Ir) {
const irData = part.metrics_summary.Callgrind.Ir;
if (irData.diffs && irData.diffs.diff_pct !== null) {
const irDiff = {
diff_pct: parseFloat(irData.diffs.diff_pct),
old: irData.metrics.Both[1].Int,
new: irData.metrics.Both[0].Int
};
hasResults = true;
const changePercentage = formatPercentage(irDiff.diff_pct);
const color = irDiff.diff_pct > 0 ? "red" : "lime";
sectionBody += `**${benchmark.module_path} ${benchmark.id}:${benchmark.details}**\n`;
sectionBody += `Instructions: \`${formatNumber(irDiff.old)}\` (master) → \`${formatNumber(irDiff.new)}\` (HEAD) : `;
sectionBody += `$$\\color{${color}}${changePercentage.replace("%", "\\\\%")}$$\n\n`;
sectionBody += "\nDetailed metrics
\n\n```\n";
sectionBody += `Baselines: master| HEAD\n`;
for (const [metricName, metricData] of Object.entries(part.metrics_summary.Callgrind)) {
if (metricData.diffs && metricData.diffs.diff_pct !== null) {
const changePercentage = formatPercentage(parseFloat(metricData.diffs.diff_pct));
const oldValue = metricData.metrics.Both[1].Int || metricData.metrics.Both[1].Float;
const newValue = metricData.metrics.Both[0].Int || metricData.metrics.Both[0].Float;
const line = `${padRight(metricName, 20)} ${padLeft(formatNumber(Math.round(oldValue)), 11)}|${padLeft(formatNumber(Math.round(newValue)), 11)} ${padLeft(changePercentage, 15)}`;
sectionBody += `${line}\n`;
}
}
sectionBody += "```\n \n\n";
if (isSignificantChange(irDiff.diff_pct, Math.abs(irDiff.new - irDiff.old), sectionTitle)) {
significantChanges = true;
hasSignificantChanges = true;
}
}
}
}
}
}
if (hasResults) {
// Wrap section in collapsible details, open only if there are significant changes
const openAttribute = hasSignificantChanges ? " open" : "";
const ruler = isLast ? "" : "\n\n---";
return `\n${sectionTitle}
\n\n${sectionBody}${ruler}\n `;
}
return "";
}
// Process each benchmark category
const sections = [
{ output: compileOutput, title: "🔧 Graph Compilation" },
{ output: updateOutput, title: "🔄 Executor Update" },
{ output: runOnceOutput, title: "🚀 Render: Cold Execution" },
{ output: runCachedOutput, title: "⚡ Render: Cached Execution" }
];
// Generate sections and determine which ones have results
const generatedSections = sections.map(({ output, title }) =>
processBenchmarkOutput(output, title, true) // temporarily mark all as last
).filter(section => section.length > 0);
// Re-generate with correct isLast flags
let sectionIndex = 0;
const finalSections = sections.map(({ output, title }) => {
const section = processBenchmarkOutput(output, title, true); // check if it has results
if (section.length > 0) {
const isLast = sectionIndex === generatedSections.length - 1;
sectionIndex++;
return processBenchmarkOutput(output, title, isLast);
}
return "";
}).filter(section => section.length > 0);
// Combine all sections
commentBody = finalSections.join("\n\n");
if (commentBody.length > 0) {
const output = `\nPerformance Benchmark Results
\n\n${commentBody}\n `;
if (hasSignificantChanges) {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
});
} else {
console.log("No significant performance changes detected. Skipping comment.");
console.log(output);
}
} else {
console.log("No benchmark results to display.");
}
- name: Fail on significant regressions
if: steps.analyze.outputs.has-significant-changes == 'true'
uses: actions/github-script@v8
with:
script: |
const regressionDetails = JSON.parse('${{ steps.analyze.outputs.regression-details }}');
const firstRegression = regressionDetails[0];
core.setFailed(`Significant performance regression detected: ${firstRegression.module_path} ${firstRegression.id} increased by ${firstRegression.absoluteChange.toLocaleString()} instructions (${firstRegression.diffPct.toFixed(2)}%)`);
================================================
FILE: .github/workflows/library-rawkit.yml
================================================
name: "Library: Rawkit"
on:
push:
branches:
- master
paths:
- "libraries/rawkit/**"
pull_request:
paths:
- "libraries/rawkit/**"
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
CARGO_INCREMENTAL: 0
SCCACHE_DIR: /var/lib/github-actions/.cache
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 🦀 Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
cache: false
rustflags: ""
- name: 📦 Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.6
continue-on-error: true
- name: 🔧 Fallback if sccache fails
if: failure()
run: |
echo "sccache failed, disabling it"
echo "RUSTC_WRAPPER=" >> $GITHUB_ENV
- name: 🔬 Check Rust formatting
run: |
cd libraries/rawkit
cargo fmt --all -- --check
- name: 🦀 Build Rust code
run: |
cd libraries/rawkit
cargo build --release --all-features
- name: 🧪 Run Rust tests
run: |
cd libraries/rawkit
cargo test --release --all-features
- name: 📈 Run sccache stat for check
shell: bash
run: sccache --show-stats || echo "sccache stats unavailable"
================================================
FILE: .github/workflows/provide-shaders.yml
================================================
name: "Provide Shaders"
on:
push:
branches:
- master
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: ❄ Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: 💾 Set up Nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: 🏗 Build graphene raster nodes shaders
run: nix build .#graphite-raster-nodes-shaders && cp result raster_nodes_shaders_entrypoint.wgsl
- name: 📤 Upload graphene raster nodes shaders to artifacts repository
run: |
bash .github/workflows/scripts/artifact-upload.bash \
${{ vars.ARTIFACTS_REPO_OWNER }} \
${{ vars.ARTIFACTS_REPO_NAME }} \
${{ vars.ARTIFACTS_REPO_BRANCH }} \
rev/${{ github.sha }}/raster_nodes_shaders_entrypoint.wgsl \
raster_nodes_shaders_entrypoint.wgsl \
"${{ github.sha }} raster_nodes_shaders_entrypoint.wgsl" \
${{ secrets.ARTIFACTS_REPO_TOKEN }}
================================================
FILE: .github/workflows/scripts/artifact-upload.bash
================================================
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <
Arguments:
owner : GitHub user or organization of the target repo
repo : Target repo name
branch : Branch name (e.g. main)
target-path : Full path (including folders + filename) in the target repo where to upload
artifact-file : Local file path to upload
commit-message : Commit message for creating/updating the file
github-token : GitHub token (PAT or equivalent) with write access to the target repo
This will perform a GitHub API PUT to /repos/{owner}/{repo}/contents/{target-path}.
If a file already exists at that path, it will auto-detect the SHA and update; otherwise it will create a new one.
EOF
exit 1
}
if [ $# -ne 7 ]; then
usage
fi
OWNER="$1"
REPO="$2"
BRANCH="$3"
TARGET_PATH="$4"
ARTIFACT_PATH="$5"
COMMIT_MSG="$6"
TOKEN="$7"
if [ ! -f "$ARTIFACT_PATH" ]; then
echo "Error: artifact file not found: $ARTIFACT_PATH" >&2
exit 1
fi
LOCAL_SHA=$(git hash-object "$ARTIFACT_PATH")
echo "Local blob SHA: $LOCAL_SHA"
GET_URL="https://api.github.com/repos/${OWNER}/${REPO}/contents/${TARGET_PATH}?ref=${BRANCH}"
GET_RESPONSE=$(curl -s -H "Authorization: token ${TOKEN}" "$GET_URL")
REMOTE_SHA=$(echo "$GET_RESPONSE" | jq -r .sha 2>/dev/null || echo "")
if [ "$REMOTE_SHA" != "null" ] && [ -n "$REMOTE_SHA" ]; then
echo "Remote blob SHA: $REMOTE_SHA"
if [ "$LOCAL_SHA" = "$REMOTE_SHA" ]; then
echo "The remote file is identical. Skipping upload."
exit 0
else
echo "Remote file differs. Preparing to upload."
fi
else
echo "No existing remote file or no SHA found. Creating."
fi
CONTENT_TMP_BASE64=$(mktemp)
if base64 --help 2>&1 | grep -q -- "-w"; then
base64 -w 0 "$ARTIFACT_PATH" > "$CONTENT_TMP_BASE64"
else
base64 "$ARTIFACT_PATH" | tr -d '\n' > "$CONTENT_TMP_BASE64"
fi
PAYLOAD_TMP=$(mktemp)
jq -n \
--arg message "$COMMIT_MSG" \
--arg branch "$BRANCH" \
--arg sha "$REMOTE_SHA" \
--rawfile content "$CONTENT_TMP_BASE64" \
'{
message: $message,
content: $content,
branch: $branch
} + (if ($sha != "" and $sha != "null") then { sha: $sha } else {} end)' \
> "$PAYLOAD_TMP"
UPLOAD_RESPONSE=$(curl -s -X PUT \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d @"$PAYLOAD_TMP" \
"https://api.github.com/repos/${OWNER}/${REPO}/contents/${TARGET_PATH}")
echo "Upload Response:"
echo "$UPLOAD_RESPONSE"
rm -f "$CONTENT_TMP_BASE64" "$PAYLOAD_TMP"
================================================
FILE: .github/workflows/website.yml
================================================
name: "Website"
on:
workflow_dispatch: {}
push:
branches:
- master
paths:
- website/**
pull_request:
paths:
- website/**
env:
CARGO_TERM_COLOR: always
INDEX_HTML_HEAD_INCLUSION:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
pull-requests: write
steps:
- name: 📥 Clone repository
uses: actions/checkout@v6
- name: 🟢 Install Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: 🕸 Install Zola
uses: taiki-e/install-action@v2
with:
tool: zola@0.22.0
- name: 🔍 Check if `website/other` directory changed
uses: dorny/paths-filter@v3
id: changes
with:
filters: |
website-other:
- "website/other/**"
- name: ✂ Replace template in of index.html
run: |
# Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)
git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_INCLUSION=""
- name: 🦀 Produce auto-generated code docs data
run: |
rustup update stable
cargo run -p crate-hierarchy-viz -- website/generated
cargo run -p editor-message-tree -- website/generated
- name: 🔧 Install website npm dependencies
run: |
cd website
npm ci
- name: 📃 Generate node catalog documentation
run: cargo run -p node-docs -- website/content/learn/node-catalog
- name: 🌐 Build Graphite website with Zola
env:
MODE: prod
run: |
cd website
npm run check
zola --config config.toml build --minify
- name: 📤 Publish to Cloudflare Pages
continue-on-error: true
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npx wrangler@3 pages deploy "website/public" --project-name="graphite-website" --commit-dirty=true
================================================
FILE: .gitignore
================================================
branding/
target/
third-party-licenses.txt*
result/
.flatpak-builder/
*.spv
*.exrc
perf.data*
profile.json
profile.json.gz
flamegraph.svg
.idea/
.direnv
.DS_Store
================================================
FILE: .nix/default.nix
================================================
inputs:
let
systems = [
"x86_64-linux"
"aarch64-linux"
];
forAllSystems = f: inputs.nixpkgs.lib.genAttrs systems (system: f system);
args =
system:
(
let
lib = inputs.nixpkgs.lib // {
call = p: import p args;
};
pkgs = import inputs.nixpkgs {
inherit system;
overlays = [ (import inputs.rust-overlay) ];
};
info = {
pname = "graphite";
version = "unstable";
src = inputs.nixpkgs.lib.cleanSourceWith {
src = ./..;
filter = path: type: !(type == "directory" && builtins.baseNameOf path == ".nix");
};
cargoVendored = deps.crane.lib.vendorCargoDeps { inherit (info) src; };
};
deps = {
crane = lib.call ./deps/crane.nix;
cef = lib.call ./deps/cef.nix;
rustGPU = lib.call ./deps/rust-gpu.nix;
};
args = {
inherit system;
inherit (inputs) self;
inherit inputs;
inherit pkgs;
inherit lib;
inherit info;
inherit deps;
}
// inputs;
in
args
);
withArgs = f: forAllSystems (system: f (args system));
in
{
packages = withArgs (
{ lib, ... }:
rec {
default = graphite;
graphite = (lib.call ./pkgs/graphite.nix) { };
graphite-dev = (lib.call ./pkgs/graphite.nix) { dev = true; };
graphite-raster-nodes-shaders = lib.call ./pkgs/graphite-raster-nodes-shaders.nix;
graphite-branding = lib.call ./pkgs/graphite-branding.nix;
graphite-bundle = (lib.call ./pkgs/graphite-bundle.nix) { };
graphite-dev-bundle = (lib.call ./pkgs/graphite-bundle.nix) { graphite = graphite-dev; };
graphite-flatpak-manifest = (lib.call ./pkgs/graphite-flatpak-manifest.nix) { };
graphite-dev-flatpak-manifest = (lib.call ./pkgs/graphite-flatpak-manifest.nix) { graphite-bundle = graphite-dev-bundle; };
# TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix;
tools = {
third-party-licenses = lib.call ./pkgs/tools/third-party-licenses.nix;
};
}
);
devShells = withArgs (
{ lib, ... }:
{
default = lib.call ./dev.nix;
}
);
formatter = withArgs ({ pkgs, ... }: pkgs.nixfmt-tree);
}
================================================
FILE: .nix/deps/cef.nix
================================================
{ pkgs, ... }:
let
cefPath = pkgs.cef-binary.overrideAttrs (finalAttrs: {
postInstall = ''
rm -r $out/* $out/.* || true
strip ./Release/*.so*
mv ./Release/* $out/
find "./Resources/locales" -maxdepth 1 -type f ! -name 'en-US.pak' -delete
mv ./Resources/* $out/
mv ./include $out/
cat ./CREDITS.html | ${pkgs.xz}/bin/xz -9 -e -c > $out/CREDITS.html.xz
echo '${
builtins.toJSON {
type = "minimal";
name = builtins.baseNameOf finalAttrs.src.url;
sha1 = "";
}
}' > $out/archive.json
'';
});
in
{
env.CEF_PATH = cefPath;
}
================================================
FILE: .nix/deps/crane.nix
================================================
{ pkgs, inputs, ... }:
{
lib = inputs.crane.mkLib pkgs;
}
================================================
FILE: .nix/deps/rust-gpu.nix
================================================
{ pkgs, ... }:
let
extensions = [
"rust-src"
"rust-analyzer"
"clippy"
"cargo"
"rustc-dev"
"llvm-tools"
];
toolchain = pkgs.rust-bin.nightly."2025-06-23".default.override {
inherit extensions;
};
cargo = pkgs.writeShellScriptBin "cargo" ''
#!${pkgs.lib.getExe pkgs.bash}
filtered_args=()
for arg in "$@"; do
case "$arg" in
+nightly|+nightly-*) ;;
*) filtered_args+=("$arg") ;;
esac
done
exec ${toolchain}/bin/cargo ${"\${filtered_args[@]}"}
'';
rustc_codegen_spirv =
(pkgs.makeRustPlatform {
cargo = toolchain;
rustc = toolchain;
}).buildRustPackage
(finalAttrs: {
pname = "rustc_codegen_spirv";
version = "0-unstable-2025-08-04";
src = pkgs.fetchFromGitHub {
owner = "Firestar99";
repo = "rust-gpu-new";
rev = "c12f216121820580731440ee79ebc7403d6ea04f";
hash = "sha256-rG1cZvOV0vYb1dETOzzbJ0asYdE039UZImobXZfKIno=";
};
cargoHash = "sha256-AEigcEc5wiBd3zLqWN/2HSbkfOVFneAqNvg9HsouZf4=";
cargoBuildFlags = [
"-p"
"rustc_codegen_spirv"
"--features=use-compiled-tools"
"--no-default-features"
];
doCheck = false;
});
in
{
toolchain = toolchain;
env = {
RUST_GPU_PATH_OVERRIDE = "${cargo}/bin:${toolchain}/bin";
RUSTC_CODEGEN_SPIRV_PATH = "${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so";
};
}
================================================
FILE: .nix/dev.nix
================================================
{ pkgs, deps, ... }:
let
libs = [
pkgs.wayland
pkgs.vulkan-loader
pkgs.libGL
pkgs.openssl
pkgs.libraw
# X11 Support
pkgs.libxkbcommon
pkgs.libXcursor
pkgs.libxcb
pkgs.libX11
];
in
pkgs.mkShell (
{
packages = libs ++ [
pkgs.pkg-config
pkgs.lld
pkgs.nodejs
pkgs.nodePackages.npm
pkgs.binaryen
pkgs.wasm-bindgen-cli_0_2_100
pkgs.wasm-pack
pkgs.cargo-about
pkgs.rustc
pkgs.cargo
pkgs.rust-analyzer
pkgs.clippy
pkgs.rustfmt
pkgs.git
pkgs.cargo-watch
pkgs.cargo-nextest
pkgs.cargo-expand
# Linker
pkgs.mold
# Profiling tools
pkgs.gnuplot
pkgs.samply
pkgs.cargo-flamegraph
# Plotting tools
pkgs.graphviz
];
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath libs}:${deps.cef.env.CEF_PATH}";
XDG_DATA_DIRS = "${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS";
shellHook = ''
alias cargo='mold --run cargo'
'';
}
// deps.cef.env
// deps.rustGPU.env
)
================================================
FILE: .nix/pkgs/graphite-branding.nix
================================================
{ info, pkgs, ... }:
let
brandingTar = pkgs.fetchurl (
let
lockContent = builtins.readFile "${info.src}/.branding";
lines = builtins.filter (s: s != [ ]) (builtins.split "\n" lockContent);
url = builtins.elemAt lines 0;
hash = builtins.elemAt lines 1;
in
{
url = url;
sha256 = hash;
}
);
in
pkgs.runCommand "${info.pname}-branding" { } ''
mkdir -p $out
tar -xvf ${brandingTar} -C $out --strip-components 1
''
================================================
FILE: .nix/pkgs/graphite-bundle.nix
================================================
{
pkgs,
self,
system,
...
}:
{
graphite ? self.packages.${system}.graphite,
}:
let
bundle =
{
archive ? false,
compression ? null,
passthru ? { },
}:
(
let
tar = if compression == null then archive else true;
nameArchiveSuffix = if tar then ".tar" else "";
nameCompressionSuffix = if compression == null then "" else "." + compression;
name = "graphite-bundle${nameArchiveSuffix}${nameCompressionSuffix}";
build = ''
mkdir -p out
mkdir -p out/bin
cp ${graphite}/bin/graphite out/bin/graphite
chmod -v +w out/bin/graphite
patchelf \
--set-rpath '$ORIGIN/../lib:$ORIGIN/../lib/cef' \
--set-interpreter '/lib64/ld-linux-x86-64.so.2' \
--remove-needed libGL.so \
out/bin/graphite
cp -r ${graphite}/share out/share
mkdir -p out/lib/cef
mkdir -p ./cef
tar -xvf ${pkgs.cef-binary.src} -C ./cef --strip-components=1
cp -r ./cef/Release/* out/lib/cef/
cp -r ./cef/Resources/* out/lib/cef/
find "out/lib/cef/locales" -type f ! -name 'en-US*' -delete
${pkgs.bintools}/bin/strip out/lib/cef/*.so*
'';
install =
if tar then
''
cd out
tar -c \
--sort=name \
--mtime='@1' --clamp-mtime \
--owner=0 --group=0 --numeric-owner \
--mode='u=rwX,go=rX' \
--format=posix \
--pax-option=delete=atime,delete=ctime \
--no-acls --no-xattrs --no-selinux \
* ${
if compression == "xz" then
"| xz "
else if compression == "gz" then
"| gzip -n "
else
""
}> $out
''
else
''
mkdir -p $out
cp -r out/* $out/
'';
in
pkgs.runCommand name
{
inherit passthru;
}
''
${build}
${install}
''
);
in
bundle {
passthru = {
tar = bundle {
archive = true;
passthru = {
gz = bundle {
compression = "gz";
};
xz = bundle {
compression = "xz";
};
};
};
};
}
================================================
FILE: .nix/pkgs/graphite-flatpak-manifest.nix
================================================
{
pkgs,
self,
system,
...
}:
{
graphite-bundle ? self.packages.${system}.graphite-bundle,
}:
(pkgs.formats.json { }).generate "art.graphite.Graphite.json" {
app-id = "art.graphite.Graphite";
runtime = "org.freedesktop.Platform";
runtime-version = "25.08";
sdk = "org.freedesktop.Sdk";
command = "graphite";
finish-args = [
"--device=dri"
"--share=ipc"
"--socket=wayland"
"--socket=fallback-x11"
"--share=network"
];
modules = [
{
name = "app";
buildsystem = "simple";
build-commands = [
"mkdir -p /app"
"cp -r ./* /app/"
"chmod +x /app/bin/*"
];
sources = [
{
type = "archive";
path = graphite-bundle.tar;
strip-components = 0;
}
];
}
];
}
================================================
FILE: .nix/pkgs/graphite-raster-nodes-shaders.nix
================================================
{ info, deps, ... }:
(deps.crane.lib.overrideToolchain (_: deps.rustGPU.toolchain)).buildPackage {
pname = "raster-nodes-shaders";
inherit (info) version src;
cargoVendorDir = deps.crane.lib.vendorMultipleCargoDeps {
inherit (deps.crane.lib.findCargoFiles (deps.crane.lib.cleanCargoSource info.src)) cargoConfigs;
cargoLockList = [
"${info.src}/Cargo.lock"
"${deps.rustGPU.toolchain.availableComponents.rust-src}/lib/rustlib/src/rust/library/Cargo.lock"
];
};
strictDeps = true;
env = deps.rustGPU.env;
buildPhase = ''
cargo build -r -p raster-nodes-shaders
'';
installPhase = ''
cp target/spirv-builder/spirv-unknown-naga-wgsl/release/deps/raster_nodes_shaders_entrypoint.wgsl $out
'';
doCheck = false;
}
================================================
FILE: .nix/pkgs/graphite.nix
================================================
{
info,
pkgs,
self,
deps,
system,
lib,
...
}:
{
dev ? false,
}:
let
branding = self.packages.${system}.graphite-branding;
cargoVendorDir = deps.crane.lib.vendorCargoDeps { inherit (info) src; };
resourcesCommon = {
pname = "${info.pname}-resources";
inherit (info) version src;
inherit cargoVendorDir;
strictDeps = true;
nativeBuildInputs = [
pkgs.pkg-config
pkgs.lld
pkgs.nodejs
pkgs.nodePackages.npm
pkgs.binaryen
pkgs.wasm-bindgen-cli_0_2_100
pkgs.wasm-pack
pkgs.cargo-about
];
buildInputs = [ pkgs.openssl ];
env.CARGO_PROFILE = if dev then "dev" else "release";
cargoExtraArgs = "--target wasm32-unknown-unknown -p graphite-wasm --no-default-features --features native";
doCheck = false;
};
resources = deps.crane.lib.buildPackage (
resourcesCommon
// {
cargoArtifacts = deps.crane.lib.buildDepsOnly resourcesCommon;
npmDeps = pkgs.importNpmLock {
npmRoot = "${info.src}/frontend";
};
npmRoot = "frontend";
npmConfigScript = "setup";
makeCacheWritable = true;
nativeBuildInputs = [
pkgs.importNpmLock.npmConfigHook
pkgs.removeReferencesTo
]
++ resourcesCommon.nativeBuildInputs;
prePatch = ''
mkdir branding
cp -r ${branding}/* branding
cp ${info.src}/.branding branding/.branding
'';
buildPhase = ''
export HOME="$TMPDIR"
pushd frontend
npm run native:build-${if dev then "dev" else "production"}
popd
'';
installPhase = ''
mkdir -p $out
cp -r frontend/dist/* $out/
'';
postFixup = ''
find "$out" -type f -exec remove-references-to -t "${cargoVendorDir}" '{}' +
'';
}
);
libs = [
pkgs.wayland
pkgs.vulkan-loader
pkgs.libGL
pkgs.openssl
pkgs.libraw
# X11 Support
pkgs.libxkbcommon
pkgs.libXcursor
pkgs.libxcb
pkgs.libX11
];
common = {
inherit (info) pname version src;
inherit cargoVendorDir;
strictDeps = true;
buildInputs = libs;
nativeBuildInputs = [
pkgs.pkg-config
pkgs.cargo-about
pkgs.removeReferencesTo
];
env = deps.cef.env // {
CARGO_PROFILE = if dev then "dev" else "release";
};
cargoExtraArgs = "-p graphite-desktop";
doCheck = false;
};
in
deps.crane.lib.buildPackage (
common
// {
cargoArtifacts = deps.crane.lib.buildDepsOnly common;
env = common.env // {
RASTER_NODES_SHADER_PATH = self.packages.${system}.graphite-raster-nodes-shaders;
EMBEDDED_RESOURCES = resources;
GRAPHITE_GIT_COMMIT_HASH = self.rev or "unknown";
GRAPHITE_GIT_COMMIT_DATE = self.lastModified or "unknown";
};
npmDeps = pkgs.importNpmLock {
npmRoot = "${info.src}/frontend";
};
npmRoot = "frontend";
nativeBuildInputs = [
pkgs.importNpmLock.npmConfigHook
pkgs.nodePackages.npm
]
++ common.nativeBuildInputs;
preBuild = ''
${lib.getExe self.packages.${system}.tools.third-party-licenses}
''
+ (
if self ? rev then
''
export GRAPHITE_GIT_COMMIT_DATE="$(date -u -d "@$GRAPHITE_GIT_COMMIT_DATE" +"%Y-%m-%dT%H:%M:%SZ")"
''
else
""
);
installPhase = ''
mkdir -p $out/bin
cp target/${if dev then "debug" else "release"}/graphite $out/bin/graphite
mkdir -p $out/share/applications
cp $src/desktop/assets/*.desktop $out/share/applications/
mkdir -p $out/share/icons/hicolor/scalable/apps
cp ${branding}/app-icons/graphite.svg $out/share/icons/hicolor/scalable/apps/art.graphite.Graphite.svg
mkdir -p $out/share/icons/hicolor/512x512/apps
cp ${branding}/app-icons/graphite-512.png $out/share/icons/hicolor/512x512/apps/art.graphite.Graphite.png
mkdir -p $out/share/icons/hicolor/256x256/apps
cp ${branding}/app-icons/graphite-256.png $out/share/icons/hicolor/256x256/apps/art.graphite.Graphite.png
mkdir -p $out/share/icons/hicolor/128x128/apps
cp ${branding}/app-icons/graphite-128.png $out/share/icons/hicolor/128x128/apps/art.graphite.Graphite.png
'';
postFixup = ''
remove-references-to -t "${cargoVendorDir}" $out/bin/graphite
patchelf \
--set-rpath "${pkgs.lib.makeLibraryPath libs}:${deps.cef.env.CEF_PATH}" \
--add-needed libGL.so \
$out/bin/graphite
'';
}
)
================================================
FILE: .nix/pkgs/tools/third-party-licenses.nix
================================================
{
info,
deps,
pkgs,
...
}:
let
cargoVendorDir = deps.crane.lib.vendorCargoDeps { inherit (info) src; };
common = {
pname = "third-party-licenses";
inherit (info) version src;
inherit cargoVendorDir;
nativeBuildInputs = [ pkgs.pkg-config ];
buildInputs = [ pkgs.openssl ];
strictDeps = true;
env = deps.cef.env // {
CARGO_PROFILE = "dev";
};
cargoExtraArgs = "-p third-party-licenses --features desktop";
doCheck = false;
};
in
deps.crane.lib.buildPackage (
common
// {
inherit cargoVendorDir;
cargoArtifacts = deps.crane.lib.buildDepsOnly common;
meta.mainProgram = "third-party-licenses";
}
)
================================================
FILE: .nvmrc
================================================
24
================================================
FILE: .prettierrc
================================================
{
"singleQuote": false,
"useTabs": true,
"tabWidth": 4,
"printWidth": 200,
"overrides": [
{
"files": [
"*.yml",
"*.yaml"
],
"options": {
"useTabs": false,
"tabWidth": 2
}
}
]
}
================================================
FILE: .vscode/extensions.json
================================================
{
// NOTE: Keep this in sync with `.devcontainer/devcontainer.json`
"recommendations": [
// Rust
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
// Web
"dbaeumer.vscode-eslint",
"svelte.svelte-vscode",
"vitaliymaz.vscode-svg-previewer",
// Code quality
"wayou.vscode-todo-highlight",
"streetsidesoftware.code-spell-checker",
// Git
"mhutchie.git-graph",
"qezhu.gitlink",
// Helpful
"wmaurer.change-case"
]
}
================================================
FILE: .vscode/launch.json
================================================
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Graphite debug executable",
"cargo": {
"args": [
"build",
"--bin=graphite",
"--package=graphite",
],
"filter": {
"name": "graphite",
"kind": "bin",
},
},
"args": [],
"cwd": "${workspaceFolder}",
"env": {
"RUST_LOG": "error",
},
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'graphite'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=graphite",
"--package=graphite",
],
"filter": {
"name": "graphite",
"kind": "bin",
},
},
"args": [],
"cwd": "${workspaceFolder}",
},
],
}
================================================
FILE: .vscode/settings.json
================================================
{
// Rust: save on format
"[rust]": {
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.defaultFormatter": "rust-lang.rust-analyzer"
},
// Web: save on format
"[javascript][typescript][svelte]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[scss]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.formatOnSave": true,
// Configured in `.prettierrc`
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json][jsonc][yaml][github-actions-workflow]": {
"editor.formatOnSave": true,
// Configured in `.prettierrc`
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Website: don't format Zola/Tera-templated HTML on save
"[html]": {
"editor.formatOnSave": false
},
// Handlebars: don't save on format
// (`about.hbs` is used by Cargo About to encode license information)
"[handlebars]": {
"editor.formatOnSave": false
},
// Rust Analyzer config
"rust-analyzer.check.command": "clippy",
"rust-analyzer.cargo.allTargets": false,
"rust-analyzer.procMacro.ignored": {
"serde_derive": ["Serialize", "Deserialize"]
},
// ESLint config
"eslint.format.enable": true,
"eslint.workingDirectories": ["./frontend", "./website"],
"eslint.validate": ["javascript", "typescript", "svelte"],
// Git Graph config
"git-graph.repository.fetchAndPrune": true,
"git-graph.repository.showRemoteHeads": false,
"git-graph.repository.commits.fetchAvatars": true,
// VS Code Git config
"git.autofetch": true,
"git.enableStatusBarSync": false,
"git.showActionButton": {
"sync": false
},
// CSpell config
"cSpell.language": "en-US",
"cSpell.logLevel": "Information",
"cSpell.allowCompoundWords": true,
// Other extensions config
"evenBetterToml.formatter.alignComments": false,
"package-json-upgrade.ignorePatterns": ["source-sans-pro"],
// VS Code config
"html.format.wrapLineLength": 200,
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.associations": {
"*.graphite": "json"
},
"editor.renderWhitespace": "boundary",
"editor.minimap.markSectionHeaderRegex": "// ===+\\n\\s*//\\s*(?