Skip to content

feat(penpot): add Penpot as alternative design source#76

Merged
alexey1312 merged 26 commits intomainfrom
feature/add-penpot-support
Mar 22, 2026
Merged

feat(penpot): add Penpot as alternative design source#76
alexey1312 merged 26 commits intomainfrom
feature/add-penpot-support

Conversation

@alexey1312
Copy link
Collaborator

Description

  • Add Penpot as a second design source alongside Figma, enabling color, typography, and icon/image export from self-hosted or cloud Penpot instances
  • Introduce PenpotColorsSource, PenpotTypographySource, and PenpotComponentsSource with SVG reconstruction from the shape tree (no raster-only limitation)
  • Extract PenpotAPI into the external swift-penpot-api package for reuse and cleaner module boundaries
  • Extend exfig init and exfig fetch wizards with Penpot flow (design source selection, file ID extraction from URLs, --source penpot flag)
  • Add Common.PenpotSource PKL schema, resolvedSourceKind auto-detection on FrameSource and VariablesSource, and SourceKindBridging helpers
  • Make FIGMA_PERSONAL_TOKEN optional with a lazy NoTokenFigmaClient fallback so pure-Penpot workflows need no Figma credentials
  • Update entry bridge methods across all four platform plugins (iOS, Android, Flutter, Web) with resolvedFileId and resolvedPenpotBaseURL
  • Consolidate DocC source documentation into ExFig.docc, add Architecture and Migration articles
  • Add comprehensive tests: PenpotSourceTests, PenpotDesignSourceTests, PenpotWizardTests, VariablesSourceResolvedTests

Additional notes

  • Penpot download commands (download all/colors/icons/images/typography) remain Figma-only by design; Penpot export uses exfig colors/icons/images via SourceFactory and exfig fetch --source penpot
  • Penpot fetch supports only svg and png formats; unsupported formats throw an error
  • file:// URLs are now allowed in FileDownloader for local SVG assets during Penpot development and testing

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.
@gemini-code-assist
Copy link

Summary of Changes

Hello, 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

  • Penpot Support: Adds Penpot as an alternative design source to Figma, enabling color, typography, and icon/image export from self-hosted or cloud Penpot instances.
  • Externalized Penpot API: Extracts PenpotAPI into the external swift-penpot-api package for reuse and cleaner module boundaries.
  • Enhanced CLI Tools: Extends exfig init and exfig fetch wizards with Penpot flow, including design source selection and file ID extraction from URLs.
  • Lazy Figma Token: Makes FIGMA_PERSONAL_TOKEN optional with a lazy NoTokenFigmaClient fallback, so pure-Penpot workflows need no Figma credentials.
  • Consolidated Documentation: Consolidates DocC source documentation into ExFig.docc, adding Architecture and Migration articles.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/deploy-docc.yml
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@alexey1312 alexey1312 merged commit b882d0a into main Mar 22, 2026
3 checks passed
@alexey1312 alexey1312 deleted the feature/add-penpot-support branch March 22, 2026 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant