feat(penpot): add Penpot as alternative design source#76
Conversation
Implement Penpot as a second design data source alongside Figma: - New PenpotAPI module: HTTP client for Penpot RPC API with endpoint protocol, retry logic, and response models (colors, components, typographies) supporting dual String/Double decoding - PenpotColorsSource, PenpotComponentsSource, PenpotTypographySource wired into SourceFactory for seamless dispatch via sourceKind - PenpotColorsConfig added to ColorsSourceConfig protocol family - PenpotSource class in Common.pkl with auto-detection (penpotSource set → sourceKind "penpot") and codegen - Entry bridge methods updated to use resolvedSourceKind across all four platform plugins - 21 unit tests for model decoding, endpoint construction, and error recovery suggestions
…type safety issues - Fix operator precedence bug in PenpotComponentsSource URL construction - Use configurable base URL from penpotSource instead of hardcoded design.penpot.app - Add resolvedFileId/resolvedPenpotBaseURL computed properties to Common_FrameSource - Add penpotBaseURL field to IconsSourceInput, ImagesSourceInput, TypographySourceInput - Replace JSONSerialization with Codable+CodingKeys+YYJSONEncoder in thumbnails endpoint - Validate fileId before API calls instead of falling back to empty string - Add ColorsConfigError.missingPenpotSource for correct error messaging - Return nil from hexToRGBA on invalid hex with warning instead of silent black - Handle CancellationError in retry loop to respect cooperative cancellation - Include response body in download error messages for diagnostics - Add preconditions for maxRetries and accessToken in BasePenpotClient - Change PenpotTypography numeric fields from var to let (immutable)
…ory dispatch, and fileId validation - Add FrameSourceResolvedSourceKindTests: auto-detect penpot, explicit override, resolvedFileId priority, resolvedPenpotBaseURL - Add PenpotColorsSourceInputTests: auto-detected penpot config, missingPenpotSource error - Add HexToRGBATests: valid/invalid hex, opacity, whitespace, lowercase - Add SourceFactoryPenpotTests: .penpot dispatch for all 3 source types + unsupported throws - Add PenpotComponentsSourceValidationTests: nil/empty fileId throws descriptive error - Extract Penpot tests from DesignSourceTests.swift to stay under file_length limit
…ch command
- GetProfileEndpoint now sends `{}` body instead of nil (Penpot returns
400 "malformed-json" for empty body)
- Resolve duplicate --timeout flag conflict in `exfig fetch` by inlining
HeavyFaultToleranceOptions fields and constructing via computed property
- Document API path difference (/api/main/methods/ vs /api/rpc/command/)
and Cloudflare behavior in PenpotAPI CLAUDE.md
- Add E2E test data documentation (README.md) for Penpot test file
- Exclude README.md from PenpotAPITests SPM resources
…mpt enhancements - Expand DesignRequirements.md with Penpot section: library colors, components, typography, file organization, limitations, troubleshooting - Add MCP guide resource (exfig://guides/DesignRequirements.md) served from Resources/Guides/ since DocC articles aren't in Bundle.module - Add `source` argument to setup-config MCP prompt (figma/penpot branches) - Update troubleshoot-export prompt to check both auth tokens - Add Penpot typography config example to Configuration.md - Regenerate llms-full.txt with updated DocC content - Update CLAUDE.md with timeout, DocC bundle, and Penpot API gotchas
- Add WizardDesignSource enum — first wizard question is now "Figma or Penpot?" - InitWizard Penpot flow: file UUID, base URL, path filters (no dark mode/variables) - FetchWizard Penpot flow: file UUID, path filter, output directory - Add extractPenpotFileId() for Penpot workspace URL parsing (file-id= query param) - Add applyPenpotResult() in InitWizardTransform — removes figma section, inserts penpotSource blocks into platform entries - Add runPenpotFetch() in DownloadImages — downloads component thumbnails via PenpotAPI - Update GenerateConfigFile to show PENPOT_ACCESS_TOKEN in next-steps for Penpot source - Update test helpers with new InitWizardResult fields (designSource, penpotBaseURL)
…sing tests - Fix download() URL mangling by detecting absolute URLs - Fix retry loop returning error response on final attempt (now throws) - Fix asset download auth conflict with S3 presigned URLs (no Authorization header) - Fix asset path: assets/by-id/ instead of assets/by-file-media-id/ - Extract PenpotClientFactory from PenpotColorsSource for shared usage - Sort dictionary iteration for deterministic export order - Replace force-unwrap wizardResult! with safe binding - Wire iOS PluginIconsExport through SourceFactory for Penpot dispatch - Add recovery suggestions for 500-level and network errors - Wrap decoding errors with HTTP context (endpoint, status code) - Add baseURL validation precondition in BasePenpotClient.init - Add precondition(!fileId.isEmpty) to GetFileEndpoint - Add warnings for empty API responses, unknown text transforms - Remove unused mainInstanceId/mainInstancePage from PenpotComponent - Fix doc comments (PenpotEndpoint, GetProfileEndpoint, PenpotTypography) - Fix DesignTokens.md false Penpot claim, CLAUDE.md module count - Add tests: applyPenpotResult, extractPenpotFileId, VariablesSource.resolvedSourceKind
… to fetch ExFigOptions.validate() no longer throws when FIGMA_PERSONAL_TOKEN is missing. Token is read lazily and only required when a Figma source is actually used. SourceFactory guards the .figma branch and throws accessTokenNotFound there. Added --source penpot/figma flag and --penpot-base-url to exfig fetch for non-interactive Penpot usage without the wizard.
…xport Replace thumbnail-based component export with SVG reconstruction from Penpot's shape tree data. This enables vector export (SVG/PNG/PDF) at any scale without headless Chrome or CDN — shapes are parsed directly from the get-file API response. - Add PenpotShape model for shape tree decoding (path, rect, circle, bool, group, frame) - Add PenpotShapeRenderer — pure function that converts shape tree → SVG string - Add PenpotPage/SVGAttributes models, extend PenpotFileData with pagesIndex - Rewrite PenpotComponentsSource to use SVG reconstruction instead of thumbnails - Update runPenpotFetch to support --format svg/png with SVG→PNG via resvg - Wire SourceFactory through all remaining platform exports (Android/Flutter/Web icons + all images) - Restore mainInstanceId/mainInstancePage on PenpotComponent for shape tree lookup - Handle mixed-type svgAttrs (strings + nested style dicts) via SVGAttributes wrapper
… limitation - Remove "v1 limitation: raster thumbnails only" from DesignRequirements, Configuration, MCP prompts - Add Penpot icons PKL config example to Configuration.md - Add "Penpot Fetch" section to Usage.md with --source penpot examples - Add "Quick Penpot Icons Export" to GettingStarted.md - Update MCP prompt: "SVG reconstructed from shape tree" replaces raster limitation - Update Known Limitations: add SVG reconstruction scope (no blur/shadow/gradients) - Regenerate llms-full.txt
Penpot SVG reconstruction writes SVGs to temp files and passes file:// URLs through the download pipeline. FileDownloader now skips HTTP download for file:// URLs and returns the local path directly.
…kening validation Move file:// URL handling from validateDownloadURL (security gate) to fetch() method — file:// URLs are filtered as local files before the download loop, keeping validateDownloadURL strictly HTTPS-only.
…oss Penpot support
Replace placeholder FigmaClient("no-token") with NoTokenFigmaClient that
throws accessTokenNotFound on any call — prevents silent HTTP requests with
invalid credentials. Add ShapeType enum replacing stringly-typed shape
dispatch, MainInstance struct enforcing paired id+page invariant, and
structured RenderResult/RenderFailure for SVG reconstruction diagnostics.
Fix SVG arc command normalization to correctly offset only endpoint
coordinates (params 5-6 of 7-param groups). Add warnings for skipped
gradient colors, empty path filter results, and unknown shape types.
Remove force unwrap in FileDownloader, fix inverted letterSpacing warning
condition, and throw error for unsupported Penpot export formats instead
of silently saving SVG.
Includes 9 new tests covering nested groups, rotation transforms, arc
normalization, relative path commands, ShapeType decoding, and
RenderFailure diagnostics.
Aligns deploy-docc.yml trigger with release.yml so both workflows run in parallel when a version tag is pushed.
Switch from patch-file to none to avoid stash overhead during commits.
…t-api Move the standalone PenpotAPI module to DesignPipe/swift-penpot-api (v0.1.0), mirroring the swift-figma-api extraction pattern. No code changes in consumer files — `import PenpotAPI` continues to work via the external SPM dependency. Also fix pre-existing test failure in ExFigErrorFormatterTests where the assertion text didn't match the updated accessTokenNotFound error message.
- Development.md: ExFig/ → ExFigCLI/, expand module list from 7 to 12, add Source/, MCP/, JinjaSupport, WebExport, update test targets, replace inline FigmaAPI guide with link to external package - ARCHITECTURE.md: ExFig (CLI) → ExFigCLI, add External Packages section (FigmaAPI, PenpotAPI, SVGKit), Stencil → Jinja2, update file structure - PKL.md, MIGRATION.md: niceplaces → DesignPipe, fix Schemas path Sources/ExFig/ → Sources/ExFigCLI/
Move ARCHITECTURE.md, PKL.md, MIGRATION.md from docs/ (which conflicts with DocC output directory) into Sources/ExFigCLI/ExFig.docc/. Move app-release-secrets.md to .github/. Update cross-references to use DocC links and hosted documentation URLs.
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces support for Penpot as a design source alongside Figma, enhancing ExFig's versatility and allowing users to export design assets from either platform. It includes significant architectural changes, such as externalizing the Penpot API and adding new PKL schemas, to accommodate Penpot integration while maintaining a clean and modular codebase. The changes also improve the CLI tools and documentation, providing a more seamless experience for users working with both Figma and Penpot. Highlights
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
The pull request introduces support for Penpot as a design source, alongside Figma and TokensFile. This includes adding a new PenpotAPI module, implementing Penpot-specific sources and configurations, updating the PKL schema, and modifying the CLI to handle Penpot authentication and file retrieval. The code changes also include error handling for missing Figma tokens and updates to documentation and build configurations. The code review identified several areas for improvement, including the need to handle potential errors when requiring the Figma token, updating comments and documentation to reflect the optional nature of the Figma token and the use of Penpot, and consolidating duplicate timeout options.
…e families Update doc-comment for the HeavyFaultToleranceOptions resolveClient overload to describe NoTokenFigmaClient fallback when accessToken is nil, matching the existing FaultToleranceOptions overload. Update Source/ directory description in CLAUDE.md to list all three source families (Figma, Penpot, TokensFile).
… support - Validate Penpot format (svg/png) before API call instead of inside component loop - Make resolvedFileId source-kind-aware to prevent passing Figma keys to Penpot API - Replace silent .figma fallback on empty entries with explicit guard + error - Add URL validation in PenpotClientFactory before client creation - Remove synthetic FetchWizardResult with empty strings, pass penpotBaseURL directly - Replace default branch with explicit cases in VariablesSourceValidation - Add ColorsConfigError.unsupportedSourceKind for proper error at config layer - Inject TerminalUI explicitly into SourceFactory instead of global static access - Remove unused sourceKind parameter from PenpotComponentsSource.loadComponents - Fix leaked swiftlint:disable cyclomatic_complexity in DownloadImages
Description
PenpotColorsSource,PenpotTypographySource, andPenpotComponentsSourcewith SVG reconstruction from the shape tree (no raster-only limitation)swift-penpot-apipackage for reuse and cleaner module boundariesexfig initandexfig fetchwizards with Penpot flow (design source selection, file ID extraction from URLs,--source penpotflag)Common.PenpotSourcePKL schema,resolvedSourceKindauto-detection onFrameSourceandVariablesSource, andSourceKindBridginghelpersFIGMA_PERSONAL_TOKENoptional with a lazyNoTokenFigmaClientfallback so pure-Penpot workflows need no Figma credentialsresolvedFileIdandresolvedPenpotBaseURLExFig.docc, add Architecture and Migration articlesPenpotSourceTests,PenpotDesignSourceTests,PenpotWizardTests,VariablesSourceResolvedTestsAdditional notes
downloadcommands (download all/colors/icons/images/typography) remain Figma-only by design; Penpot export usesexfig colors/icons/imagesviaSourceFactoryandexfig fetch --source penpotsvgandpngformats; unsupported formats throw an errorfile://URLs are now allowed inFileDownloaderfor local SVG assets during Penpot development and testing