├── .github └── workflows │ ├── update-lastupdated.yml │ └── update-readme.yml ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── Rakefile ├── add ├── lib └── plugin-directory-utils.rb ├── plugins.json └── stats /.github/workflows/update-lastupdated.yml: -------------------------------------------------------------------------------- 1 | name: Update lastUpdated 2 | on: 3 | schedule: 4 | - cron: '0 0 * * 0' 5 | workflow_dispatch: 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Set up Ruby 3.1 12 | uses: ruby/setup-ruby@v1 13 | with: 14 | ruby-version: '3.1' 15 | - name: Update lastUpdated 16 | run: | 17 | gem install octokit 18 | rake lastUpdated 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | - name: push 22 | run: | 23 | git config --global user.name 'Sketch Developer' 24 | git config --global user.email 'developer@sketch.com' 25 | git add plugins.json 26 | git commit -am 'content: Update lastUpdated' --allow-empty 27 | git push 28 | -------------------------------------------------------------------------------- /.github/workflows/update-readme.yml: -------------------------------------------------------------------------------- 1 | name: Update README 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - plugins.json 9 | workflow_run: 10 | workflows: ["Update lastUpdated"] 11 | types: 12 | - completed 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Ruby 3.1 19 | uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: '3.1' 22 | - name: Update README 23 | run: rake readme 24 | - name: push 25 | run: | 26 | git config --global user.name 'Sketch Developer' 27 | git config --global user.email 'developer@sketch.com' 28 | git add README.md 29 | git commit -am 'content: Update README' 30 | git push 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | clones 2 | vendor 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | To add a new plugin to the directory: 4 | 5 | 1. Fork the repo (create a new branch as well if you want) 6 | 1. Modify `plugins.json` by adding your plugin (do not edit `README.md`, it will be automatically generated once your change is merged) 7 | 1. Push your changes to your fork and [submit a pull request](https://github.com/sketchplugins/plugin-directory/compare/) 8 | 9 | Thanks in advance! 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sketch Plugin Directory 2 | 3 | A list of Sketch plugins hosted at GitHub, in alphabetical order. 4 | 5 | **Note:** if you want to add yours, just send a pull request, or use [skpm](https://skpm.io) to develop it (skpm takes care of publishing automatically). Once your plugin is added here, it will appear on [the website](https://sketchapp.com/extensions/plugins) as soon as we make a new deploy (that can take anywhere from a few minutes to a few days) 6 | 7 | - [Abstract](https://goabstract.com/), by Abstract: Abstract lets you manage, version, and document your designs in one place 8 | - [AEUX](https://github.com/google/sketch2ae), by Battle Axe: Transfer layer to After Effects 9 | - [Anto](https://github.com/canisminor1990/anto), by CanisMinor: Sketch Tools for AFX Designers 10 | - [App Asset Export](https://github.com/work4blue/sketch-app-asset-export), by Andrew Huang: Sketch3 plugin, One key export Android/iOS App icons. 11 | - [Artboard Manager](https://github.com/bomberstudios/artboard-manager#readme), by Ale Muñoz: Because moving artboards manually is *so* 2016. 12 | - [Artboard Tools](https://github.com/frankko/artboard-tools), by Frank Kolodziej: Sketch.app plugins for arranging artboards and navigating between artboards. 13 | - [autorename](https://github.com/dchahla/autorename), by dchahla: Lightweight, low-fi Auto Name Group ( ⌘ + g ) Sketch Plugin 14 | - [Awesome Ipsums](https://github.com/inVoltag/awesome-ipsums), by Aurélien Grimaud: Generate ipsums from an external Google spreadsheet. Make these datas collaborative! 15 | - [Batch Create Symbols](https://github.com/demersdesigns/sketch-batch-create-symbols), by Paul Demers: A plugin for Sketch to convert selected layers to individual symbols. 16 | - [Brandfetch](https://github.com/Brandfetch/Brandfetch-Sketch-Plugin), by Brandfetch: Pull brand assets into Sketch 17 | - [Brandfolder](https://github.com/brandfolder/sketch-plugin-brandfolder), by brandfolder: Easily use your digital assets from Brandfolder right within your favorite platform for digital design! 18 | - [Cloudinary Plugin](https://github.com/cloudinary-devs/cloudinary-sketch-plugin), by Maya Shavin: Cloudinary plugin for Sketch projects 19 | - [CodeWave](https://github.com/netease-lcap/sketch-plugin), by liuyiqi: 导出 Sketch 设计稿中的任意部分到 CodeWave 20 | - [Codia Ai Design: Screenshot to Editable Sketch Design](https://codia.ai/image-to-sketch), by Codia.AI: Transform screenshots into editable Sketch UI designs effortlessly. Simply upload a snapshot of an app or website, and let our tool do the rest. Then, begin our design journey. 21 | - [Color Copy Paste](https://github.com/sonnylazuardi/color-copy-paste), by Sonny Lazuardi Hermawan: 22 | - [CreatoCode](https://github.com/CreatoCode/CreatoCodeSketchPlugin), by flashgeek: Easily convert design drafts into code, streamline the development process, and seamlessly connect your creativity with reality. 23 | - [Crowdin for Sketch](https://github.com/crowdin/sketch-crowdin), by Crowdin: Localize the UI before programming starts. Translate and preview any design with ease 24 | - [css to Shadow Style](https://github.com/oliverpitsch/CSS-Shadow-to-Sketch-Style-Plugin), by Oliver Pitsch: CSS box-shadow to Layer Style Converter 25 | - [Easy Code](https://github.com/luchaoqun125/sketch-version/), by luchaoqun125: easy code 26 | - [Export to Proto.io](https://proto.io), by Proto.io: Give life to your Sketch designs! Export all or selected artboards to Proto.io screens, preserving layer positioning and hierarchy. By exporting to Proto.io you can link screens together, add interactions and animations. 27 | - [File Cleaner](https://github.com/monzo/file-cleaner), by Monzo: Keep your Sketch files immaculately clean and in order. 28 | - [Find And Replace](https://github.com/thierryc/sketch-find-and-replace), by Martin Steven. Maintained by Thierry Charbonnel: Sketch 3 plugin to do a simple find and replace on text within layers 29 | - [Frontify Plugin](https://github.com/Frontify/sketch), by Frontify: Connecting your brand and design worlds 30 | - [GxDesignOps](https://github.com/genexuslabs/sketchdesignops), by Gastón Milano: Sketch Plugin For GeneXus internal use 31 | - [imgcook](https://www.imgcook.com), by Taobao FED: A cook who can transform design to code 32 | - [Import SVG as Artboards](https://github.com/mathieudutour/import-svg-as-artboard), by mathieudutour: Import SVG files as Artboards. 33 | - [killswitch](https://github.com/madebysnacks/killswitch), by Snacks Studio: A simple Sketch plugin for disabling Symbol overrides. 34 | - [lippy](https://github.com/abynim/lippy), by Aby Nimbalkar: An interactive lorem ipsum generator plugin for Sketch 35 | - [Looper](https://github.com/sureskumar/looper), by Sures Kumar: Looper helps automate duplication of groups and layers. One can control properties like Rotate, Scale and Opacity while duplicating. This powerful combination enables artists and designers to create interesting geometric and organic patterns. 36 | - [Miro Plugin for Sketch](https://github.com/miroapp/sketch_plugin), by Miro: Add, share and discuss your Sketch artboards with the team in Miro. The plugin allow to sync the artboards with the boards in one click 37 | - [Outsystems ui Plugin](https://github.com/HiInteractive/OutSystemsUIPlugin), by Pedro Oliveira: 38 | - [Palette Stripes](https://github.com/sureskumar/palette-stripes), by Sures Kumar: Generate palette stripes in sketch from shape fills. 39 | - [Paste to Fill](https://github.com/tgfjt/sketch-paste-to-fill#readme), by tgfjt: Paste your image from clipboard, to fill layer. 40 | - [PersianSupplier](https://github.com/hiradary/persiansupplier), by Hirad Arshadi: Sketch Plugin for supplying dynamic persian data. 41 | - [Phosphor Icons](https://phosphoricons.com), by Tobias Fried: A flexible icon family for interfaces, diagrams, presentations — whatever, really. 42 | - [Picsart Bg Remover & Image Enhancer](https://picsart.io), by PicsArt, Inc: Elevate your Sketch designs with the Picsart Plugin: effortlessly remove backgrounds from product photos, cars, furniture, jewelry, and more, and upscale images by up to 8x per side (64x resolution). Achieve clean, high-quality visuals directly in Sketch with AI-powered tools that streamline your creative process. 43 | - [Place Linked Bitmap](https://github.com/frankko/place-linked-bitmap), by Frank Kolodziej: A plugin to place external bitmap files into Sketch and update Sketch layers after external bitmaps are updated 44 | - [Rename Instances](https://github.com/exevil/sketch-rename-instances), by Vyacheslav Dubovitsky: Tiny single-command Sketch plugin that renames every symbol instance with its master's name. 45 | - [represent.](https://github.com/swipecircus/represent), by Swipe Circus: Present your UX/UI Designs lightning fast on client's devices without leaving Sketch. 📲 46 | - [Segmented Circle](https://github.com/design4use/gb-sketch-segmentcircle), by German Bauer: Create precise segmented circular graphics for diagrams, instrumentation and analytics. 47 | - [Single Border](https://github.com/sureskumar/single-border), by Sures Kumar: Add single borders with advanced controls. 48 | - [Sketch Data Populator](https://datapopulator.com), by precious design studio: A Sketch App plugin to populate your documents with meaningful data. Goodbye Lorem Ipsum. Hello JSON. 49 | - [Sketch Guides](https://github.com/luvmex/sketch-guides), by Celyn Xie: Add Guides to edges and midpoints at once. 50 | - [Sketch Isometric](https://github.com/sureskumar/sketch-isometric), by Sures Kumar: Generate Isometric views from Artboards and Rectangles. 51 | - [Sketch Meaxure](https://github.com/qjebbs/sketch-meaxure), by utom & jebbs: Make it a fun to create spec for developers and teammates 52 | - [Sketch Move Half Pixel](https://github.com/canisminor1990/sketch-move-half-pixel), by Canis Minor: 🐾 Move layers half pixel 53 | - [Sketch Name Organizer](https://github.com/canisminor1990/sketch-name-organizer), by Canis Minor: 🖌 Rename artboards based on their x and y position; Rename layers based on their Style and Symbol. 54 | - [Sketch Pexels](https://github.com/pexels/pexels-sketchplugin), by Pexels: A Pexels plugin for Sketch 55 | - [Sketch Play](http://sureskumar.com/sketchplay/), by Sures Kumar: Create game plays directly from Sketch app. 56 | - [Sketch Recursive Export](https://github.com/commonpike/sketch-recursive-export), by commonpike: Simple plugin to recursively export exportable layers in a selected naming scheme 57 | - [Sketch Select](https://github.com/canisminor1990/sketch-select), by Canis Minor: Make it much more convenient to select layers with similar attributes. 58 | - [Sketch Style Master](https://github.com/aparajita/sketch-style-master), by Aparajita Fishman: Sketch plugin for renaming shared styles 59 | - [Sketch to Sitely Website Builder](https://sitely.app/docs/sketch-plugin.html), by Sitely: Export Sketch artboards to Sitely Website Builder. 60 | - [Sketch Wakatime](https://github.com/wakatime/sketch-wakatime), by WakaTime: A Plugin to update your WakaTime stats automatically from Sketch. 61 | - [Sketch Parser Plugn](https://github.com/Dilomen/schema-sketch-parser), by dilomen: Sketch设计稿数据解析 62 | - [Sketch Tailwind](https://github.com/jan-dh/sketch-tailwind), by Jan D'Hollander: Export your design to a theme-file you can use in your Tailwind Css config. 63 | - [Stark](http://www.getstark.co), by Stark Lab, Inc.: Ensure your design is accessible and high contrast for every type of color blindness. 64 | - [States](https://github.com/edenvidal/states), by Eden Vidal: Create different artboard states and switch between them easily 65 | - [Super Shapes](https://github.com/sureskumar/super-shapes), by Sures Kumar: Generate complex organic super-shapes using super formula. 66 | - [svgo for Elements](https://github.com/mogin-net/svgo-for-elements), by Pt. Mogin Technology Terpadu: 67 | - [Symbol Finder](https://github.com/afifkhaidir/symbol-finder), by afifkhaidir: Finder-like interface for browsing and inserting local symbols in Sketch 68 | - [Symbol Instance Locator](https://github.com/sonburn/symbol-instance-locator), by Jason Burns: Locate all instances of a selected symbol or instance. 69 | - [uiLogos](https://github.com/realvjy/uiLogos-sketch-plugin), by vijay verma: Insert professionally designed dummy logos of companies. 70 | - [WtDesign](https://github.com/wantedly/sketch-wt-design), by Yoshinori Kawasaki: Sketch plugin for Wantedly Design System 71 | - [Zeplin](https://zeplin.io), by Zeplin: Zeplin Sketch Plugin. 72 | - [zerozero](https://github.com/flavormingo/zerozero), by mazz: the sketch plugin that magically starts your page from (0,0) 73 | - [📕 PDF Export](https://github.com/dwilliames/pdf-export-sketch-plugin), by David Williames: Export all pages, current page or selected artboards into a PDF — based on a range of settings 74 | 75 | 76 | ## Sorted by last update (newest on top) 77 | 78 | - [Frontify Plugin](https://github.com/Frontify/sketch), by Frontify: Connecting your brand and design worlds 79 | - [Rename Instances](https://github.com/exevil/sketch-rename-instances), by Vyacheslav Dubovitsky: Tiny single-command Sketch plugin that renames every symbol instance with its master's name. 80 | - [Phosphor Icons](https://phosphoricons.com), by Tobias Fried: A flexible icon family for interfaces, diagrams, presentations — whatever, really. 81 | - [Abstract](https://goabstract.com/), by Abstract: Abstract lets you manage, version, and document your designs in one place 82 | - [Sketch Tailwind](https://github.com/jan-dh/sketch-tailwind), by Jan D'Hollander: Export your design to a theme-file you can use in your Tailwind Css config. 83 | - [Miro Plugin for Sketch](https://github.com/miroapp/sketch_plugin), by Miro: Add, share and discuss your Sketch artboards with the team in Miro. The plugin allow to sync the artboards with the boards in one click 84 | - [Zeplin](https://zeplin.io), by Zeplin: Zeplin Sketch Plugin. 85 | - [svgo for Elements](https://github.com/mogin-net/svgo-for-elements), by Pt. Mogin Technology Terpadu: 86 | - [Sketch Meaxure](https://github.com/qjebbs/sketch-meaxure), by utom & jebbs: Make it a fun to create spec for developers and teammates 87 | - [Batch Create Symbols](https://github.com/demersdesigns/sketch-batch-create-symbols), by Paul Demers: A plugin for Sketch to convert selected layers to individual symbols. 88 | - [Picsart Bg Remover & Image Enhancer](https://picsart.io), by PicsArt, Inc: Elevate your Sketch designs with the Picsart Plugin: effortlessly remove backgrounds from product photos, cars, furniture, jewelry, and more, and upscale images by up to 8x per side (64x resolution). Achieve clean, high-quality visuals directly in Sketch with AI-powered tools that streamline your creative process. 89 | - [Sketch Pexels](https://github.com/pexels/pexels-sketchplugin), by Pexels: A Pexels plugin for Sketch 90 | - [Sketch Recursive Export](https://github.com/commonpike/sketch-recursive-export), by commonpike: Simple plugin to recursively export exportable layers in a selected naming scheme 91 | - [Brandfolder](https://github.com/brandfolder/sketch-plugin-brandfolder), by brandfolder: Easily use your digital assets from Brandfolder right within your favorite platform for digital design! 92 | - [Codia Ai Design: Screenshot to Editable Sketch Design](https://codia.ai/image-to-sketch), by Codia.AI: Transform screenshots into editable Sketch UI designs effortlessly. Simply upload a snapshot of an app or website, and let our tool do the rest. Then, begin our design journey. 93 | - [represent.](https://github.com/swipecircus/represent), by Swipe Circus: Present your UX/UI Designs lightning fast on client's devices without leaving Sketch. 📲 94 | - [Crowdin for Sketch](https://github.com/crowdin/sketch-crowdin), by Crowdin: Localize the UI before programming starts. Translate and preview any design with ease 95 | - [killswitch](https://github.com/madebysnacks/killswitch), by Snacks Studio: A simple Sketch plugin for disabling Symbol overrides. 96 | - [zerozero](https://github.com/flavormingo/zerozero), by mazz: the sketch plugin that magically starts your page from (0,0) 97 | - [Artboard Tools](https://github.com/frankko/artboard-tools), by Frank Kolodziej: Sketch.app plugins for arranging artboards and navigating between artboards. 98 | - [PersianSupplier](https://github.com/hiradary/persiansupplier), by Hirad Arshadi: Sketch Plugin for supplying dynamic persian data. 99 | - [GxDesignOps](https://github.com/genexuslabs/sketchdesignops), by Gastón Milano: Sketch Plugin For GeneXus internal use 100 | - [Awesome Ipsums](https://github.com/inVoltag/awesome-ipsums), by Aurélien Grimaud: Generate ipsums from an external Google spreadsheet. Make these datas collaborative! 101 | - [Place Linked Bitmap](https://github.com/frankko/place-linked-bitmap), by Frank Kolodziej: A plugin to place external bitmap files into Sketch and update Sketch layers after external bitmaps are updated 102 | - [lippy](https://github.com/abynim/lippy), by Aby Nimbalkar: An interactive lorem ipsum generator plugin for Sketch 103 | - [Brandfetch](https://github.com/Brandfetch/Brandfetch-Sketch-Plugin), by Brandfetch: Pull brand assets into Sketch 104 | - [Easy Code](https://github.com/luchaoqun125/sketch-version/), by luchaoqun125: easy code 105 | - [Looper](https://github.com/sureskumar/looper), by Sures Kumar: Looper helps automate duplication of groups and layers. One can control properties like Rotate, Scale and Opacity while duplicating. This powerful combination enables artists and designers to create interesting geometric and organic patterns. 106 | - [Sketch Play](http://sureskumar.com/sketchplay/), by Sures Kumar: Create game plays directly from Sketch app. 107 | - [Super Shapes](https://github.com/sureskumar/super-shapes), by Sures Kumar: Generate complex organic super-shapes using super formula. 108 | - [Palette Stripes](https://github.com/sureskumar/palette-stripes), by Sures Kumar: Generate palette stripes in sketch from shape fills. 109 | - [Single Border](https://github.com/sureskumar/single-border), by Sures Kumar: Add single borders with advanced controls. 110 | - [Sketch Isometric](https://github.com/sureskumar/sketch-isometric), by Sures Kumar: Generate Isometric views from Artboards and Rectangles. 111 | - [AEUX](https://github.com/google/sketch2ae), by Battle Axe: Transfer layer to After Effects 112 | - [uiLogos](https://github.com/realvjy/uiLogos-sketch-plugin), by vijay verma: Insert professionally designed dummy logos of companies. 113 | - [📕 PDF Export](https://github.com/dwilliames/pdf-export-sketch-plugin), by David Williames: Export all pages, current page or selected artboards into a PDF — based on a range of settings 114 | - [Sketch Move Half Pixel](https://github.com/canisminor1990/sketch-move-half-pixel), by Canis Minor: 🐾 Move layers half pixel 115 | - [Stark](http://www.getstark.co), by Stark Lab, Inc.: Ensure your design is accessible and high contrast for every type of color blindness. 116 | - [Sketch Guides](https://github.com/luvmex/sketch-guides), by Celyn Xie: Add Guides to edges and midpoints at once. 117 | - [CodeWave](https://github.com/netease-lcap/sketch-plugin), by liuyiqi: 导出 Sketch 设计稿中的任意部分到 CodeWave 118 | - [autorename](https://github.com/dchahla/autorename), by dchahla: Lightweight, low-fi Auto Name Group ( ⌘ + g ) Sketch Plugin 119 | - [Sketch Data Populator](https://datapopulator.com), by precious design studio: A Sketch App plugin to populate your documents with meaningful data. Goodbye Lorem Ipsum. Hello JSON. 120 | - [Symbol Finder](https://github.com/afifkhaidir/symbol-finder), by afifkhaidir: Finder-like interface for browsing and inserting local symbols in Sketch 121 | - [Export to Proto.io](https://proto.io), by Proto.io: Give life to your Sketch designs! Export all or selected artboards to Proto.io screens, preserving layer positioning and hierarchy. By exporting to Proto.io you can link screens together, add interactions and animations. 122 | - [Find And Replace](https://github.com/thierryc/sketch-find-and-replace), by Martin Steven. Maintained by Thierry Charbonnel: Sketch 3 plugin to do a simple find and replace on text within layers 123 | - [Paste to Fill](https://github.com/tgfjt/sketch-paste-to-fill#readme), by tgfjt: Paste your image from clipboard, to fill layer. 124 | - [Sketch Select](https://github.com/canisminor1990/sketch-select), by Canis Minor: Make it much more convenient to select layers with similar attributes. 125 | - [WtDesign](https://github.com/wantedly/sketch-wt-design), by Yoshinori Kawasaki: Sketch plugin for Wantedly Design System 126 | - [Anto](https://github.com/canisminor1990/anto), by CanisMinor: Sketch Tools for AFX Designers 127 | - [Sketch Name Organizer](https://github.com/canisminor1990/sketch-name-organizer), by Canis Minor: 🖌 Rename artboards based on their x and y position; Rename layers based on their Style and Symbol. 128 | - [App Asset Export](https://github.com/work4blue/sketch-app-asset-export), by Andrew Huang: Sketch3 plugin, One key export Android/iOS App icons. 129 | - [Outsystems ui Plugin](https://github.com/HiInteractive/OutSystemsUIPlugin), by Pedro Oliveira: 130 | - [Color Copy Paste](https://github.com/sonnylazuardi/color-copy-paste), by Sonny Lazuardi Hermawan: 131 | - [imgcook](https://www.imgcook.com), by Taobao FED: A cook who can transform design to code 132 | - [Sketch Style Master](https://github.com/aparajita/sketch-style-master), by Aparajita Fishman: Sketch plugin for renaming shared styles 133 | - [File Cleaner](https://github.com/monzo/file-cleaner), by Monzo: Keep your Sketch files immaculately clean and in order. 134 | - [Artboard Manager](https://github.com/bomberstudios/artboard-manager#readme), by Ale Muñoz: Because moving artboards manually is *so* 2016. 135 | - [Segmented Circle](https://github.com/design4use/gb-sketch-segmentcircle), by German Bauer: Create precise segmented circular graphics for diagrams, instrumentation and analytics. 136 | - [Sketch Wakatime](https://github.com/wakatime/sketch-wakatime), by WakaTime: A Plugin to update your WakaTime stats automatically from Sketch. 137 | - [Import SVG as Artboards](https://github.com/mathieudutour/import-svg-as-artboard), by mathieudutour: Import SVG files as Artboards. 138 | - [Symbol Instance Locator](https://github.com/sonburn/symbol-instance-locator), by Jason Burns: Locate all instances of a selected symbol or instance. 139 | - [css to Shadow Style](https://github.com/oliverpitsch/CSS-Shadow-to-Sketch-Style-Plugin), by Oliver Pitsch: CSS box-shadow to Layer Style Converter 140 | - [States](https://github.com/edenvidal/states), by Eden Vidal: Create different artboard states and switch between them easily 141 | - [Cloudinary Plugin](https://github.com/cloudinary-devs/cloudinary-sketch-plugin), by Maya Shavin: Cloudinary plugin for Sketch projects 142 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | 3 | require 'json' 4 | require 'time' 5 | require "./lib/plugin-directory-utils" 6 | 7 | GITHUB_AUTH_TOKEN = ENV['GITHUB_TOKEN'] 8 | 9 | def title_for plugin 10 | title = plugin['title'] || plugin['name'] 11 | return titlefy(title) 12 | end 13 | 14 | def fix_plugin_title plugin 15 | if (plugin['name'] == plugin['title'] && !(IGNORE.include? plugin['title'])) || plugin['title'] == nil 16 | # puts "— #{plugin['name']} - #{plugin['title']}: Plugin title is wrong, fixing" 17 | plugin['title'] = titlefy(plugin['name']) 18 | end 19 | end 20 | 21 | def get_plugins_from_json 22 | data = IO.read('plugins.json') 23 | data.force_encoding('utf-8') 24 | JSON.parse(data) 25 | end 26 | 27 | def is_plugin_too_old? plugin 28 | if plugin['lastUpdated'] 29 | last_update = Time.parse(plugin['lastUpdated']) 30 | # If plugin is more than two years old, it is probably not maintained 31 | return (Time.now - last_update) > 60_000_000 32 | else 33 | return false 34 | end 35 | end 36 | 37 | desc "Clones all repositories to the 'clones' folder" 38 | task :clone do 39 | mkdir "clones" unless File.directory? "clones" 40 | get_plugins_from_json.each do |plugin| 41 | name = plugin['name'] 42 | owner = plugin['owner'] 43 | url = "https://github.com/#{owner}/#{name}" 44 | system("git clone #{url} clones/#{owner}-#{name}") 45 | end 46 | end 47 | 48 | desc "Updates all clones in the 'clones' folder" 49 | task :update do 50 | get_plugins_from_json.each do |plugin| 51 | name = plugin['name'] 52 | owner = plugin['owner'] 53 | url = "https://github.com/#{owner}/#{name}" 54 | 55 | if File.directory? "clones/#{owner}-#{name}" 56 | puts "Updating #{owner}-#{name} to latest version" 57 | system("cd clones/#{owner}-#{name}/ && git up") 58 | else 59 | puts "Cloning #{owner}-#{name} to latest version" 60 | system("git clone #{url} clones/#{owner}-#{name}") 61 | end 62 | end 63 | end 64 | 65 | desc "Generate README.md from plugins.json" 66 | task :readme do 67 | 68 | plugins = get_plugins_from_json 69 | 70 | output = < " ")) 143 | end 144 | end 145 | 146 | desc "Update `lastUpdated` field for all plugin in JSON" 147 | task :lastUpdated do 148 | 149 | require 'octokit' 150 | client = Octokit::Client.new(:access_token => GITHUB_AUTH_TOKEN) 151 | 152 | json_data = get_plugins_from_json 153 | 154 | json_data.each do |plugin| 155 | # Only check for last push date for plugins with a repo 156 | if plugin['owner'] && plugin['name'] 157 | # puts "Updating #{titlefy(plugin['name'])}" 158 | plugin_url = plugin['owner'] + "/" + plugin['name'] 159 | begin 160 | repo = client.repo(plugin_url) 161 | # user = client.user(plugin['owner']) 162 | # puts "— Plugin was updated at #{repo.pushed_at}" 163 | plugin['lastUpdated'] = repo.pushed_at 164 | rescue Exception => e 165 | puts e 166 | puts "https://github.com/#{plugin['owner']}/#{plugin['name']}" 167 | end 168 | 169 | # if plugin['name'] == plugin['title'] && plugin['title'] == nil 170 | # puts "— Plugin title is wrong, fixing" 171 | # plugin['title'] = titlefy(plugin['name']) 172 | # end 173 | else 174 | if plugin['lastUpdated'].nil? 175 | puts plugin['name'] 176 | puts plugin['title'] 177 | puts "— Plugin is not on GitHub, you may want to manually add a date" 178 | end 179 | end 180 | puts 181 | end 182 | 183 | File.open("plugins.json","w") do |f| 184 | f.write(JSON.pretty_generate(json_data, :indent => " ")) 185 | end 186 | 187 | end 188 | 189 | desc "List authors" 190 | task :authors do 191 | plugins = get_plugins_from_json 192 | authors = plugins.collect { |plugin| (plugin['author'] ? plugin['author'].downcase + " (" + plugin['owner'].downcase + ")" : plugin['owner'].downcase ) }.uniq.sort 193 | puts authors 194 | puts "\n" + authors.size.to_s + " unique authors." 195 | end 196 | 197 | desc "Interactive JSON: prompt user for information about their plugin and generate plugins.json and README.md" 198 | task :interactive do 199 | begin 200 | plugin = prompt_for_plugin() 201 | 202 | # Save JSON 203 | save_json(plugin) 204 | STDOUT.puts "plugins.json saved." 205 | 206 | # Create readme 207 | Rake::Task["readme"].invoke 208 | STDOUT.puts "README.md updated." 209 | 210 | STDOUT.puts "To commit your plugin to the plugin directory, open a pull request for https://github.com/sketchplugins/plugin-directory/." 211 | rescue SystemExit, Interrupt # Allow for interrupt to leave script 212 | exit 213 | end 214 | end 215 | 216 | def prompt_for_plugin() 217 | if prompt_yes_no("Is your plugin hosted on GitHub?") 218 | return prompt_gh_plugin() 219 | else 220 | return prompt_inputs({}, false) 221 | end 222 | end 223 | 224 | def prompt_gh_plugin() 225 | STDOUT.puts "What is the URL of your GitHub project?" 226 | input = STDIN.gets.strip 227 | matches = input.match('(https?://)?github.com/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)/?') 228 | owner = matches[2] 229 | name = matches[3] 230 | 231 | if owner && name 232 | STDOUT.puts "Repository owner: '#{owner}', name: '#{name}'" 233 | 234 | # Get info from GitHub 235 | gh_defaults = get_gh_defaults(owner, name) 236 | 237 | # Use branch to get manifest.json, but don't include in final saved JSON 238 | branch = gh_defaults.delete("branch") 239 | 240 | # Get info from manifest.json file hosted on Git?Hub 241 | manifest_defaults = get_manifest_defaults(owner, name, branch) 242 | 243 | # Combine info into default values 244 | defaults = gh_defaults.merge(manifest_defaults) 245 | 246 | # Prompt for plugin 247 | return prompt_inputs(defaults, true) 248 | else 249 | STDOUT.puts "ERROR: '#{input}' is not a valid GitHub repository." 250 | prompt_gh_url() 251 | end 252 | end 253 | 254 | def prompt_inputs(defaults, has_github) 255 | plugin = defaults 256 | if !has_github 257 | plugin['owner'] = prompt_owner() 258 | end 259 | 260 | plugin['title'] = prompt_item("Enter a title for your plugin", defaults["title"], !has_github) 261 | plugin['description'] = prompt_item("Describe your plugin in 1-2 sentences", defaults["description"], true) 262 | plugin['homepage'] = prompt_item("Enter the homepage for your plugin", defaults["homepage"], !has_github) 263 | plugin['author'] = prompt_item("Enter your name", defaults["author"], false) 264 | 265 | # Filter out nil values 266 | plugin = plugin.select{|k,v| v != nil} 267 | 268 | # Get string representation of plugin options 269 | str = plugin.map{|k,v| "\t#{k}: #{v}"}.join("\n") 270 | 271 | STDOUT.puts "Your plugin:\n" + str 272 | 273 | if !prompt_yes_no("Is the above information correct?") 274 | return prompt_inputs(defaults, has_github) 275 | end 276 | 277 | return plugin 278 | end 279 | 280 | def prompt_owner() 281 | owner = prompt_item("Enter the person or organisation who owns this plugin (no spaces or special characters)", nil, true) 282 | 283 | if !(owner =~ /^[a-zA-Z0-9_\-]+$/) 284 | STDOUT.puts "ERROR: Owner can only contain letters, numbers, hyphen, or underscores." 285 | return prompt_owner() 286 | end 287 | 288 | return owner 289 | end 290 | 291 | def prompt_item(prompt, default, required) 292 | if required 293 | prompt = prompt + " (required)" 294 | else 295 | prompt = prompt + " (optional)" 296 | end 297 | 298 | prompt = prompt + ": " 299 | 300 | if default 301 | prompt = prompt + "#{default}" 302 | end 303 | 304 | STDOUT.puts prompt 305 | input = STDIN.gets.strip 306 | 307 | if input.to_s.empty? && default 308 | return default 309 | elsif input.to_s.empty? && !required 310 | return nil 311 | elsif !(input.to_s.empty?) 312 | return input 313 | end 314 | 315 | STDOUT.puts "ERROR: Required field" 316 | return prompt_item(prompt, default, required) 317 | end 318 | 319 | def get_gh_defaults(owner, name) 320 | require 'octokit' 321 | client = Octokit::Client.new() 322 | repo_name = "#{owner}/#{name}" 323 | defaults = {} 324 | 325 | STDOUT.puts "Checking for '#{repo_name}' on GitHub..." 326 | begin 327 | repo = client.repo(repo_name) 328 | 329 | defaults["owner"] = owner 330 | defaults["name"] = name 331 | 332 | if !(repo.homepage.to_s.empty?) 333 | defaults["homepage"] = repo.homepage 334 | else 335 | defaults["homepage"] = repo.html_url 336 | end 337 | 338 | if !(repo.description.to_s.empty?) 339 | defaults["description"] = repo.description 340 | end 341 | 342 | defaults["lastUpdated"] = repo.updated_at 343 | defaults["branch"] = repo.default_branch 344 | 345 | return defaults 346 | rescue SystemExit, Interrupt # Allow for interrupt to leave script 347 | exit 348 | rescue # Error contacting GitHub API 349 | STDOUT.puts "ERROR: The repository '#{repo_name}' does not exist or is not publically accessible on GitHub." 350 | exit 351 | end 352 | end 353 | 354 | def get_manifest_defaults(owner, name, branch) 355 | content = get_manifest_content(owner, name, branch) 356 | if !content 357 | return {} 358 | end 359 | 360 | defaults = {} 361 | begin 362 | manifest = JSON.parse(content) 363 | 364 | name = manifest['name'] 365 | description = manifest['description'] 366 | author = manifest['author'] 367 | homepage = manifest['homepage'] 368 | identifier = manifest['identifier'] 369 | version = manifest['version'] 370 | 371 | error = false 372 | if identifier.to_s.empty? 373 | error = true 374 | STDOUT.puts "ERROR: The manifest.json file does not contain an identifier." 375 | end 376 | if version.to_s.empty? 377 | error = true 378 | STDOUT.puts "ERROR: The manifest.json file does not contain a version." 379 | end 380 | 381 | if error 382 | STDOUT.puts "\tWe suggest that you update your plugin's manifest.json file to contain these values before adding it to the plugin-directory." 383 | STDOUT.puts "\tSee Sketch's requirements for plugin bundles: http://developer.sketchapp.com/introduction/plugin-bundles/" 384 | 385 | prompt_continue() 386 | end 387 | 388 | if !(name.to_s.empty?) 389 | defaults['title'] = name 390 | end 391 | if !(author.to_s.empty?) 392 | defaults['author'] = author 393 | end 394 | if !(homepage.to_s.empty?) 395 | defaults['homepage'] = homepage 396 | end 397 | if !(description.to_s.empty?) 398 | defaults['description'] = description 399 | end 400 | 401 | return defaults 402 | 403 | rescue JSON::ParserError 404 | STDOUT.puts "ERROR: The manifest.json file for this plugin is not valid JSON." 405 | STDOUT.puts "\tWe suggest that you update your plugin to contain a valid manifest.json file before adding it to the plugin-directory." 406 | STDOUT.puts "\tTry using a JSON validator to check its contents: https://jsonformatter.curiousconcept.com/" 407 | 408 | prompt_continue() 409 | 410 | return {} 411 | end 412 | end 413 | 414 | def get_manifest_content(owner, name, branch) 415 | require 'octokit' 416 | client = Octokit::Client.new() 417 | repo_name = "#{owner}/#{name}" 418 | 419 | STDOUT.puts "Checking for manifest.json file in '#{repo_name}' on GitHub..." 420 | begin 421 | tree = client.tree(repo_name, branch, :recursive => true) 422 | 423 | tree.tree.each do |item| 424 | path = item.path 425 | if !(path.downcase.end_with? ".sketchplugin/contents/sketch/manifest.json") 426 | next 427 | end 428 | 429 | sha = item.sha 430 | 431 | blob = client.blob(repo_name, sha) 432 | encoding = blob.encoding 433 | content = blob.content 434 | if encoding == "base64" 435 | content = Base64.decode64(content) 436 | end 437 | 438 | return content 439 | end 440 | 441 | STDOUT.puts "ERROR: The plugin in the repository '#{repo_name}' does not have a manifest.json file." 442 | STDOUT.puts "\tWe suggest that you update your plugin to contain a valid manifest.json file before adding it to the plugin-directory." 443 | STDOUT.puts "\tSee Sketch's requirements for plugin bundles: http://developer.sketchapp.com/introduction/plugin-bundles/" 444 | 445 | prompt_continue() 446 | 447 | return nil 448 | 449 | rescue SystemExit, Interrupt # Allow for interrupt to leave script 450 | exit 451 | rescue # Error contacting GitHub API 452 | puts e 453 | STDOUT.puts "ERROR: The repository '#{repo_name}' does not exist or is not publically accessible on GitHub." 454 | exit 455 | end 456 | end 457 | 458 | def prompt_continue() 459 | if !prompt_yes_no("Are you sure you want to continue?") 460 | exit 461 | end 462 | end 463 | 464 | def prompt_yes_no(prompt) 465 | STDOUT.puts prompt + " (y/n)" 466 | input = STDIN.gets.strip 467 | if input.downcase.start_with?('y') 468 | return true 469 | elsif input.downcase.start_with?('n') 470 | return false 471 | else 472 | STDOUT.puts "ERROR: Respond with 'y' or 'n'." 473 | return prompt_yes_no(prompt) 474 | end 475 | end 476 | 477 | def save_json(plugin) 478 | # Get current plugins 479 | plugins = get_plugins_from_json 480 | 481 | # Check for duplicates 482 | existing_plugin_index = check_for_duplicates(plugin, plugins) 483 | 484 | if existing_plugin_index >= 0 485 | if prompt_yes_no("Would you like to update the existing plugin instead?") 486 | plugins[existing_plugin_index] = plugin 487 | else 488 | exit 489 | end 490 | else 491 | plugins << plugin 492 | end 493 | 494 | File.open("plugins.json","w") do |f| 495 | f.write(JSON.pretty_generate(plugins, :indent => " ")) 496 | end 497 | end 498 | 499 | def check_for_duplicates(plugin, plugins) 500 | puts "Checking for duplicates for #{plugin}" 501 | plugins.each_with_index do |saved_plugin, index| 502 | plugin_name = plugin["name"] || plugin["title"] 503 | plugin_owner = plugin["owner"] || plugin["author"] 504 | saved_plugin_name = saved_plugin["name"] || saved_plugin["title"] 505 | saved_plugin_owner = saved_plugin["owner"] || saved_plugin["author"] 506 | 507 | puts "#{plugin_name} - #{plugin_owner}" 508 | puts "#{saved_plugin_name} - #{saved_plugin_owner}" 509 | 510 | if plugin_name.downcase == saved_plugin_name.downcase && plugin_owner.downcase == saved_plugin_owner.downcase 511 | STDOUT.puts "ERROR: Found another plugin with the same owner & title." 512 | return index 513 | end 514 | end 515 | return -1 516 | end 517 | 518 | desc "Print Star Ranking" 519 | task :stars do 520 | 521 | require 'octokit' 522 | client = Octokit::Client.new(:access_token => GITHUB_AUTH_TOKEN) 523 | json_data = get_plugins_from_json 524 | json_data.each do |plugin| 525 | # Only check for last push date for plugins with a repo 526 | if plugin['owner'] && plugin['name'] 527 | plugin_url = plugin['owner'] + "/" + plugin['name'] 528 | begin 529 | repo = client.repo(plugin_url) 530 | puts "#{repo.watchers},#{titlefy(plugin['name'])}" 531 | # user = client.user(plugin['owner']) 532 | # puts "— Plugin was updated at #{repo.pushed_at}" 533 | # plugin['lastUpdated'] = repo.pushed_at 534 | rescue Exception => e 535 | puts "— Repo not available for #{plugin_url}" 536 | end 537 | 538 | # if plugin['name'] == plugin['title'] && plugin['title'] == nil 539 | # puts "— Plugin title is wrong, fixing" 540 | # plugin['title'] = titlefy(plugin['name']) 541 | # end 542 | end 543 | end 544 | 545 | # File.open("plugins.json","w") do |f| 546 | # f.write(JSON.pretty_generate(json_data, :indent => " ")) 547 | # end 548 | 549 | 550 | end 551 | 552 | desc "Default: generate README.md from plugin" 553 | task :default => :readme 554 | -------------------------------------------------------------------------------- /add: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'json' 3 | require 'octokit' 4 | require './lib/plugin-directory-utils' 5 | 6 | GITHUB_AUTH_TOKEN = `git config com.bohemiancoding.qa.token`.strip 7 | USERNAME = `git config github.user`.strip 8 | 9 | # TODO: add to Rakefile 10 | 11 | if ARGV[0].nil? 12 | puts "Usage: #{File.basename(__FILE__)} username/reponame" 13 | exit 14 | end 15 | 16 | # First, update the local repo to avoid conflicts later on 17 | # (use git-up if it's installed) 18 | if `which git-up`.empty? 19 | `git pull --rebase` 20 | else 21 | `git up` 22 | end 23 | 24 | plugin_url = ARGV[0] 25 | 26 | # Generate the new plugin hash 27 | owner = plugin_url.split("/")[0] 28 | name = plugin_url.split("/")[1] 29 | 30 | client = Octokit::Client.new(:access_token => GITHUB_AUTH_TOKEN) 31 | repo = client.repo(plugin_url) 32 | user = client.user(owner) 33 | 34 | new_plugin = { 35 | :title => titlefy(name), 36 | :description => repo.description, 37 | :author => user.name || owner, 38 | :owner => owner, 39 | :name => name, 40 | :lastUpdated => repo.updated_at 41 | } 42 | 43 | # Load the JSON file with all the plugins 44 | file = File.read('plugins.json') 45 | json_data = JSON.parse(file) 46 | 47 | # make sure we're not adding an existing plugin 48 | existing_plugin = json_data.select{ |item| item['name'] == name && item['owner'] == owner } 49 | 50 | if existing_plugin.count == 0 51 | # Add plugin & save file 52 | json_data.push(new_plugin) 53 | 54 | File.open("plugins.json","w") do |f| 55 | f.write(JSON.pretty_generate(json_data, :indent => " ")) 56 | end 57 | 58 | puts "We now have #{json_data.length} plugins on the list" 59 | system("git add plugins.json") 60 | log_msg = "Added #{owner}/#{name}" 61 | if ARGV[1] 62 | log_msg += ". Closes ##{ARGV[1]}" 63 | end 64 | system("git commit -m '#{log_msg}'") 65 | else 66 | puts "Plugin was already on the list, so let's update it" 67 | existing_plugin[0] 68 | end 69 | 70 | puts "We have #{json_data.length} plugins on the list" 71 | -------------------------------------------------------------------------------- /lib/plugin-directory-utils.rb: -------------------------------------------------------------------------------- 1 | # This is used on the titlefy function. The idea here is to ignore some word that should never be 2 | # re-capitalised 3 | IGNORE = %w(the of a and to for as by in with px iOS iPhone iPad SketchContentSync LayerRenamer SketchRunner Gridy Looper SizeArtboard Shapr nSlicer Click-Thru ColorSpark ImageOptim LaTeX PaintCode RealtimeBoard DevTools TinyFaces NoPrint CloudApp ViewController SelectPlus) 4 | UPCASE = %w(ae ps html ui sf css rtl ps html ui sf json jsx scss rgb hsl hex vr svg svgo pdf png sd ds afux qr wcag vk) 5 | 6 | def titlefy string 7 | if IGNORE.include? (string) 8 | return string 9 | end 10 | s = string.gsub('.sketchplugin','').gsub('-',' ').split(' ') 11 | if s.count == 1 12 | return string 13 | end 14 | 15 | # puts "Words: #{s}" 16 | s.map do |word| 17 | word_lowercase = word.downcase 18 | if IGNORE.include?(word) 19 | word 20 | elsif UPCASE.include? (word.downcase) 21 | word.upcase 22 | else 23 | word.capitalize! 24 | end 25 | end 26 | title = s.join(' ') 27 | # puts title 28 | return title 29 | end 30 | -------------------------------------------------------------------------------- /stats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'octokit' 4 | require 'json' 5 | 6 | GITHUB_AUTH_TOKEN = `git config com.bohemiancoding.qa.token`.strip 7 | ISSUES_PER_PAGE = 2000 8 | USERNAME = `git config github.user`.strip 9 | 10 | client = Octokit::Client.new(:access_token => GITHUB_AUTH_TOKEN) 11 | 12 | file = File.read('plugins.json') 13 | json_data = JSON.parse(file) 14 | total_plugin_count = json_data.size 15 | plugin_count = 0 16 | 17 | open('plugin-stats.md', 'w') { |f| 18 | f.puts "# Plugin stats - #{Time.now}" 19 | f.puts 20 | f.puts "Total plugins: #{total_plugin_count}" 21 | f.puts 22 | } 23 | open('plugin-stats.csv', 'w') { |f| 24 | f.puts "Name,Last Pushed,Stars,Open Issues,URL" 25 | } 26 | 27 | json_data.each do |plugin| 28 | plugin_name = plugin['name'] 29 | plugin_title = plugin['title'] || plugin_name 30 | plugin_owner = plugin['owner'] 31 | plugin_author = plugin['author'] 32 | 33 | plugin_count += 1 34 | 35 | puts "#{plugin_count} / #{total_plugin_count} : #{plugin_name}" 36 | 37 | if plugin_name && plugin_owner 38 | begin 39 | repo_name = plugin_owner + '/' + plugin_name 40 | repo = client.repo(repo_name) 41 | updated_days_ago = ((Time.now - repo.pushed_at) / (60 * 60 * 24)).to_i 42 | 43 | open('plugin-stats.md', 'a') { |f| 44 | f.puts 45 | f.puts "#{plugin_count}. [#{plugin_title}](https://github.com/#{repo_name})" 46 | f.puts 47 | f.puts " Last push: #{repo.pushed_at} (#{updated_days_ago} days ago)" 48 | if updated_days_ago > 900 49 | f.puts " FIXME: Remove this from directory" 50 | end 51 | f.puts " Stars: #{repo.stargazers_count}" 52 | f.puts " Open issues: #{repo.open_issues_count}" 53 | } 54 | 55 | open('plugin-stats.csv', 'a') { |f| 56 | f.puts "#{plugin_title.gsub(',','')},#{repo.pushed_at},#{repo.stargazers_count},#{repo.open_issues_count},https://github.com/#{repo_name}" 57 | } 58 | rescue 59 | # puts "Plugin has no repo." 60 | open('plugin-stats.md', 'a') { |f| 61 | f.puts 62 | f.puts "#{plugin_count}. #{repo_name}" 63 | f.puts 64 | f.puts " FIXME: #{repo_name} has no repo" 65 | f.puts 66 | } 67 | end 68 | end 69 | # # 70 | end 71 | 72 | puts 73 | puts "#{total_plugin_count} plugins" 74 | --------------------------------------------------------------------------------