This document defines the runtime architecture, source layout, extension model, and tooling used by @coderrob/figma-connecter.
figma-connecter is a Node.js CLI that scans TypeScript Web Component sources, extracts component metadata from the TypeScript AST, and generates or updates Figma Code Connect files for supported targets.
Current built-in targets:
- Parser:
webcomponent - Emitters:
webcomponent,react
Primary outcomes:
- Discover
*.component.tssource files - Build a TypeScript program for symbol resolution and inheritance analysis
- Parse component metadata into a normalized model
- Emit Code Connect files into
code-connect/ - Update generated sections safely when files already exist
- Report warnings, errors, and file changes with structured logging
The system is intentionally layered.
| Layer | Responsibility | Main Paths |
|---|---|---|
| CLI | Program bootstrapping, help text, option parsing, command dispatch | bin/, src/cli/, src/commands/ |
| Core | Shared enums, result/report helpers, logging, registry primitives | src/core/ |
| IO | File discovery, tsconfig loading, file writes, generated section updates | src/io/ |
| Parsers | AST traversal and metadata extraction into a normalized component model | src/parsers/ |
| Mappers | Data normalization and mapping helpers for emitters | src/mappers/ |
| Emitters | Target-specific Code Connect generation | src/emitters/ |
| Pipeline | End-to-end orchestration over discovery, parse, emit, and reporting | src/pipeline/ |
| Utils | Pure path, string, merge, and TypeScript utility functions | src/utils/ |
The layers are designed to be mostly one-way:
CLI -> Pipeline -> IO / Parsers / Emitters -> Core / Utils
The repository standards in AGENTS.md are reflected in the implementation:
- Separation of concerns: orchestration lives in
pipeline, AST logic lives inparsers, file writes live inio. - Immutability first: result objects and models are built via copies, not in-place mutation.
- Strategy plus factory: parser and emitter selection is registry-driven rather than switch-based.
- Fail fast, recover gracefully: invalid CLI input throws early; parse and emit issues accumulate diagnostics in reports.
- Operational transparency: pipeline and command stages log progress and return structured summaries.
The main execution path is:
bin/figma-connecter.tsstarts the CLI.src/cli/program.tscreates the Commander program, global flags, help formatting, and command registration.src/commands/connect/handler.tsvalidates user input, resolves effective options, builds a logger and progress indicator, and callsrunConnectPipeline.src/pipeline/runner.tsorchestrates:- file discovery
- parser/emitter initialization
- TypeScript program loading
- per-file batch processing
- report finalization
src/pipeline/batch.tsprocesses each discovered component file:- resolve the
ts.SourceFile - parse to
IComponentModel - emit one file per selected emitter
- write full content or update generated sections
- resolve the
- The command layer prints the summary, warnings, errors, and dry-run details, then sets
process.exitCodeon failure.
runConnectPipeline() in src/pipeline/runner.ts runs these steps in order:
discoverComponentsStepinitializePipelineSteploadSourcesStepwarnOnMissingEmittersSteprunBatchStepfinalizeReportStep
Each stage receives and returns an immutable IResult<IRunnerContext> state object. This keeps diagnostics and state transitions explicit.
processComponentBatch() in src/pipeline/batch.ts runs each discovered file through:
resolveSourceFileStepparseComponentStepemitComponentStep
Per-file behavior is controlled by shared IPipelineContext values such as:
dryRunstrictcontinueOnErrorbaseImportPathforceiocheckersourceFileMap
The architecture depends on a few central contracts.
Parsers produce a normalized IComponentModel from the core contracts in src/core/. Emitters consume that model without depending on parser internals. This is the main seam that keeps parsing and code generation decoupled.
Typical model contents:
- component/class name
- tag name
- properties and attributes
- events
- inheritance-derived metadata
- source-path context
src/core/result.ts provides the result container used across parse and pipeline stages. It carries:
valuewarningserrors
This allows parser and pipeline code to return useful partial outcomes without throwing for every non-fatal issue.
src/core/report.ts aggregates component-level results into an IGenerationReport with:
- created / updated / unchanged counts
- duration
- warning and error collections
- optional
componentResultsfor command output and dry-run reporting
The CLI uses commander as its command framework.
Main command infrastructure:
src/cli/program.ts: root program constructionsrc/cli/options.ts: global option accesssrc/cli/validators.ts: path and config validationsrc/cli/progress.ts: progress reportingsrc/commands/registry.ts: command registrationsrc/commands/connect/: the main supported command
The connect command is built as staged execution:
- validate
- execute
- report
- onError
That staged design isolates user interaction concerns from pipeline logic.
Parsers are strategy objects created through src/parsers/factory.ts.
Built-in parser:
WebComponentParserinsrc/parsers/webcomponent/
Key parser responsibilities:
- find the component class
- resolve decorators and JSDoc metadata
- extract properties and events
- resolve tag names
- follow inheritance when required
- convert AST findings into a stable component model
Important parser modules:
component-discovery.tsdecorator-extractor.tsevent-extractor.tsinheritance-resolver.tschain-extractor.tstagname-resolver.tstagname/export-resolution.tstagname/namespace.ts
The parser layer is intentionally TypeScript-AST-centric. It receives a ts.SourceFile plus ts.TypeChecker in standardized parse context, which keeps type resolution and inheritance logic inside the parser boundary.
Emitters are also strategy objects created through src/emitters/factory.ts.
Built-in emitters:
FigmaWebComponentEmitterFigmaReactEmitter
Emitter responsibilities:
- choose output file path and extension
- convert
IComponentModelinto Code Connect source text - optionally expose generated sections for marker-based partial updates
- return warnings when output can be generated but is incomplete
Shared emitter helpers:
file-builder.tssection-builder.tsformatting.tsfigma-mapper.tsutils.ts
Target-specific emitters live in:
src/emitters/figma-webcomponent/src/emitters/figma-react/
Registry order matters. createEmitters() returns emitters in registration order so file generation stays deterministic even if CLI target order differs.
The IO layer isolates file system and TypeScript program interactions.
Main IO modules:
file-discovery.ts: finds supported component sourcessource-loader.ts: loads tsconfig, compiler options, program, checker, and source mapfile-writer.ts: writes generated files and reports created/updated/unchanged statesection-updater.ts: updates only marked generated blocks in existing filesadapter.ts: filesystem abstraction for runtime and tests
Important behavior:
- Discovery excludes generated and dependency directories such as
distandnode_modules. - Section updates are safe by default. If generated markers are missing, the file is preserved and a warning is reported.
--forceswitches from partial update behavior to full file replacement.dryRunflows through write calls without mutating the filesystem.
The repo now treats path handling as a first-class concern because the tool runs on Windows and POSIX environments.
Current path design:
- normalization utilities are centralized in
src/utils/paths.ts - portable normalization prefers POSIX-style separators in generated/import paths
- absolute-path detection handles both Windows drive roots and POSIX roots
- source loading and output generation avoid leaking platform-specific path bugs into import generation and test expectations
This matters for:
- output file location
- import path calculation
- tsconfig discovery
- snapshot and integration test stability
The codebase is designed for extension without pipeline rewrites.
Registry primitives:
src/core/registry-factory.tssrc/emitters/factory.tssrc/parsers/factory.tssrc/plugins/
Extension rules:
- parsers and emitters register metadata plus a factory function
- targets cannot be registered twice
- registration must happen before factories are used
- orchestration code does not branch on individual targets
Plugin metadata is used for:
- target enumeration
- help and introspection
- file-extension and file-pattern declarations
- external plugin discovery via
getPluginInfo()
This is a strategy-plus-registry architecture, not an inheritance-heavy framework.
The project uses jest with ts-jest.
Test layout:
- unit tests under
__tests__/ - integration coverage in
__tests__/integration/ - per-layer tests mirroring the source tree
- generated integration output under
__tests__/__output__/
jest.config.js characteristics:
preset: 'ts-jest'testEnvironment: 'node'- roots include
__tests__andsrc - path alias mapping for
@/ - global coverage threshold of 90% for branches and 95% across functions, lines, and statements
The tests validate:
- AST extraction behavior
- CLI option handling
- report aggregation
- emitter output
- section update logic
- path normalization
- end-to-end connect command behavior
- TypeScript
5.9.x - Node.js
>=20 - npm
>=8 - npm
>=8declared as the supported package manager for local development
Rollup is used for packaging.
Build outputs:
dist/figma-connecter.cjsdist/index.cjsdist/index.d.ts
Key build tooling:
rollup@rollup/plugin-typescript@rollup/plugin-node-resolve@rollup/plugin-commonjs@rollup/plugin-terserrollup-plugin-dts
The package publishes the dist/ directory and exposes:
- the
figma-connecterCLI executable via the packagebinfield - CommonJS runtime bundle
- bundled type declarations
Linting uses ESLint flat config in eslint.config.mjs.
Major lint components:
@eslint/jstypescript-eslinteslint-plugin-importeslint-plugin-jsdoc@coderrob/eslint-plugin-zero-tolerance
Lint profile characteristics:
- strict type-safety rules
- import hygiene
- required JSDoc on source APIs
- maintainability/complexity limits
- test-specific overrides
- parser/emitter complexity relaxations for AST-heavy files
tsconfig.json is strict and produces declarations for builds. The project uses CommonJS output with path aliasing via @/*.
Main scripts from package.json:
npm run buildnpm run cleannpm run duplicationnpm run lintnpm run lint:fixnpm run quality:gatenpm run repo:hygienenpm run testnpm run test:coveragenpm run tsc
Duplication control is enforced with jscpd over src/ and bin/ with a hard threshold below 1%. Generated output and dependency directories are excluded from that gate.
Repository hygiene is enforced with repo:hygiene, which requires a single uppercase CHANGELOG.md, rejects stale placeholder markers such as TBD and pending, and verifies there is exactly one [Unreleased] section.
High-level source structure:
bin/
figma-connecter.ts
src/
cli/
commands/
connect/
core/
emitters/
figma-react/
figma-webcomponent/
io/
mappers/
parsers/
webcomponent/
tagname/
pipeline/
plugins/
utils/
__tests__/
Structural conventions:
- folders with multiple public modules expose a barrel
index.ts - source files are kebab-case
- tests mirror feature ownership by directory
- public APIs are exported from
src/index.ts
The most important boundaries are:
- CLI code should not parse ASTs directly.
- Emitters should not know how the parser found metadata.
- Parsers should not perform file writes.
- Pipeline code should orchestrate, not implement target-specific business logic.
- IO adapters should be the seam for filesystem behavior in tests.
Violating these boundaries would make extension harder and cross-platform behavior less predictable.
The current architecture is not trying to be:
- a general-purpose build system
- a live file watcher or daemon
- a multi-language parser platform
- a runtime plugin loader with sandboxing
- a framework that mutates arbitrary user files outside generated markers by default
It is a focused generation tool with a strong bias toward safe updates and deterministic output.
When adding new capabilities:
- add new parser or emitter targets through the appropriate factory registry
- preserve immutable result flows
- keep the normalized component model as the parse/emit contract
- prefer metadata-driven registration over special-case branching
- keep filesystem effects inside
src/io/ - add unit tests at the module layer and integration coverage when output behavior changes
Useful extension references already in the repo:
src/README.mdsrc/parsers/README.mdsrc/emitters/EXTENDING.md
figma-connecter is a layered TypeScript CLI with:
- Commander for command execution
- TypeScript AST analysis for component parsing
- registry-based parser and emitter composition
- IO abstractions for safe writes and testability
- strict lint, type-check, and coverage gates
- plugin seams for future targets without rewriting the pipeline
That combination gives the project deterministic generation behavior, clear extension points, and a code structure that can scale beyond the initial Web Component and React targets.