{"id":19560427,"url":"https://github.com/codelytv/figma-plugin-skeleton","last_synced_at":"2025-04-26T23:33:03.622Z","repository":{"id":95838798,"uuid":"515938880","full_name":"CodelyTV/figma-plugin-skeleton","owner":"CodelyTV","description":"🪆 Template intended to serve as a starting point if you want to bootstrap a Figma Plugin in TypeScript.","archived":false,"fork":false,"pushed_at":"2023-02-08T16:08:42.000Z","size":542,"stargazers_count":39,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-04T18:38:41.114Z","etag":null,"topics":["figma","figma-api","figma-plugin","figma-plugin-example","figma-plugins"],"latest_commit_sha":null,"homepage":"https://pro.codely.com/library/desarrolla-plugins-de-figma-mantenibles-con-typescript-187373","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CodelyTV.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-20T10:38:13.000Z","updated_at":"2025-02-24T18:02:32.000Z","dependencies_parsed_at":"2023-03-02T19:00:28.803Z","dependency_job_id":null,"html_url":"https://github.com/CodelyTV/figma-plugin-skeleton","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":"CodelyTV/typescript-basic-skeleton","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodelyTV%2Ffigma-plugin-skeleton","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodelyTV%2Ffigma-plugin-skeleton/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodelyTV%2Ffigma-plugin-skeleton/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodelyTV%2Ffigma-plugin-skeleton/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CodelyTV","download_url":"https://codeload.github.com/CodelyTV/figma-plugin-skeleton/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251068040,"owners_count":21531475,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["figma","figma-api","figma-plugin","figma-plugin-example","figma-plugins"],"created_at":"2024-11-11T05:07:31.686Z","updated_at":"2025-04-26T23:32:58.851Z","avatar_url":"https://github.com/CodelyTV.png","language":"TypeScript","readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://codely.com\"\u003e\n    \u003cimg src=\"https://user-images.githubusercontent.com/10558907/170513882-a09eee57-7765-4ca4-b2dd-3c2e061fdad0.png\" width=\"300px\" height=\"92px\" alt=\"Codely logo\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003e\n  🪆 Codely Figma Plugin Skeleton\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/CodelyTV/figma-plugin-skeleton/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/CodelyTV/figma-plugin-skeleton/actions/workflows/ci.yml/badge.svg\" alt=\"Build status\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/CodelyTV\"\u003e\u003cimg src=\"https://img.shields.io/badge/CodelyTV-OS-green.svg?style=flat-square\" alt=\"Codely Open Source\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://pro.codely.com\"\u003e\u003cimg src=\"https://img.shields.io/badge/CodelyTV-PRO-black.svg?style=flat-square\" alt=\"CodelyTV Courses\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Template intended to serve as a starting point if you want to \u003cstrong\u003ebootstrap a Figma Plugin in TypeScript\u003c/strong\u003e.\n  \u003cbr /\u003e\n  \u003cbr /\u003e\n  Take a look, play and have fun with this.\n  \u003ca href=\"https://github.com/CodelyTV/figma-plugin-skeleton/stargazers\"\u003eStars are welcome 😊\u003c/a\u003e\n\u003c/p\u003e\n\nThe purpose of this repository is to leave it with the bare minimum dependencies and tools needed to build Figma Plugins\nbut **based on software development best practices such as SOLID principles, testing, and tooling already configured**\n🤟\n\n## 🚀 Running the app\n\n- Install the dependencies: `npm install`\n- Execute the tests: `npm run test`\n- Check linter errors: `npm run lint`\n- Fix linter errors: `npm run lint:fix`\n- Make a build unifying everything in the same `dist/figmaEntrypoint.js` file: `npm run build`\n- Run a watcher on your plugin files and make the build on every change: `npm run dev`\n\n## 🗺️ Steps to develop your own plugin\n\n1. Click on the \"Use this template\" button in order to create your own repository based on this one\n2. Clone your repository\n3. Replace the skeleton branding by your own:\n\n- Modify the `name` property of your [`manifest.json`](manifest.json) file, and set the `id` value following the next\n  steps in order to obtain it from Figma:\n  1. Generate a plugin in the Figma App: `Figma menu` \u003e `Plugins` \u003e `Development` \u003e `New Plugin…`\n  2. Give it a random name and choose any kind of plugin\n  3. Save the Figma plugin files locally\n  4. Open the saved `manifest.json` and copy the `id` property value\n- Modify the following [`package.json`](package.json) properties: `name`, `description`, `repository.url`, `bugs.url`,\n  and `homepage`\n\n4. Install all the plugin dependencies running: `npm install`\n5. Develop in a continuos feedback loop with the watcher: `npm run dev`\n6. Install your plugin in your Figma App: `Figma menu` \u003e `Plugins` \u003e `Development` \u003e `Import plugin from manifest…`\n7. [Remove the unnecessary code](https://github.com/CodelyTV/figma-plugin-skeleton#-remove-unnecessary-code)\n8. [Add your new use case Command](https://github.com/CodelyTV/figma-plugin-skeleton#-how-to-add-new-commands)\n9. Now you can call to the `handleCommand` function passing in the created command\n\nℹ️ And remember to star this repository in order to promote the work behind it 🌟😊\n\n## 🏗️ Software Architecture\n\n### 📍 Figma entrypoint\n\nYou will find the entrypoint that Figma will execute once the plugin is executed in\nthe [`src/figma-entrypoint.ts`](src/figma-entrypoint.ts) file, which is intended to represent the interaction with the\nFigma UI, leaving the logic of your plugin to the different commands that will be executed in the Browser or in the\nFigma Scene Sandbox.\n\n### 🎨 UI\n\nIn the [`src/ui`](src/ui) folder you will find the HTML, CSS, and TS files corresponding to the plugin user interface.\nWe have decided to split them up in order to allow better code modularization, and leaving Webpack to transpile the\nTypeScript code into JavaScript and inline it into the HTML due to Figma restrictions 😊\n\n### ⚡ Commands\n\nCommands are the different actions an end user can perform from the plugin UI. In the [`src/ui/ui.ts`](src/ui/ui.ts) you\nwill see that we are adding event listeners to the plugin UI in order to execute these Commands such as the following\none:\n\n```typescript\nimport { executeCommand } from \"./commands-setup/executeCommand\";\n\ndocument.addEventListener(\"click\", function(event: MouseEvent) {\n  const target = event.target as HTMLElement;\n\n  switch (target.id) {\n    case \"cancel\":\n      executeCommand(new CancelCommand());\n      break;\n    // […]\n  }\n});\n```\n\nThis `executeCommand(new CancelCommand());` function call is needed due to how Figma Plugins run, that is, communicating\nourselves between the following types of elements:\n\n![Codely Figma Plugin Skeleton Architecture](assets/figma-plugins-architecture.png 'There are 2 different \"worlds\". The Figma scene one, and the browser iframe one')\n\n1. The [`src/figma-entrypoint.ts`](src/figma-entrypoint.ts): As described before, in general this is the file that Figma\n   will execute once the user runs your plugin. However, there are multiple scenarios depending on the type of plugin:\n\n- **Plugins with a single use case**:\n\n  ![Single Use Case Figma Plugins does not have a dropdown menu](assets/single-use-case-figma-plugin.png)\n  - **Plugins with UI**: If you do not have a `\"menu\"` key declared in the [`manifest.json`](manifest.json), but\n    a `\"ui\"` one, Figma will render the [`src/ui/ui.html`](src/ui/ui.html) which is bundled together with\n    the [`src/ui/ui.ts`](src/ui/ui.ts) and [`src/ui/ui.css`](src/ui/ui.css). That UI will run inside the Figma Browser\n    iframe, and you will be able to execute Commands as in the previous example, that is, using the `executeCommand`\n    method from the `ui.ts`. These commands will arribe to this `figma-entrypoint.ts` in order to be executed. You\n    have an example for the \"Cancel\" button of the plugin UI mentioned\n    before: [`CancelCommand`](src/scene-commands/cancel/CancelCommand.ts) mapped in\n    the [`CommandsMapping`](src/commands-setup/CommandsMapping.ts#L12) to\n    the [`CancelCommandHandler`](src/scene-commands/cancel/CancelCommandHandler.ts) and tested out in\n    the [`CancelCommandHandler.test`](tests/scene-commands/CancelCommandHandler.test.ts).\n  - **Plugins without UI**: If you do not have either a `\"menu\"` key declared in the [`manifest.json`](manifest.json),\n    nor a `\"ui\"` one, Figma will render the [`src/figma-entrypoint.ts`](src/figma-entrypoint.ts) and you will be able\n    to execute Commands directly from there with the `handleCommand` method. These commands will arribe to\n    this `figma-entrypoint.ts` in order to be executed.\n- **Plugins with multiple use cases**:\n\n  ![Multiple Use Cases Figma Plugins does have a dropdown menu](assets/multiple-use-cases-figma-plugin.png)\n\n  You can define several use cases will be defined as `menu` items declared in the [`manifest.json`](manifest.json).\n  In this case, this entrypoint will directly execute the Command Handler mapped in\n  the [`src/commands-setup/CommandsMapping.ts`](src/commands-setup/CommandsMapping.ts) that corresponds to\n  the `menu.[].command` key. You have an example for instance for the `createShapes` Command which is mapped to\n  the [`src/scene-commands/create-shapes/CreateShapesCommandHandler.ts`](src/scene-commands/create-shapes/CreateShapesCommandHandler.ts)\n  .\n  One of this use cases can actually be to show the UI. You can see\n  it [declared with the `showUi` command name](manifest.json#L13)\n  and [handled as a particular case](src/figma-entrypoint.ts#L11).\n\n2. The Browser iframe Figma creates for us in order to run the plugin UI. This iframe is needed in order to gain access\n   to the browser APIs in order to perform HTTP requests for instance.\n3. The Figma scene exposed in order to create elements or access to the different layers from\n   the [`src/scene-commands`](src/scene-commands) which runs inside the Figma sandbox.\n4. The previous commands could need some information from the external world, so they must send out a command to be\n   handled inside the iframe. You can see an example of this in\n   the [`PaintCurrentUserAvatarCommandHandler`](src/scene-commands/paint-current-user-avatar/PaintCurrentUserAvatarCommandHandler.ts)\n   . All you have to do to perform the request is executing a `NetworkRequestCommand`:\n   ```typescript\n   executeCommand(\n     new NetworkRequestCommand(\"https://example.com/some/api/endpoint\", \"text\")\n   );\n   ```\n   And listen for the response:\n   ```typescript\n   return new Promise((resolve) =\u003e {\n     this.figma.ui.onmessage = async (message) =\u003e {\n       await this.doThingsWith(message.payload);\n       resolve();\n     };\n   });\n   ```\n\n#### 🆕 How to add new commands\n\nIf you want to add new capabilities to your plugin, we have intended to allow you to do so without having to worry about\nall the TypeScript stuff behind the Commands concept. It is as simple as:\n\n1. Create a folder giving a name to your Command. Example: [`src/scene-commands/cancel`](src/scene-commands/cancel)\n2. Create the class that will represent your Command.\n\n- Example of the simplest Command you can think of (only provides\n  semantics): [`src/scene-commands/cancel/CancelCommand.ts`](src/scene-commands/cancel/CancelCommand.ts)\n- Example of a Command needing\n  parameters: [`src/scene-commands/create-shapes/CreateShapesCommand.ts`](src/scene-commands/create-shapes/CreateShapesCommand.ts)\n\n3. Create the CommandHandler that will receive your Command and will represent the business logic behind it. Following\n   the previous examples:\n\n- [`src/scene-commands/cancel/CancelCommandHandler.ts`](src/scene-commands/cancel/CancelCommandHandler.ts)\n- [`src/scene-commands/create-shapes/CreateShapesCommandHandler.ts`](src/scene-commands/create-shapes/CreateShapesCommandHandler.ts)\n\n4. Link your Command to your CommandHandler adding it to\n   the [`src/commands-setup/CommandsMapping.ts`](src/commands-setup/CommandsMapping.ts)\n5. Send the command from one of the following places depending on your plugin type:\n\n- Plugins with UI: From [`src/ui/ui.ts`](src/ui/ui.ts) with `executeCommand(new CancelCommand());`\n- Plugins without UI: From the [`src/figma-entrypoint.ts`](src/figma-entrypoint.ts)\n  with `await handleCommand(new CancelCommand());`\n\n## 🌈 Features\n\n### ✨ Illustrative working examples\n\nIn order to show the potential Figma Plugins have, we have developed several use cases:\n\n![Plugin menu with the 3 use cases](assets/plugin-use-cases.png)\n\n#### 👀 Shapes Creator Form\n\n![Shapes Creator Form](assets/shapes-creator-form-use-case.png)\n![Shapes Creator Form Result](assets/shapes-creator-form-use-case-result.png)\n\nDemonstrative purposes:\n\n- Render a UI allowing it to be modular and scalable (Webpack bundling working in Figma thanks to JS inline)\n- How to communicate from the Figma Browser iframe where the UI lives to the Figma Scene Sandbox in order to execute\n  commands like the `createShapes` one which require to modify the viewport, create and select objects, and so on\n- Work with the Figma Plugins API randomizing multiple variables to make it a little more playful:\n  - The shapes to create (rectangles and ellipses)\n  - The rotation of each shape\n  - The color of the shapes\n\n#### ⌨️ Shapes Creator Parametrized\n\nYou can launch parametrized menu commands from the Figma Quick Actions search bar:\n\n![Shapes Creator Parametrized in the Quick Actions search bar](assets/shapes-creator-parametrized-use-case.png)\n\nIt even allows you to configure optional parameters and suggestions for them:\n\n![Filtering our the type of shapes parameter value](assets/shapes-creator-parametrized-suggestions-use-case.png)\n\nDemonstrative purposes:\n\n- Take advantage of the Parametrized Figma Plugins in order to offer a simple UI integrated with the Figma ecosystem\n  without having to implement any HTML or CSS\n- Reuse the very same use\n  case ([`CreateShapesCommandHandler`](src/scene-commands/create-shapes/CreateShapesCommandHandler.ts)) from multiple\n  entry-points. That, is we are using that very same business logic class:\n  - Calling it [from the `ui.ts`](src/ui/ui.ts#L23) once the user clicks on the `create` button because we are executing\n    the command with `executeCommand(new CreateShapesCommand(count));`\n  - Configuring [the parametrized menu entry in the `manifest.json`](manifest.json#L14) with the very same `command`\n    name [as mapped in the `CommandsMapping`](src/commands-setup/CommandsMapping.ts#L13), and the same parameters `key`\n    as defined in [the command constructor](src/scene-commands/create-shapes/CreateShapesCommand.ts#L12)\n- Configure optional parameters and how they map to nullable TypeScript arguments\n- Specify suggestions for some parameter values that can be programmatically set. Example\n  in [the `figma-entrypoint`](src/figma-entrypoint.ts#L28) for the `typeOfShapes` parameter.\n\n#### 🎨 Paint current user avatar\n\n![How the use case paint out the avatar and its user name](assets/paint-current-user-avatar-use-case.png)\n\nDemonstrative purposes:\n\n- Communicate back from the Figma Scene Sandbox to the Figma Browser iframe in order to perform the HTTP request in\n  order to get the actual user avatar image based on its URL due to not having access to browser APIs inside\n  the `src/scene-commands` world\n- Define the architecture in order to have that HTTP request response handler defined in a cohesive way inside the\n  actual use case which fires it. Example in\n  the [`PaintCurrentUserAvatarCommandHandler`](src/scene-commands/paint-current-user-avatar/PaintCurrentUserAvatarCommandHandler.ts#L29)\n  .\n- Paint an image inside the Figma scene based on its binary information\n- Declare a more complex menu structure containing separators and sub-menu items\n- Loading the text font needed in order to create a text layer and position it relative to the image size\n\n### 🫵 Simplified communication\n\nIf you take a look at the official documentation\non [how Figma Plugins run](https://www.figma.com/plugin-docs/how-plugins-run/), you will see that there is\na `postMessage` function in order to communicate between the two Figma Plugin worlds previously described:\n\n![Original Figma Plugins Architecture](assets/figma-plugins-architecture-original.png)\n\nHowever, that `postMessage` function is different depending on where you are executing it:\n\n- From the Figma Scene sandbox to the UI iframe: `figma.ui.postMessage(message)`\n- From the UI iframe to the Figma Scene sandbox: `window.parent.postMessage({ pluginMessage: command }, \"*\")`\n\nWe have simplified this with an abstraction that also provides semantics and type constraints making it easier to use.\nYou only have to use [the `executeCommand` function](src/commands-setup/executeCommand.ts) without worrying about\nanything else:\n\n```typescript\nimport { executeCommand } from \"./commands-setup/executeCommand\";\n\nexecuteCommand(new CancelCommand());\n```\n\nThis is why you will see it on the Codely Figma Plugin Architecture diagram while communicating on both ways:\n\n![Codely Figma Plugin Skeleton Architecture](assets/figma-plugins-architecture.png 'There are 2 different \"worlds\". The Figma scene one, and the browser iframe one')\n\n### ✅ Software development best practices\n\nFocus of all the decisions made in the development of this skeleton: Let you, the developer of the plugin that end users\nwill install, **focus on implementing your actual use cases** instead of all the surrounding boilerplate ⚡\n\nWe have followed an approach for developing this Codely Figma Plugin Skeleton based on the SOLID Software Principles,\nspecially the Open/Closed Principle in order to make it easy for you to **extend the capabilities of your plugin with\njust adding little pieces of code** in a very structured way 😊\n\n### ✨ Developer and end user experience\n\nThis skeleton already provides a friendly way to handle error produced by the plugins built with it.\n\nIf your plugin makes use of the `executeCommand` method in order to execute commands, we already have you covered in\ncase you have not registered them yet. It would be visible in the actual Figma interface, and specify all the details in\nthe JavaScript console, ¡even suggesting a fix! 🌈:\n\n![Error seen if you do not add your new command.](assets/error-handling-unhandled-plugin-exception.png)\n\nIn case you already registered your command, but it throws an unhandled by you error for whatever reason, we propagate\nit to the end user in a very friendly way 😇:\n\n![Error seen if you do not handle it.](assets/error-handling-unregistered-command.png)\n\n### 🧰 Tooling already configured\n\n- [TypeScript](https://typescriptlang.org) (v4)\n- [Prettier](https://prettier.io)\n- [Webpack](https://webpack.js.org)\n- [ESLint](https://eslint.org) with:\n  - [Simple Import Sort](https://github.com/lydell/eslint-plugin-simple-import-sort)\n  - [Import plugin](https://github.com/benmosher/eslint-plugin-import)\n  - And a few other ES2015+ related rules\n- [Jest](https://jestjs.io) with [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro)\n- [GitHub Action workflows](https://github.com/CodelyTV/figma-plugin-skeleton/actions) set up to run tests and linting\n  on push\n- [SWC](https://swc.rs): Execute your tests in less than 200ms\n\n### 🤏 Decisions made to promote code quality and structure consistency\n\n- Specify proper dependencies version restriction (no wild wildcards `*`)\n- Encapsulate all the transpiled code into the `dist` folder\n- Encapsulate all the Plugin source code into the `src` folder\n- Configure TypeScript through the `tsconfig.json` in order to promote safety and robust contracts (no more `any`\n  paradise)\n- Add code style checker with Prettier and ESLint\n- Add test suite runner with Jest\n- Add Continuous Integration Workflow with GitHub Actions\n\n### 🧽 Remove unnecessary code\n\nDepending on your plugin type you will find unnecessary code in this template. However, here you have the instructions\non how to delete it with a few commands 😊\n\n#### 🙈 Plugins without UI\n\n☝️ Attention: We will not remove the `ui` key from the `manifest.json` and some JS code such as\nthe `registerUiCommandHandlers` function call because we still need them even if we do not have a UI. The reason why is\nthat this code is used as an invisible UI while communicating from the Scene Sandbox to the UI iframe in order to access\nbrowser APIs. These browser APIs are used for instance while performing network requests from our plugin. See more on\nthe \"⚡ Commands\" software architecture section.\n\n- Remove unneeded dependencies: `npm remove style-loader css-loader figma-plugin-ds`\n- [`webpack.config.js`](webpack.config.js): Remove the css and static assets rules from `module.exports.module.rules`\n  only leaving out the ts files one\n- Remove the visual parts of the UI:\n  - `rm src/ui/register-ui-command-handlers.ts`\n  - `echo -n \"\" \u003e| src/ui/ui.html`\n  - `echo \"import { registerUiCommandHandlers } from \\\"./register-ui-command-handlers\\\";\\n\\nregisterUiCommandHandlers();\" \u003e| src/ui/ui.ts`\n\n#### ☝️ Plugins without menus (just a single use case)\n\n- [`manifest.json`](manifest.json): Remove the `menu` property\n- Modify the [`src/figma-entrypoint.ts`](src/figma-entrypoint.ts) removing the support for menu commands and directly\n  executing your use case command keeping the support for the invisible UI. Example for a plugin which only would\n  execute the `paintCurrentUserAvatar` command:\n  ```typescript\n  import { handleCommand } from \"./commands-setup/handleCommand\";\n  import { PaintCurrentUserAvatarCommand } from \"./scene-commands/paint-current-user-avatar/PaintCurrentUserAvatarCommand\";\n  \n  createInvisibleUiForBrowserApiAccess();\n  \n  await handleCommand(new PaintCurrentUserAvatarCommand());\n  \n  function createInvisibleUiForBrowserApiAccess() {\n    figma.showUI(__html__, { visible: false });\n  }\n  ```\n\n#### 🖌️ Plugins without FigJam support\n\n[`manifest.json`](manifest.json): Remove the `figjam` value from the `editorType` property, leaving the property as an\narray but only containing the `figma` value.\n\n#### 🧊 Plugins without tests\n\n- Remove the `✅ Run tests` step from [the Continuous Integration pipeline](.github/workflows/ci.yml)\n- `rm -rf tests`\n- `rm -rf jest.config.js`\n- `npm remove jest @types/jest jest-mock-extended @swc/jest @swc/core`\n- Remove the `scripts.test` property from the [`package.json`](package.json)\n\n#### 🔒 Plugins without special permissions\n\nRemove the `permissions` key from your `manifest.json`.\n\n## 👀 Inspiration\n\nOther Figma plugins repositories where we found inspiration to create this one:\n\n- [figma-plugin-typescript-boilerplate](https://github.com/aarongarciah/figma-plugin-typescript-boilerplate)\n- [Create Figma Plugin](https://yuanqing.github.io/create-figma-plugin/)\n\n## 👌 Codely Code Quality Standards\n\nPublishing this package we are committing ourselves to the following code quality standards:\n\n- 🤝 Respect **Semantic Versioning**: No breaking changes in patch or minor versions\n- 🤏 No surprises in transitive dependencies: Use the **bare minimum dependencies** needed to meet the purpose\n- 🎯 **One specific purpose** to meet without having to carry a bunch of unnecessary other utilities\n- ✅ **Tests** as documentation and usage examples\n- 📖 **Well documented ReadMe** showing how to install and use\n- ⚖️ **License favoring Open Source** and collaboration\n\n## 🔀 Related skeleton templates\n\nOpinionated TypeScript skeletons ready for different purposes:\n\n- [🔷🌱 TypeScript Basic Skeleton](https://github.com/CodelyTV/typescript-basic-skeleton)\n- [🔷🕸️ TypeScript Web Skeleton](https://github.com/CodelyTV/typescript-web-skeleton)\n- [🔷🌍 TypeScript API Skeleton](https://github.com/CodelyTV/typescript-api-skeleton)\n- [🔷✨ TypeScript DDD Skeleton](https://github.com/CodelyTV/typescript-ddd-skeleton)\n\nThis very same basic skeleton philosophy implemented in other programming languages:\n\n- [✨ JavaScript Basic Skeleton](https://github.com/CodelyTV/javascript-basic-skeleton)\n- [☕ Java Basic Skeleton](https://github.com/CodelyTV/java-basic-skeleton)\n- [📍 Kotlin Basic Skeleton](https://github.com/CodelyTV/kotlin-basic-skeleton)\n- [🧬 Scala Basic Skeleton](https://github.com/CodelyTV/scala-basic-skeleton)\n- [🦈 C# Basic Skeleton](https://github.com/CodelyTV/csharp-basic-skeleton)\n- [🐘 PHP Basic Skeleton](https://github.com/CodelyTV/php-basic-skeleton)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodelytv%2Ffigma-plugin-skeleton","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodelytv%2Ffigma-plugin-skeleton","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodelytv%2Ffigma-plugin-skeleton/lists"}