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*(?