├── .editorconfig ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .npmrc ├── .nvmrc ├── .vscode-test.mjs ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── commitlint.config.js ├── docs ├── cli-commands.md ├── context-menu.md ├── generate-files.md ├── images │ ├── commands.png │ ├── create-project.gif │ ├── demo.gif │ ├── json.gif │ ├── menu-general.png │ ├── menu-prisma.png │ ├── preview.png │ ├── settings.png │ ├── snippets.png │ └── workspace-settings.png └── snippets.md ├── icon.png ├── package-lock.json ├── package.json ├── schemas └── config.schema.json ├── snippets ├── drizzle.code-snippets ├── html.code-snippets ├── i18next.code-snippets ├── javascript.code-snippets ├── nextauth.code-snippets ├── nextjs.code-snippets ├── prisma.code-snippets ├── reactjs.code-snippets ├── tailwindcss.code-snippets ├── trpc.code-snippets └── zod.code-snippets ├── src ├── app │ ├── configs │ │ ├── config.ts │ │ ├── constants.ts │ │ └── index.ts │ ├── controllers │ │ ├── feedback.controller.ts │ │ ├── file.controller.ts │ │ ├── index.ts │ │ ├── list-files.controller.ts │ │ ├── terminal.controller.ts │ │ └── transform.controller.ts │ ├── helpers │ │ ├── command.helper.ts │ │ ├── dialog.helper.ts │ │ ├── filesystem.helper.ts │ │ ├── index.ts │ │ └── inflector.helper.ts │ ├── models │ │ ├── index.ts │ │ └── node.model.ts │ └── providers │ │ ├── feedback.provider.ts │ │ ├── index.ts │ │ ├── list-components.providers.ts │ │ ├── list-files.providers.ts │ │ ├── list-hooks.providers.ts │ │ └── list-routes.providers.ts ├── assets │ └── logo.svg ├── extension.ts └── test │ └── extension.test.ts ├── tsconfig.doc.json ├── tsconfig.json └── vsc-extension-quickstart.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*.{ts,json}] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 2 10 | indent_style = space 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | insert_final_newline = true 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # prettier v2 dictates LF 2 | # https://prettier.io/docs/en/options.html#end-of-line 3 | * text=auto eol=lf 4 | 5 | .vscode/*.json linguist-language=jsonc 6 | tslint.json linguist-language=jsonc 7 | tsconfig.json linguist-language=jsonc 8 | tsconfig.*.json linguist-language=jsonc 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ManuelGil] 4 | ko_fi: ManuelGil 5 | custom: 6 | ['https://paypal.me/ManuelFGil', 'https://www.buymeacoffee.com/ManuelGil'] 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode-test/ 2 | *.vsix 3 | compodoc/ 4 | dist 5 | node_modules 6 | out 7 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "./**/*.ts": ["biome format --write", "biome lint --write", "git add ."] 3 | } 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.10.0 2 | -------------------------------------------------------------------------------- /.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@vscode/test-cli'; 2 | 3 | export default defineConfig({ 4 | files: 'out/test/**/*.test.js', 5 | }); 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "biomejs.biome" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "${defaultBuildTask}" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "[typescript]": { 12 | "editor.defaultFormatter": "biomejs.biome" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .husky/** 2 | .vscode/** 3 | .vscode-test/** 4 | out/test/** 5 | compodoc/** 6 | src/** 7 | .editorconfig 8 | .eslintignore 9 | .eslintrc.json 10 | .gitattributes 11 | .gitignore 12 | .lintstagedrc 13 | .prettierignore 14 | .prettierrc 15 | .yarnrc 16 | biome.json 17 | commitlint.config.js 18 | yarn.lock 19 | vsc-extension-quickstart.md 20 | **/tsconfig.json 21 | **/tsconfig.*.json 22 | **/.eslintrc.json 23 | **/*.map 24 | **/*.ts 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "T3 Stack / NextJS / ReactJS File Generator for VSCode" extension will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [2.6.0] - 2025-01-09 11 | 12 | ### Added 13 | 14 | - Add version extension check to show a message when the extension is outdated 15 | - Add `biome` linting and formatting tool to the project 16 | - Add `biome` configuration file to define the linting and formatting rules 17 | - Add VS Code test configuration and update test scripts 18 | 19 | ### Changed 20 | 21 | - Improve the welcome and update messages in the extension 22 | - Upgrade dependencies to the latest versions available 23 | 24 | ## [2.5.1] - 2024-11-07 25 | 26 | ### Fixed 27 | 28 | - Fix the `json2ts` and `json2zod` issues when the selection does not have a JSON strict format 29 | 30 | ## [2.5.0] - 2024-04-01 31 | 32 | ### Changed 33 | 34 | - Update the `Drizzle` snippets to integrate the new `Drizzle` functionality 35 | - Update the `cli-commands.md` file with the new `Drizzle` commands 36 | - Update the `README.md` file to improve the contribution guidelines 37 | - Upgrade dependencies to the latest versions available 38 | 39 | ## [2.4.0] - 2024-03-28 40 | 41 | ### Changed 42 | 43 | - Update the `getFiles` method in the `ListFilesController` so that it can be used without instantiating the class 44 | - Update the `ListFilesProvider` and `ListRoutesProvider` to use the new `getFiles` method 45 | - Upgrade dependencies to the latest versions available 46 | 47 | ## [2.3.0] - 2024-03-07 48 | 49 | ### Added 50 | 51 | - Add List of Hooks View 52 | 53 | ### Changed 54 | 55 | - Improve the route regex to match the routes in the file 56 | 57 | ## [2.2.0] - 2024-03-04 58 | 59 | ### Added 60 | 61 | - Add `Show Path In File Name` setting to show the path in the file name 62 | 63 | ## [2.1.0] - 2024-03-02 64 | 65 | ### Added 66 | 67 | - Add List of Components View 68 | 69 | ### Fixed 70 | 71 | - Fix issues with the file generation 72 | 73 | ## [2.0.0] - 2024-03-01 74 | 75 | ### Added 76 | 77 | - Add List of Files View 78 | - Add List of Routes View 79 | - Add Feedback View 80 | - Add file includes section to the settings 81 | - Add file excludes section to the settings 82 | - Add file to watch section to the settings 83 | - Add compodoc dependencies for the documentation generation 84 | 85 | ### Changed 86 | 87 | - Refactor the folder structure of the extension to improve the codebase 88 | - Improve the generation of the files to use the new folder structure 89 | - Upgrade dependencies to the latest versions available 90 | - Update settings to use the new folder structure 91 | - Improve the documentation of the extension 92 | 93 | ### Fixed 94 | 95 | - Fix issues related to the new folder structure 96 | 97 | ## [1.5.0] - 2024-01-22 98 | 99 | ### Added 100 | 101 | - Add new `Drizzle` snippets 102 | 103 | ## [1.4.0] - 2024-01-22 104 | 105 | ### Added 106 | 107 | - Add `Drizzle` snippets and commands 108 | 109 | ## [1.3.0] - 2024-01-14 110 | 111 | ### Added 112 | 113 | - Add convert `json` to `typescript` and `zod` object commands 114 | 115 | ### Changed 116 | 117 | - Improve file generation 118 | 119 | ## [1.2.0] - 2024-01-08 120 | 121 | ### Added 122 | 123 | - Add settings for experimental https function for NextJS server 124 | 125 | ## [1.1.1] - 2023-12-27 126 | 127 | ### Added 128 | 129 | - Add new documentation for Features, Commands, and Snippets 130 | 131 | ## [1.1.0] - 2023-12-26 132 | 133 | ### Added 134 | 135 | - Add settings for the default import alias for `tRPC` files 136 | 137 | ## [1.0.1] - 2023-12-24 138 | 139 | ### Changed 140 | 141 | - Update `README.md` file 142 | 143 | ## [1.0.0] - 2023-12-24 144 | 145 | ### Added 146 | 147 | - Add `NextAuth` commands for generating `[...nextauth].ts` file 148 | - Add `tRPC` commands for generating `Router` and `Controller` files 149 | - Add `NextAuth` snippets 150 | - Add `tRPC` snippets 151 | - Add `TailwindCSS` snippets 152 | - Add `i18next` snippets 153 | - Add `zod` snippets 154 | - Add configuration for showing the type in the name of the generated files (e.g. `index.component.tsx`) 155 | - Add new context menu for `NextAuth` and `tRPC` files, and `Prisma` commands 156 | 157 | ### Changed 158 | 159 | - Reorganize all project commands in a new `Project` category for better organization 160 | - Update Page and Layout commands removing the class name input and add a default value of `Page` and `Layout` respectively 161 | - Rename all commands from `Next` to `t3` to avoid conflicts with other extensions commands that use `Next` as a prefix 162 | 163 | ### Removed 164 | 165 | - Remove plain text scope from all snippets to avoid conflicts with `GitHub Copilot Chat` and other extensions 166 | 167 | ## [0.5.1] - 2023-12-19 168 | 169 | ### Fixed 170 | 171 | - Fix typos in `package.json` 172 | 173 | ## [0.5.0] - 2023-12-19 174 | 175 | ### Added 176 | 177 | - Add `JavaScript` snippets 178 | - Add `NextJS` commands 179 | 180 | ### Changed 181 | 182 | - Update `HTML` and `RectJS` snippets for better organization and readability of the code 183 | 184 | ### Fixed 185 | 186 | - Fix default extension name in `NextJS` commands 187 | 188 | ## [0.4.0] - 2023-12-17 189 | 190 | ### Added 191 | 192 | - Add `Prisma` commands for `model`, `migration`, and `seed` generation, and `studio` commands 193 | - Add `Prisma` snippets 194 | 195 | ### Changed 196 | 197 | - Update `ReactJS` Snippets 198 | 199 | ## [0.3.0] - 2023-12-16 200 | 201 | ### Added 202 | 203 | - Add `HTML` snippets 204 | - Add `ReactJS` snippets 205 | 206 | ## [0.2.0] - 2023-12-15 207 | 208 | ### Added 209 | 210 | - Add `Create App` commands for `T3 Stack`, `NextJS`, and `ReactJS` projects 211 | 212 | ## [0.1.0] - 2023-12-14 213 | 214 | - Initial release 215 | 216 | [unreleased]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v2.6.0...HEAD 217 | [2.6.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v2.5.1...v2.6.0 218 | [2.5.1]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v2.5.0...v2.5.1 219 | [2.5.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v2.4.0...v2.5.0 220 | [2.4.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v2.3.0...v2.4.0 221 | [2.3.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v2.2.0...v2.3.0 222 | [2.2.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v2.1.0...v2.2.0 223 | [2.1.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v2.0.0...v2.1.0 224 | [2.0.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v1.5.0...v2.0.0 225 | [1.5.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v1.4.0...v1.5.0 226 | [1.4.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v1.3.0...v1.4.0 227 | [1.3.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v1.2.0...v1.3.0 228 | [1.2.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v1.1.1...v1.2.0 229 | [1.1.1]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v1.1.0...v1.1.1 230 | [1.1.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v1.0.1...v1.1.0 231 | [1.0.1]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v1.0.0...v1.0.1 232 | [1.0.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v0.5.1...v1.0.0 233 | [0.5.1]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v0.5.0...v0.5.1 234 | [0.5.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v0.4.0...v0.5.0 235 | [0.4.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v0.3.0...v0.4.0 236 | [0.3.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v0.2.0...v0.3.0 237 | [0.2.0]: https://github.com/ManuelGil/vscode-nextjs-generator/compare/v0.1.0...v0.2.0 238 | [0.1.0]: https://github.com/ManuelGil/vscode-nextjs-generator/releases/tag/v0.1.0 239 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [INSERT CONTACT METHOD]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | - Using welcoming and inclusive language 36 | - Being respectful of differing viewpoints and experiences 37 | - Gracefully accepting constructive criticism 38 | - Focusing on what is best for the community 39 | - Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | - The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | - Trolling, insulting/derogatory comments, and personal or political attacks 46 | - Public or private harassment 47 | - Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | - Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Manuel Gil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # T3 Stack / NextJS / ReactJS File Generator 2 | 3 | [![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/imgildev.vscode-nextjs-generator?style=for-the-badge&label=VS%20Marketplace&logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-nextjs-generator) 4 | [![Visual Studio Marketplace Installs](https://img.shields.io/visual-studio-marketplace/i/imgildev.vscode-nextjs-generator?style=for-the-badge&logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-nextjs-generator) 5 | [![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/imgildev.vscode-nextjs-generator?style=for-the-badge&logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-nextjs-generator) 6 | [![Visual Studio Marketplace Rating](https://img.shields.io/visual-studio-marketplace/r/imgildev.vscode-nextjs-generator?style=for-the-badge&logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-nextjs-generator&ssr=false#review-details) 7 | [![GitHub Repo stars](https://img.shields.io/github/stars/ManuelGil/vscode-nextjs-generator?style=for-the-badge&logo=github)](https://github.com/ManuelGil/vscode-nextjs-generator) 8 | [![GitHub license](https://img.shields.io/github/license/ManuelGil/vscode-nextjs-generator?style=for-the-badge&logo=github)](https://github.com/ManuelGil/vscode-nextjs-generator/blob/main/LICENSE) 9 | 10 |

11 | T3 Stack, Next and React File Generator - Effortless file generation | Product Hunt 12 |

13 | 14 | Elevate your development workflow with our cutting-edge extension tailored for NextJS 14 (compatibility for version 13). Designed as the quintessential development companion, this toolset redefines file generation, optimizing every phase of your project's lifecycle. Seamlessly create pages, components, layouts, and more—all meticulously crafted to align with the esteemed T3 Stack paradigm. Leverage the capabilities of advanced technologies like NextJS, ReactJS, Prisma, Drizzle, TailwindCSS, i18next, Zod, and numerous other essential frameworks. 15 | 16 | ![demo](https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/main/docs/images/demo.gif) 17 | 18 | Ready to transcend your development experience? 19 | 20 | Boost your efficiency with this VSCode extension, designed to streamline file generation for your T3 Stack project. Whether crafting individual components or kickstarting a new venture, the extension simplifies tasks through intuitive commands. Additionally, initiate your NextJS server effortlessly, enabling swift previews of your application. 21 | 22 | ![preview](https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/main/docs/images/preview.png) 23 | 24 | ## Index 25 | 26 | - [T3 Stack / NextJS / ReactJS File Generator](#t3-stack--nextjs--reactjs-file-generator) 27 | - [Index](#index) 28 | - [Requirements](#requirements) 29 | - [Create a New Project](#create-a-new-project) 30 | - [Project Settings](#project-settings) 31 | - [Features](#features) 32 | - [Commands to Create Files](#commands-to-create-files) 33 | - [Terminal Commands](#terminal-commands) 34 | - [Snippets](#snippets) 35 | - [Context Menu](#context-menu) 36 | - [Follow Me](#follow-me) 37 | - [VSXpert Template](#vsxpert-template) 38 | - [Other Extensions](#other-extensions) 39 | - [Contributing](#contributing) 40 | - [Code of Conduct](#code-of-conduct) 41 | - [Changelog](#changelog) 42 | - [Authors](#authors) 43 | - [License](#license) 44 | 45 | ## Requirements 46 | 47 | - VSCode 1.90.0 or later 48 | 49 | ## Create a New Project 50 | 51 | You can create a new project using the T3 Stack / NextJS / Vite CLI. To do so, open the command palette in VSCode: 52 | 53 | - `CTRL + SHIFT + P` (Windows) 54 | - `CMD + SHIFT + P` (Mac OS) 55 | 56 | Type `T3: Create Project` and press `ENTER`. 57 | 58 | ![create-project](https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/main/docs/images/create-project.gif) 59 | 60 | ## Project Settings 61 | 62 | Configure your project by creating or updating a settings.json file at the project's root. If you already have a `.vscode/settings.json` file, skip the first two steps. 63 | 64 | 1. Open the command palette in VSCode: 65 | 66 | - `CTRL + SHIFT + P` (Windows) 67 | - `CMD + SHIFT + P` (Mac OS) 68 | 69 | 2. Type `Preferences: Open Workspace Settings (JSON)`. 70 | 71 | 3. In the `.vscode/settings.json` file, copy and paste the following settings: 72 | 73 | ```jsonc 74 | { 75 | "nextjs.files.alias": "~", // The import alias for the files to be created. Example: "~", "@", "#", etc 76 | "nextjs.files.extension": "tsx", // The extension of the files to be created. Example: "tsx" 77 | "nextjs.files.showType": true, // Show the type of the file in the file name. Example: "home.component.tsx" 78 | "nextjs.files.include": [ 79 | "js", 80 | "jsx", 81 | "ts", 82 | "tsx" 83 | ], // The file extensions to watch for changes. Example: "js", "jsx", "ts", "tsx" 84 | "nextjs.files.exclude": [ 85 | "**/node_modules/**", 86 | "**/dist/**", 87 | "**/out/**", 88 | "**/build/**", 89 | "**/.*/**" 90 | ], // The files to exclude from watching. Example: "**/node_modules/**", "**/dist/**", "**/out/**", "**/build/**", "**/.*/**" 91 | "nextjs.files.watch": [ 92 | "controllers", 93 | "components", 94 | "routers" 95 | ], // The types of files to watch for changes. Example: "controllers", "components", "routers" 96 | "nextjs.files.showPath": true, // Show the path of the file in the file name. Example: "home.component.tsx (pages/home)" 97 | "nextjs.server.turbo": true, // Enable Turbo Mode for NextJS server (Only for NextJS 14 or later) 98 | "nextjs.server.experimentalHttps": true, // Enable HTTPS for the NextJS server (Only for NextJS 14 or later) 99 | } 100 | ``` 101 | 102 | 4. **Restart VS Code** 103 | 104 | Your project is now set up to automatically format code upon saving. 105 | 106 | ## Features 107 | 108 | ### Commands to Create Files 109 | 110 | See the following documentation about [how to create files](./docs/generate-files.md) for more information. 111 | 112 | ### Terminal Commands 113 | 114 | See the following documentation about [how to use the terminal commands](./docs/cli-commands.md) for more information. 115 | 116 | ### Snippets 117 | 118 | See the following documentation about [how to use the snippets](./docs/snippets.md) for more information. 119 | 120 | ### Context Menu 121 | 122 | See the following documentation about [how to use the context menu](./docs/context-menu.md) for more information. 123 | 124 | ## Follow Me 125 | 126 | If you enjoy using this extension, consider following me for updates on this and future projects: 127 | 128 | [![GitHub followers](https://img.shields.io/github/followers/ManuelGil?style=for-the-badge&logo=github)](https://github.com/ManuelGil) 129 | [![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/imgildev?style=for-the-badge&logo=x)](https://twitter.com/imgildev) 130 | 131 | ## VSXpert Template 132 | 133 | This extension was created using [VSXpert](https://vsxpert.com), a template that helps you create Visual Studio Code extensions with ease. VSXpert provides a simple and easy-to-use structure to get you started quickly. 134 | 135 | ## Other Extensions 136 | 137 | - [Angular File Generator](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-angular-generator) 138 | - [NestJS File Generator](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-nestjs-generator) 139 | - [T3 Stack / NextJS / ReactJS File Generator](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-nextjs-generator) 140 | - [Auto Barrel](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-auto-barrel) 141 | - [CodeIgniter 4 Spark](https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-codeigniter4-spark) 142 | 143 | ## Contributing 144 | 145 | T3 Stack / NextJS / ReactJS File Generator for VSCode is open-source software, and we welcome contributions from the community. If you'd like to contribute, please fork the [GitHub repository](https://github.com/ManuelGil/vscode-nextjs-generator) and submit a pull request with your changes. 146 | 147 | Before contributing, please read our [Contribution Guidelines](./CONTRIBUTING.md) for instructions on coding standards, testing, and more. 148 | 149 | ## Code of Conduct 150 | 151 | We are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender, sexual orientation, disability, ethnicity, religion, or similar personal characteristic. Please review our [Code of Conduct](./CODE_OF_CONDUCT.md) before participating in our community. 152 | 153 | ## Changelog 154 | 155 | For a complete list of changes, see the [CHANGELOG.md](./CHANGELOG.md) 156 | 157 | ## Authors 158 | 159 | - **Manuel Gil** - _Owner_ - [ManuelGil](https://github.com/ManuelGil) 160 | 161 | See also the list of [contributors](https://github.com/ManuelGil/vscode-nextjs-generator/contributors) who participated in this project. 162 | 163 | ## License 164 | 165 | T3 Stack / NextJS / ReactJS File Generator for VSCode is licensed under the MIT License - see the [MIT License](https://opensource.org/licenses/MIT) for details. 166 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, 4 | "files": { "include": ["src/**/*.ts"], "ignoreUnknown": false, "ignore": [] }, 5 | "formatter": { 6 | "enabled": true, 7 | "useEditorconfig": true, 8 | "formatWithErrors": false, 9 | "indentStyle": "space", 10 | "indentWidth": 2, 11 | "lineEnding": "lf", 12 | "lineWidth": 80, 13 | "attributePosition": "auto", 14 | "bracketSpacing": true, 15 | "ignore": [ 16 | "./.vscode", 17 | "./compodoc", 18 | "./coverage", 19 | "./docs", 20 | "./node_modules", 21 | "./out" 22 | ] 23 | }, 24 | "organizeImports": { "enabled": true }, 25 | "linter": { 26 | "enabled": true, 27 | "rules": { 28 | "recommended": false, 29 | "style": { 30 | "useBlockStatements": "warn", 31 | "useNamingConvention": { 32 | "level": "warn", 33 | "options": { "strictCase": false } 34 | }, 35 | "useThrowOnlyError": "warn" 36 | }, 37 | "suspicious": { "noDoubleEquals": "warn" } 38 | }, 39 | "ignore": [ 40 | "**/out", 41 | "**/*.d.ts", 42 | "**/.vscode/", 43 | "**/compodoc/", 44 | "**/coverage/", 45 | "**/docs/", 46 | "**/node_modules/", 47 | "**/out/", 48 | "**/commitlint.config.js" 49 | ] 50 | }, 51 | "javascript": { 52 | "formatter": { 53 | "jsxQuoteStyle": "double", 54 | "quoteProperties": "preserve", 55 | "trailingCommas": "all", 56 | "semicolons": "always", 57 | "arrowParentheses": "always", 58 | "bracketSameLine": false, 59 | "quoteStyle": "single", 60 | "attributePosition": "auto", 61 | "bracketSpacing": true 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/cli-commands.md: -------------------------------------------------------------------------------- 1 | # How to use the terminal commands 2 | 3 | ## Index 4 | 5 | - [How to use the terminal commands](#how-to-use-the-terminal-commands) 6 | - [Index](#index) 7 | - [Commands](#commands) 8 | - [Usage](#usage) 9 | - [Settings](#settings) 10 | 11 | ## Commands 12 | 13 | | Title | Purpose | 14 | | ------------------------- | ---------------------------------------------------------------------------- | 15 | | T3: Create Project | Creates a new project using the T3 Stack / NextJS / Vite CLI | 16 | | T3: Prisma DB Execute | Execute native commands to your database | 17 | | T3: Prisma DB Pull | Pull the state from the database to the Prisma schema using introspection | 18 | | T3: Prisma DB Push | Push the state from your Prisma schema to your database | 19 | | T3: Prisma DB Seed | Seed your database | 20 | | T3: Prisma Format | Format a Prisma schema | 21 | | T3: Prisma Generate | Generate artifacts | 22 | | T3: Prisma Init | Set up a new Prisma project | 23 | | T3: Prisma Migrate Deploy | Apply pending migrations to update the database schema in production/staging | 24 | | T3: Prisma Migrate Dev | Create a migration from changes in Prisma schema | 25 | | T3: Prisma Migrate Reset | Reset your database and apply all migrations, all data will be lost | 26 | | T3: Prisma Migrate Status | Check the status of your database migrations | 27 | | T3: Prisma Studio | Browse your data with Prisma Studio | 28 | | T3: Prisma Validate | Validate a Prisma schema | 29 | | T3: Drizzle Generate | Generate migrations based on you Drizzle schema | 30 | | T3: Drizzle Pull | Pull DDL from existing database | 31 | | T3: Drizzle Push | Push your schema changes directly to the database | 32 | | T3: Drizzle Drop | Delete previously generated migrations from migrations folder | 33 | | T3: Drizzle Up | Utility command to keep all metadata up to date | 34 | | T3: Drizzle Check | It's a very powerful tool for you to check consistency of your migrations | 35 | | T3: Drizzle Studio | Launch Drizzle Studio database browser locally from you config file | 36 | | T3: Start Server | Builds and serves your application, rebuilding on file changes | 37 | 38 | ## Usage 39 | 40 | ![commands](https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/main/docs/images/commands.png) 41 | 42 | ![menu-prisma](https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/main/docs/images/menu-prisma.png) 43 | 44 | ## Settings 45 | 46 | In the `.vscode/settings.json` file, copy and paste the following settings: 47 | 48 | ```jsonc 49 | { 50 | "nextjs.server.turbo": true, 51 | "nextjs.server.experimentalHttps": true, 52 | } 53 | ``` 54 | 55 | - `nextjs.server.turbo`: Turbo mode is a new mode that enables incremental compilation and dramatically improves the startup time of Next.js development servers. It's only available in Next.js 14 or newer. 56 | - `nextjs.server.experimentalHttps`: Enable HTTPS for the NextJS server. This is an experimental feature and it's only available in Next.js 14 or newer. You can read more about it [here](https://nextjs.org/docs/pages/api-reference/next-cli#https-for-local-development). 57 | -------------------------------------------------------------------------------- /docs/context-menu.md: -------------------------------------------------------------------------------- 1 | # How to use the snippets 2 | 3 | ## Index 4 | 5 | - [How to use the snippets](#how-to-use-the-snippets) 6 | - [Index](#index) 7 | - [Commands](#commands) 8 | - [Usage](#usage) 9 | 10 | ## Commands 11 | 12 | | Title | Purpose | 13 | | -------------------------- | ------------------------------------------------ | 14 | | Convert JSON to TypeScript | Create a TypeScript interface from a JSON object | 15 | | Convert JSON to Zod | Create a Zod schema from a JSON object | 16 | 17 | ## Usage 18 | 19 | ![json](https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/main/docs/images/json.gif) 20 | -------------------------------------------------------------------------------- /docs/generate-files.md: -------------------------------------------------------------------------------- 1 | # How to create files 2 | 3 | ## Index 4 | 5 | - [How to create files](#how-to-create-files) 6 | - [Index](#index) 7 | - [Commands](#commands) 8 | - [Usage](#usage) 9 | - [Settings](#settings) 10 | 11 | ## Commands 12 | 13 | | Title | Purpose | 14 | | ------------------------------- | ---------------------------------------------------- | 15 | | T3: Generate Class or Interface | Creates a new, generic class or interface definition | 16 | | T3: Generate Component | Creates a new, generic component | 17 | | T3: Generate Layout | Creates a new, generic layout | 18 | | T3: Generate Loading | Creates a new, generic loading component | 19 | | T3: Generate Page | Creates a new, generic page | 20 | | T3: Add NextAuth file | Adds a new file called [...nextauth].ts | 21 | | T3: Generate tRPC Router | Creates a new, generic tRPC router | 22 | | T3: Generate tRPC Controller | Creates a new, generic tRPC controller | 23 | 24 | ## Usage 25 | 26 | ![commands](https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/main/docs/images/commands.png) 27 | 28 | ![menu-general](https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/main/docs/images/menu-general.png) 29 | 30 | ## Settings 31 | 32 | In the `.vscode/settings.json` file, copy and paste the following settings: 33 | 34 | ```jsonc 35 | { 36 | "nextjs.files.alias": "~", 37 | "nextjs.files.extension": "tsx", 38 | "nextjs.files.showType": true, 39 | "nextjs.files.include": [ 40 | "js", 41 | "jsx", 42 | "ts", 43 | "tsx" 44 | ], // The file extensions to watch for changes. Example: "js", "jsx", "ts", "tsx" 45 | "nextjs.files.exclude": [ 46 | "**/node_modules/**", 47 | "**/dist/**", 48 | "**/out/**", 49 | "**/build/**", 50 | "**/.*/**" 51 | ], // The files to exclude from watching. Example: "**/node_modules/**", "**/dist/**", "**/out/**", "**/build/**", "**/.*/**" 52 | "nextjs.files.watch": [ 53 | "controllers", 54 | "components", 55 | "routers" 56 | ], // The types of files to watch for changes. Example: "controllers", "components", "routers" 57 | "nextjs.files.showPath": true, // Show the path of the file in the file name. Example: "home.component.tsx (pages/home)" 58 | } 59 | ``` 60 | 61 | - `nextjs.files.alias`: The alias to use for the import statement. For example, if you use `~` as an alias, the import statement will look like this: `import { Component } from '~/components'` 62 | - `nextjs.files.extension`: The extension to use for the file. For example, if you use `tsx` as an extension, the file will be created as `component.tsx` 63 | - `nextjs.files.showType`: Whether to show the type of the component. For example, if you use `true` as a value, the file will be created as `home.component.tsx` and if you use `false` as a value, the file will be created as `home.ts` 64 | - `nextjs.files.include`: The file extensions to watch for changes. For example, if you use `ts` and `tsx` as extensions, the extension will watch for changes in `.ts` and `.tsx` files 65 | - `nextjs.files.exclude`: The files to exclude from watching. For example, if you use `**/node_modules/**` and `**/dist/**` as exclusions, the extension will not watch for changes in the `node_modules` and `dist` folders 66 | - `nextjs.files.watch`: The types of files to watch for changes. For example, if you use `components` and `routers` as types, the extension will watch for changes with the `component` and `router` type in the file name. For example, `home.component.tsx` and `home.router.tsx` 67 | - `nextjs.files.showPath`: Whether to show the path of the file in the file name. For example, if you use `true` as a value, the file will be created as `home.component.tsx (pages/home)` and if you use `false` as a value, the file will be created as `home.component.tsx` 68 | -------------------------------------------------------------------------------- /docs/images/commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/commands.png -------------------------------------------------------------------------------- /docs/images/create-project.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/create-project.gif -------------------------------------------------------------------------------- /docs/images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/demo.gif -------------------------------------------------------------------------------- /docs/images/json.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/json.gif -------------------------------------------------------------------------------- /docs/images/menu-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/menu-general.png -------------------------------------------------------------------------------- /docs/images/menu-prisma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/menu-prisma.png -------------------------------------------------------------------------------- /docs/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/preview.png -------------------------------------------------------------------------------- /docs/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/settings.png -------------------------------------------------------------------------------- /docs/images/snippets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/snippets.png -------------------------------------------------------------------------------- /docs/images/workspace-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/docs/images/workspace-settings.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManuelGil/vscode-nextjs-generator/f768bc0218dced2cff3ca7e0521fc68104b4a6f0/icon.png -------------------------------------------------------------------------------- /schemas/config.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "type": "object", 4 | "properties": { 5 | "nextjs.files.alias": { 6 | "type": "string", 7 | "default": "~", 8 | "scope": "resource", 9 | "description": "Sets the default import alias for the generated files" 10 | }, 11 | "nextjs.files.extension": { 12 | "type": "string", 13 | "default": "tsx", 14 | "enum": [ 15 | "js", 16 | "jsx", 17 | "ts", 18 | "tsx" 19 | ], 20 | "scope": "resource", 21 | "description": "Sets the default extension for the generated files" 22 | }, 23 | "nextjs.files.showType": { 24 | "type": "boolean", 25 | "default": true, 26 | "scope": "resource", 27 | "description": "Show the type in the name of the generated files" 28 | }, 29 | "nextjs.files.include": { 30 | "type": "array", 31 | "default": [ 32 | "js", 33 | "jsx", 34 | "ts", 35 | "tsx" 36 | ], 37 | "scope": "resource", 38 | "description": "Glob patterns to include in the package. The default is js, jsx, ts, and tsx." 39 | }, 40 | "nextjs.files.exclude": { 41 | "type": "array", 42 | "default": [ 43 | "**/node_modules/**", 44 | "**/dist/**", 45 | "**/out/**", 46 | "**/build/**", 47 | "**/.*/**" 48 | ], 49 | "scope": "resource", 50 | "description": "Glob patterns to exclude from the package. The default is node_modules, dist, out, build, and any hidden files." 51 | }, 52 | "nextjs.files.watch": { 53 | "type": "array", 54 | "default": [ 55 | "controllers", 56 | "components", 57 | "routers" 58 | ], 59 | "scope": "resource", 60 | "description": "The list of directories to watch for changes" 61 | }, 62 | "nextjs.files.showPath": { 63 | "type": "boolean", 64 | "default": true, 65 | "scope": "resource", 66 | "description": "Show the path of the file in the name of the list of generated files" 67 | }, 68 | "nextjs.server.turbo": { 69 | "type": "boolean", 70 | "default": true, 71 | "scope": "resource", 72 | "description": "Star server in turbo mode (only for NextJS 14+)" 73 | }, 74 | "nextjs.server.experimentalHttps": { 75 | "type": "boolean", 76 | "default": false, 77 | "scope": "resource", 78 | "description": "Start server in https mode. Use `--experimental-https` flag (only for NextJS 14+)" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /snippets/i18next.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Trans": { 3 | "prefix": ["i18next_trans", "", "\t$0", ""] 6 | }, 7 | "useTranslation": { 8 | "prefix": ["i18next_use_translation", "useTranslation"], 9 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 10 | "body": ["const { t } = useTranslation();"] 11 | }, 12 | "LanguageDetector": { 13 | "prefix": ["i18next_language_detector", "LanguageDetector"], 14 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 15 | "body": [ 16 | "import i18next from 'i18next';", 17 | "import LanguageDetector from 'i18next-browser-languagedetector';", 18 | "", 19 | "i18next.use(LanguageDetector).init({", 20 | "\t// detection: options,", 21 | "});" 22 | ] 23 | }, 24 | "i18nextMiddleware": { 25 | "prefix": ["i18next_middleware", "i18nextMiddleware"], 26 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 27 | "body": [ 28 | "import i18next from 'i18next'", 29 | "import Backend from 'i18next-fs-backend'", 30 | "import i18nextMiddleware from 'i18next-http-middleware'", 31 | "", 32 | "i18next", 33 | "\t.use(i18nextMiddleware.LanguageDetector)", 34 | "\t.use(Backend)", 35 | "\t.init({", 36 | "\t\tinitImmediate: false,", 37 | "\t\tfallbackLng: 'en',", 38 | "\t\tpreload: ['en'],", 39 | "\t\tbackend: {", 40 | "\t\t\tloadPath: __dirname + '/resources/locales/{{lng}}/{{ns}}.json'", 41 | "\t\t}", 42 | "\t})", 43 | "" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /snippets/nextjs.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Image Component": { 3 | "prefix": ["next_image", "" 6 | }, 7 | "Link Component": { 8 | "prefix": ["next_link", "$2" 11 | }, 12 | "Page Component with Props and Params": { 13 | "prefix": [ 14 | "next_page_props_params", 15 | "Page Component with Props and Params" 16 | ], 17 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 18 | "body": [ 19 | "type ${1:name}Props = {", 20 | "\tparams: {", 21 | "\t\t${2:paramName}: string;", 22 | "\t}", 23 | "};", 24 | "", 25 | "export default function ${1:name}Page({", 26 | "\tparams", 27 | "} : ${1:name}Props) {", 28 | "\tconst { ${2:paramName} } = params;", 29 | "\treturn (", 30 | "\t\t${3:
", 31 | "\t\t\t

$0

", 32 | "\t\t
}", 33 | "\t);", 34 | "}", 35 | "" 36 | ] 37 | }, 38 | "Page Component with Props": { 39 | "prefix": ["next_page_props", "Page Component with Props"], 40 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 41 | "body": [ 42 | "type ${1:name}PageProps = {", 43 | "\t$2", 44 | "};", 45 | "", 46 | "export default function ${1:name}Page({", 47 | "\t$3", 48 | "}: ${1:name}PageProps) {", 49 | "\t$4", 50 | "\treturn (", 51 | "\t\t${5:
", 52 | "\t\t\t

$0

", 53 | "\t\t
}", 54 | "\t);", 55 | "}", 56 | "" 57 | ] 58 | }, 59 | "Page Component": { 60 | "prefix": ["next_page", "Page Component"], 61 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 62 | "body": [ 63 | "", 64 | "export default function ${1:name}Page() {", 65 | "\treturn (", 66 | "\t\t${2:
", 67 | "\t\t\t

$0

", 68 | "\t\t
}", 69 | "\t);", 70 | "}", 71 | "" 72 | ] 73 | }, 74 | "Async Server Action Function": { 75 | "prefix": ["next_server_action_function", "Async Server Action Function"], 76 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 77 | "body": ["async function $1() {", "\t'use server';", "\t$2", "}", ""] 78 | }, 79 | "Client Component": { 80 | "prefix": ["next_client_component", "Client Component"], 81 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 82 | "body": [ 83 | "'use client';", 84 | "", 85 | "export default function ${1:name}() {", 86 | "\treturn (", 87 | "\t\t${2:
", 88 | "\t\t\t

$0

", 89 | "\t\t
}", 90 | "\t);", 91 | "}" 92 | ] 93 | }, 94 | "Error Component": { 95 | "prefix": ["next_error_component", "Error Component"], 96 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 97 | "body": [ 98 | "'use client';", 99 | "", 100 | "import { useEffect } from 'react';", 101 | "", 102 | "type ErrorProps = {", 103 | "\terror: Error & { digest?: string };", 104 | "\treset: () => void;", 105 | "};", 106 | "", 107 | "export default function Error({ error, reset }: ErrorProps) {", 108 | "\tuseEffect(() => {", 109 | "\t\tconsole.error(error);", 110 | "\t}, [error]);", 111 | "", 112 | "\t$1", 113 | "", 114 | "\treturn (", 115 | "\t\t
", 116 | "\t\t\t

{error.message}

", 117 | "\t\t\t", 118 | "\t\t
", 119 | "\t);", 120 | "}", 121 | "" 122 | ] 123 | }, 124 | "function generateMetaData": { 125 | "prefix": ["next_generate_metadata", "generateMetaData"], 126 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 127 | "body": [ 128 | "import type { Metadata } from 'next'", 129 | "", 130 | "export async function generateMetadata({ params }: { params: { [${2:slug}] }}): Promise {", 131 | "\tconst product = await ${1:getData}([${2:slug}]);", 132 | "\t return { title: product.title }", 133 | "}" 134 | ] 135 | }, 136 | "function generateStaticParams for Dynamic Page Static": { 137 | "prefix": [ 138 | "next_generate_static_params_dynamic", 139 | "generateStaticParams for Dynamic Page Static" 140 | ], 141 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 142 | "body": [ 143 | "", 144 | "export async function generateStaticParams() {", 145 | "\tconst posts = await fetch('${1:fetchData}').then((res) => res.json());", 146 | "", 147 | "\treturn posts.map((post) => ({", 148 | "\t\t slug: post.slug,", 149 | "\t}));", 150 | "}" 151 | ] 152 | }, 153 | "function Loading": { 154 | "prefix": ["next_loading_function", "Loading Function"], 155 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 156 | "body": [ 157 | "", 158 | "export default function Loading() {", 159 | "\t", 160 | "\treturn (
Loading...
)", 161 | "\t}" 162 | ] 163 | }, 164 | "function Route Handler API DELETE": { 165 | "prefix": ["next_route_handler_api_delete", "Route Handler API DELETE"], 166 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 167 | "body": [ 168 | "import { NextResponse, NextRequest } from 'next/server'", 169 | "", 170 | "export async function DELETE(req: Request) {", 171 | "\tconst { ${1:id} } = req.params;", 172 | "}" 173 | ] 174 | }, 175 | "function Route Handler API GET and Search": { 176 | "prefix": [ 177 | "next_route_handler_api_get_search", 178 | "Route Handler API GET and Search" 179 | ], 180 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 181 | "body": [ 182 | "import { NextResponse, NextRequest } from 'next/server'", 183 | "", 184 | "export async function GET(request: Request) { ", 185 | "", 186 | "const { searchParams } = new URL(request.url);", 187 | "const name = searchParams.get('name');", 188 | "", 189 | "\treturn NextResponse.json({ message: '$0' })", 190 | "}" 191 | ] 192 | }, 193 | "function Route Handler API GET with Dynamic": { 194 | "prefix": [ 195 | "next_route_handler_api_get_dynamic", 196 | "Route Handler API GET with Dynamic" 197 | ], 198 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 199 | "body": [ 200 | "import { NextResponse, NextRequest } from 'next/server'", 201 | "", 202 | "export async function GET(request: Request, context: { params: { ${1:slug}: string } }) { ", 203 | "", 204 | "\tconst { ${1:slug} } = context.params", 205 | "}" 206 | ] 207 | }, 208 | "function Route Handler API GET": { 209 | "prefix": ["next_route_handler_api_get", "Route Handler API GET"], 210 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 211 | "body": [ 212 | "import { NextResponse, NextRequest } from 'next/server'", 213 | "", 214 | "export async function GET() { ", 215 | "\treturn NextResponse.json({ message: '$0' })", 216 | "}" 217 | ] 218 | }, 219 | "function Route Handler API POST": { 220 | "prefix": ["next_route_handler_api_post", "Route Handler API POST"], 221 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 222 | "body": [ 223 | "import { NextResponse, NextRequest } from 'next/server'", 224 | "", 225 | "export async function POST(req: Request) {", 226 | "\tconst { ${1:title} } = await req.json();", 227 | "", 228 | "\treturn NextResponse.json({ message: '$0' })", 229 | "\t}" 230 | ] 231 | }, 232 | "function Route Handler API UPDATE": { 233 | "prefix": ["next_route_handler_api_update", "Route Handler API UPDATE"], 234 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 235 | "body": [ 236 | "import { NextResponse, NextRequest } from 'next/server'", 237 | "", 238 | "export async function PUT(req: Request) {", 239 | "\tconst { ${1:title} } = await req.json();", 240 | "", 241 | "\treturn NextResponse.json({ ${1:title} }, { status: 201 });", 242 | "\t}" 243 | ] 244 | }, 245 | "Generated Metadata": { 246 | "prefix": ["next_generated_metadata", "Generated Metadata"], 247 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 248 | "body": [ 249 | "import type { Metadata } from 'next';", 250 | "", 251 | "type GenerateMetadataProps = {", 252 | "\tparams: {", 253 | "\t\t$1: string", 254 | "\t}", 255 | "};", 256 | "", 257 | "export function generateMetadata(", 258 | "\t{ params }: GenerateMetadataProps", 259 | "): Metadata {", 260 | "\tconst $1 = params.$1;", 261 | "", 262 | "\treturn {", 263 | "\t\ttitle: $1,", 264 | "\t$2", 265 | "\t};", 266 | "}" 267 | ] 268 | }, 269 | "Global Error Component": { 270 | "prefix": ["next_global_error_component", "Global Error Component"], 271 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 272 | "body": [ 273 | "'use client';", 274 | "", 275 | "import { useEffect } from 'react';", 276 | "", 277 | "type GlobalErrorProps = {", 278 | "\terror: Error & { digest?: string };", 279 | "\treset: () => void;", 280 | "};", 281 | "", 282 | "export default function GlobalError({ error, reset }: GlobalErrorProps) {", 283 | "\tuseEffect(() => {", 284 | "\t\tconsole.error(error);", 285 | "\t}, [error]);", 286 | "", 287 | "\t$1", 288 | "", 289 | "\treturn (", 290 | "\t\t
", 291 | "\t\t\t

{error.message}

", 292 | "\t\t\t", 293 | "\t\t
", 294 | "\t);", 295 | "}", 296 | "" 297 | ] 298 | }, 299 | "Layout Component": { 300 | "prefix": ["next_layout_component", "Layout Component"], 301 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 302 | "body": [ 303 | "import type { ReactNode } from 'react';", 304 | "", 305 | "type LayoutProps = {", 306 | "\tchildren: ReactNode;", 307 | "};", 308 | "", 309 | "export default function Layout({ children }: LayoutProps) {", 310 | "\t$1", 311 | "\treturn (", 312 | "\t\t
", 313 | "\t\t\t{children}", 314 | "\t\t
", 315 | "\t);", 316 | "}", 317 | "" 318 | ] 319 | }, 320 | "Layout Root Component": { 321 | "prefix": ["next_layout_root", "Layout Root"], 322 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 323 | "body": [ 324 | "", 325 | "export default function ${1:name}Layout({", 326 | "\tchildren", 327 | "}: {", 328 | "\tchildren: React.ReactNode;", 329 | "}) {", 330 | "\treturn (", 331 | "\t\t${2:
", 332 | "\t\t\t

$0

", 333 | "\t\t
}", 334 | "\t);", 335 | "}" 336 | ] 337 | }, 338 | "Loading Component": { 339 | "prefix": ["next_loading_component", "Loading Component"], 340 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 341 | "body": [ 342 | "export default function Loading() {", 343 | "\t$1", 344 | "\treturn (", 345 | "\t\t
", 346 | "\t\t\t$2", 347 | "\t\t
", 348 | "\t);", 349 | "}", 350 | "" 351 | ] 352 | }, 353 | "Middleware": { 354 | "prefix": ["next_middleware", "Middleware"], 355 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 356 | "body": [ 357 | "import { NextResponse } from 'next/server';", 358 | "import type { NextRequest } from 'next/server';", 359 | "", 360 | "export function middleware(request: NextRequest) {", 361 | "\treturn NextResponse.redirect(new URL('/', request.url));", 362 | "}", 363 | "", 364 | "export const config = {", 365 | "\tmatcher: '$1',", 366 | "};" 367 | ] 368 | }, 369 | "Not Found Component": { 370 | "prefix": ["next_not_found_component", "Not Found Component"], 371 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 372 | "body": [ 373 | "import Link from 'next/link';", 374 | "", 375 | "export default function NotFound() {", 376 | "\t$1", 377 | "", 378 | "\treturn (", 379 | "\t\t
", 380 | "\t\t\t

Not Found

", 381 | "\t\t\t

Could not find requested resource

", 382 | "\t\t\tReturn Home", 383 | "\t\t
", 384 | "\t);", 385 | "}", 386 | "" 387 | ] 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /snippets/prisma.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "export default Prisma": { 3 | "prefix": ["prisma_export_default_prisma", "export default Prisma"], 4 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 5 | "body": [ 6 | "import { PrismaClient } from '@prisma/client'", 7 | "", 8 | "export default const prisma = new PrismaClient()", 9 | "" 10 | ] 11 | }, 12 | "id Int": { 13 | "prefix": ["prisma_id", "id Int"], 14 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 15 | "body": "id Int @id @default(autoincrement())" 16 | }, 17 | "updatedAt": { 18 | "prefix": ["prisma_updatedAt", "updatedAt"], 19 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 20 | "body": "updatedAt DateTime @updatedAt" 21 | }, 22 | "default now": { 23 | "prefix": ["prisma_default_now", "default now"], 24 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 25 | "body": "createdAt DateTime @default(now())" 26 | }, 27 | "default dbgenerated": { 28 | "prefix": ["prisma_default_dbgenerated", "default dbgenerated"], 29 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 30 | "body": "createdAt DateTime @default(dbgenerated())" 31 | }, 32 | "default cuid": { 33 | "prefix": ["prisma_default_cuid", "default cuid"], 34 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 35 | "body": "id String @id @default(cuid())" 36 | }, 37 | "default uuid": { 38 | "prefix": ["prisma_default_uuid", "default uuid"], 39 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 40 | "body": "id String @id @default(uuid())" 41 | }, 42 | "default sequential": { 43 | "prefix": ["prisma_default_sequential", "default sequential"], 44 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 45 | "body": "id Int @id @default(sequential())" 46 | }, 47 | "default random": { 48 | "prefix": ["prisma_default_random", "default random"], 49 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 50 | "body": "id Int @id @default(random())" 51 | }, 52 | "default anonymous": { 53 | "prefix": ["prisma_default_anonymous", "default anonymous"], 54 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 55 | "body": "id Int @id @default(anonymous())" 56 | }, 57 | "relation": { 58 | "prefix": ["prisma_relation", "relation"], 59 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 60 | "body": "@relation(fields: [$1Id], references: [id])" 61 | }, 62 | "unique": { 63 | "prefix": ["prisma_unique", "unique"], 64 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 65 | "body": "@@unique([$1])" 66 | }, 67 | "map": { 68 | "prefix": ["prisma_map", "map"], 69 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 70 | "body": "@@map($1)" 71 | }, 72 | "schema": { 73 | "prefix": ["prisma_schema", "schema"], 74 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 75 | "body": "@@schema($1)" 76 | }, 77 | "create": { 78 | "prefix": ["prisma_create", "create"], 79 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 80 | "body": ["await prisma.$1.create({", "\tdata: {", "\t\t$2", "\t}", "})", ""] 81 | }, 82 | "findMany": { 83 | "prefix": ["prisma_findMany", "findMany"], 84 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 85 | "body": ["await prisma.$1.findMany({", "\t$2", "})", ""] 86 | }, 87 | "findUnique": { 88 | "prefix": ["prisma_findUnique", "findUnique"], 89 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 90 | "body": ["await prisma.$1.findUnique({", "\t$2", "})", ""] 91 | }, 92 | "update": { 93 | "prefix": ["prisma_update", "update"], 94 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 95 | "body": ["await prisma.$1.update({", "\t$2", "})", ""] 96 | }, 97 | "delete": { 98 | "prefix": ["prisma_delete", "delete"], 99 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 100 | "body": ["await prisma.$1.delete({", "\t$2", "})", ""] 101 | }, 102 | "deleteMany": { 103 | "prefix": ["prisma_deleteMany", "deleteMany"], 104 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 105 | "body": ["await prisma.$1.deleteMany({", "\t$2", "})", ""] 106 | }, 107 | "upsert": { 108 | "prefix": ["prisma_upsert", "upsert"], 109 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 110 | "body": ["await prisma.$1.upsert({", "\t$2", "})", ""] 111 | }, 112 | "aggregate": { 113 | "prefix": ["prisma_aggregate", "aggregate"], 114 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 115 | "body": ["await prisma.$1.aggregate({", "\t$2", "})", ""] 116 | }, 117 | "count": { 118 | "prefix": ["prisma_count", "count"], 119 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 120 | "body": ["await prisma.$1.count({", "\t$2", "})", ""] 121 | }, 122 | "groupBy": { 123 | "prefix": ["prisma_groupBy", "groupBy"], 124 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 125 | "body": ["await prisma.$1.groupBy({", "\t$2", "})", ""] 126 | }, 127 | "findFirst": { 128 | "prefix": ["prisma_findFirst", "findFirst"], 129 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 130 | "body": ["await prisma.$1.findFirst({", "\t$2", "})", ""] 131 | }, 132 | "orderBy": { 133 | "prefix": ["prisma_orderBy", "orderBy"], 134 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 135 | "body": ["orderBy: {", "\t$1: $2", "}", ""] 136 | }, 137 | "where": { 138 | "prefix": ["prisma_where", "where"], 139 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 140 | "body": ["where: {", "\t$1: $2", "}", ""] 141 | }, 142 | "skip take": { 143 | "prefix": ["prisma_skip_take", "skip take"], 144 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 145 | "body": "skip: $1, take: $2" 146 | }, 147 | "include": { 148 | "prefix": ["prisma_include", "include"], 149 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 150 | "body": ["include: {", "\t$1: $2", "}", ""] 151 | }, 152 | "select": { 153 | "prefix": ["prisma_select", "select"], 154 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 155 | "body": ["select: {", "\t$1: $2", "}", ""] 156 | }, 157 | "transaction": { 158 | "prefix": ["prisma_transaction", "transaction"], 159 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 160 | "body": ["prisma.\\$transaction([", "\t$1", "])", ""] 161 | }, 162 | "queryRaw": { 163 | "prefix": ["prisma_queryRaw", "queryRaw"], 164 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 165 | "body": "prisma.\\$queryRaw($1)" 166 | }, 167 | "executeRaw": { 168 | "prefix": ["prisma_executeRaw", "executeRaw"], 169 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 170 | "body": "prisma.\\$executeRaw($1)" 171 | }, 172 | "use": { 173 | "prefix": ["prisma_use", "use"], 174 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 175 | "body": "prisma.\\$use($1)" 176 | }, 177 | "on": { 178 | "prefix": ["prisma_on", "on"], 179 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 180 | "body": "prisma.\\$on($1)" 181 | }, 182 | "disconnect": { 183 | "prefix": ["prisma_disconnect", "disconnect"], 184 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 185 | "body": "prisma.\\$disconnect()" 186 | }, 187 | "connect": { 188 | "prefix": ["prisma_connect", "connect"], 189 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 190 | "body": "prisma.\\$connect()" 191 | }, 192 | "queryRawUnsafe": { 193 | "prefix": ["prisma_queryRawUnsafe", "queryRawUnsafe"], 194 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 195 | "body": "prisma.\\$queryRawUnsafe($1)" 196 | }, 197 | "executeRawUnsafe": { 198 | "prefix": ["prisma_executeRawUnsafe", "executeRawUnsafe"], 199 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 200 | "body": "prisma.\\$executeRawUnsafe($1)" 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /snippets/trpc.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Server Standalone": { 3 | "prefix": ["trpc_server_standalone", "Server Standalone"], 4 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 5 | "body": [ 6 | "import { createHTTPServer } from '@trpc/server/adapters/standalone';", 7 | "", 8 | "const appRouter = router({", 9 | "\t$0", 10 | "});", 11 | "", 12 | "const server = createHTTPServer({", 13 | "\trouter: appRouter,", 14 | "});", 15 | "", 16 | "server.listen(3000);" 17 | ] 18 | }, 19 | "initTRPC": { 20 | "prefix": ["trpc_init", "initTRPC"], 21 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 22 | "body": [ 23 | "import { initTRPC } from '@trpc/server';", 24 | "", 25 | "const t = initTRPC.create();", 26 | "", 27 | "export const router = t.router;", 28 | "export const middleware = t.middleware;", 29 | "export const publicProcedure = t.procedure;" 30 | ] 31 | }, 32 | "createTRPCRouter": { 33 | "prefix": ["trpc_create_trpc_router", "createTRPCRouter"], 34 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 35 | "body": ["export const ${1:name}Router = createTRPCRouter({", "\t$0", "});"] 36 | }, 37 | "publicProcedure query": { 38 | "prefix": ["trpc_public_query", "publicProcedure query"], 39 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 40 | "body": "${1:prefix}: publicProcedure.input(${2:object}).query(async ({ ${3:args} }) => ${4:functionHandler}({ ${3:args} }))" 41 | }, 42 | "publicProcedure mutation": { 43 | "prefix": ["trpc_public_mutation", "publicProcedure mutation"], 44 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 45 | "body": "${1:prefix}: publicProcedure.input(${2:object}).mutation(async ({ ${3:args} }) => ${4:functionHandler}({ ${3:args} }))" 46 | }, 47 | "protectedProcedure query": { 48 | "prefix": ["trpc_protected_query", "protectedProcedure query"], 49 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 50 | "body": "${1:prefix}: protectedProcedure.input(${2:object}).query(async ({ ${3:args} }) => ${4:functionHandler}({ ${3:args} }))" 51 | }, 52 | "protectedProcedure mutation": { 53 | "prefix": ["trpc_protected_mutation", "protectedProcedure mutation"], 54 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 55 | "body": "${1:prefix}: protectedProcedure.input(${2:object}).mutation(async ({ ${3:args} }) => ${4:functionHandler}({ ${3:args} }))" 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /snippets/zod.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "array nonempty": { 3 | "prefix": ["zod_array_nonempty", "array nonempty"], 4 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 5 | "body": "z.array(z.string()).nonempty()" 6 | }, 7 | "bigint positive": { 8 | "prefix": ["zod_bigint_positive", "bigint positive"], 9 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 10 | "body": "z.bigint().positive()" 11 | }, 12 | "date max": { 13 | "prefix": ["zod_date_max", "date max"], 14 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 15 | "body": "z.date().max(new Date())" 16 | }, 17 | "deepPartial": { 18 | "prefix": ["zod_deep_partial", "deepPartial"], 19 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 20 | "body": "const $1 = ${1:schema}.deepPartial();" 21 | }, 22 | "enum": { 23 | "prefix": ["zod_enum", "zod enum"], 24 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 25 | "body": "z.enum([${1:values}])" 26 | }, 27 | "schema keyof": { 28 | "prefix": ["zod_schema_keyof", "schema keyof"], 29 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 30 | "body": "const $1 = ${2:schema}.keyof();" 31 | }, 32 | "string nullable": { 33 | "prefix": ["zod_string_nullable", "string nullable"], 34 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 35 | "body": "z.nullable(z.string())" 36 | }, 37 | "number int positive": { 38 | "prefix": ["zod_number_int_positive", "number int positive"], 39 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 40 | "body": "z.number().int().positive()" 41 | }, 42 | "string optional": { 43 | "prefix": ["zod_string_optional", "string optional"], 44 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 45 | "body": "z.optional(z.string())" 46 | }, 47 | "schema partial": { 48 | "prefix": ["zod_schema_partial", "schema partial"], 49 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 50 | "body": "const $1 = ${2:schema}.partial();" 51 | }, 52 | "schema passthrough": { 53 | "prefix": ["zod_schema_passthrough", "schema passthrough"], 54 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 55 | "body": "${1:schema}.passthrough().parse()" 56 | }, 57 | "schema strip": { 58 | "prefix": ["zod_schema_strip", "schema strip"], 59 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 60 | "body": "${1:schema}.strip().parse()" 61 | }, 62 | "schema strict": { 63 | "prefix": ["zod_schema_strict", "schema strict"], 64 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 65 | "body": "${1:schema}.strict().parse()" 66 | }, 67 | "promise": { 68 | "prefix": ["zod_promise", "zod promise"], 69 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 70 | "body": "z.promise(z.string())" 71 | }, 72 | "function parameters": { 73 | "prefix": ["zod_function_parameters", "function parameters"], 74 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 75 | "body": "${1:function}.parameters()" 76 | }, 77 | "function returnType": { 78 | "prefix": ["zod_function_returnType", "function returnType"], 79 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 80 | "body": "${1:function}.returnType()" 81 | }, 82 | "string max": { 83 | "prefix": ["zod_string_max", "string max"], 84 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 85 | "body": "z.string().max(${1:maxLength})" 86 | }, 87 | "string min": { 88 | "prefix": ["zod_string_min", "string min"], 89 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 90 | "body": "z.string().min(${1:minLength})" 91 | }, 92 | "string length": { 93 | "prefix": ["zod_string_length", "string length"], 94 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 95 | "body": "z.string().length(${1:length})" 96 | }, 97 | "string email": { 98 | "prefix": ["zod_string_email", "string email"], 99 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 100 | "body": "z.string().email()" 101 | }, 102 | "string url": { 103 | "prefix": ["zod_string_url", "string url"], 104 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 105 | "body": "z.string().url().nullish()" 106 | }, 107 | "string emoji": { 108 | "prefix": ["zod_string_emoji", "string emoji"], 109 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 110 | "body": "z.string().emoji()" 111 | }, 112 | "string uuid": { 113 | "prefix": ["zod_string_uuid", "string uuid"], 114 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 115 | "body": "z.string().uuid()" 116 | }, 117 | "string cuid": { 118 | "prefix": ["zod_string_cuid", "string cuid"], 119 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 120 | "body": "z.string().cuid()" 121 | }, 122 | "string cuid2": { 123 | "prefix": ["zod_string_cuid2", "string cuid2"], 124 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 125 | "body": "z.string().cuid2()" 126 | }, 127 | "string ulid": { 128 | "prefix": ["zod_string_ulid", "string ulid"], 129 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 130 | "body": "z.string().ulid()" 131 | }, 132 | "string regex": { 133 | "prefix": ["zod_string_regex", "string regex"], 134 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 135 | "body": "z.string().regex(${1:regex})" 136 | }, 137 | "string includes": { 138 | "prefix": ["zod_string_includes", "string includes"], 139 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 140 | "body": "z.string().includes(${1:value})" 141 | }, 142 | "string startsWith": { 143 | "prefix": ["zod_string_startsWith", "string startsWith"], 144 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 145 | "body": "z.string().startsWith(${1:value})" 146 | }, 147 | "string endsWith": { 148 | "prefix": ["zod_string_endsWith", "string endsWith"], 149 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 150 | "body": "z.string().endsWith(${1:value})" 151 | }, 152 | "string datetime": { 153 | "prefix": ["zod_string_datetime", "string datetime"], 154 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 155 | "body": "z.string().datetime()" 156 | }, 157 | "string ip": { 158 | "prefix": ["zod_string_ip", "string ip"], 159 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 160 | "body": "z.string().ip({ version: '${1:v4|v6}' })" 161 | }, 162 | "string trim": { 163 | "prefix": ["zod_string_trim", "zod string trim"], 164 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 165 | "body": "z.string().trim()" 166 | }, 167 | "string toLowerCase": { 168 | "prefix": ["zod_string_toLowerCase", "zod string toLowerCase"], 169 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 170 | "body": "z.string().toLowerCase()" 171 | }, 172 | "string toUpperCase": { 173 | "prefix": ["zod_string_toUpperCase", "zod string toUpperCase"], 174 | "scope": "javascript,javascriptreact,typescript,typescriptreact", 175 | "body": "z.string().toUpperCase()" 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/app/configs/config.ts: -------------------------------------------------------------------------------- 1 | import { WorkspaceConfiguration } from 'vscode'; 2 | 3 | import { 4 | ALIAS, 5 | EXCLUDE, 6 | EXPERIMENTAL_HTTPS, 7 | EXTENSION, 8 | INCLUDE, 9 | SHOW_PATH, 10 | SHOW_TYPE, 11 | TURBO, 12 | WATCH, 13 | } from './constants'; 14 | 15 | /** 16 | * The Config class. 17 | * 18 | * @class 19 | * @classdesc The class that represents the configuration of the extension. 20 | * @export 21 | * @public 22 | * @property {WorkspaceConfiguration} config - The workspace configuration 23 | * @property {string[]} include - The files to include 24 | * @property {string[]} exclude - The files to exclude 25 | * @property {string[]} watch - The files to watch 26 | * @example 27 | * const config = new Config(workspace.getConfiguration()); 28 | * console.log(config.include); 29 | * console.log(config.exclude); 30 | */ 31 | export class Config { 32 | // ----------------------------------------------------------------- 33 | // Properties 34 | // ----------------------------------------------------------------- 35 | 36 | // Public properties 37 | /** 38 | * The default import alias. 39 | * @type {string} 40 | * @public 41 | * @memberof Config 42 | * @example 43 | * const config = new Config(workspace.getConfiguration()); 44 | * console.log(config.alias); 45 | */ 46 | alias: string; 47 | /** 48 | * The file extension. 49 | * @type {string} 50 | * @public 51 | * @memberof Config 52 | * @example 53 | * const config = new Config(workspace.getConfiguration()); 54 | * console.log(config.extension); 55 | */ 56 | extension: string; 57 | /** 58 | * Whether to show the type or not. 59 | * @type {boolean} 60 | * @public 61 | * @memberof Config 62 | * @example 63 | * const config = new Config(workspace.getConfiguration()); 64 | * console.log(config.showType); 65 | */ 66 | showType: boolean; 67 | /** 68 | * The files to include. 69 | * @type {string[]} 70 | * @public 71 | * @memberof Config 72 | * @example 73 | * const config = new Config(workspace.getConfiguration()); 74 | * console.log(config.include); 75 | */ 76 | include: string[]; 77 | /** 78 | * The files to exclude. 79 | * @type {string[]} 80 | * @public 81 | * @memberof Config 82 | * @example 83 | * const config = new Config(workspace.getConfiguration()); 84 | * console.log(config.exclude); 85 | */ 86 | exclude: string[]; 87 | /** 88 | * The files to watch. 89 | * @type {string[]} 90 | * @public 91 | * @memberof Config 92 | * @example 93 | * const config = new Config(workspace.getConfiguration()); 94 | * console.log(config.watch); 95 | */ 96 | watch: string[]; 97 | /** 98 | * Whether to show the path or not. 99 | * @type {boolean} 100 | * @public 101 | * @memberof Config 102 | * @example 103 | * const config = new Config(workspace.getConfiguration()); 104 | * console.log(config.showPath); 105 | */ 106 | showPath: boolean; 107 | /** 108 | * Whether to use turbo or not. 109 | * @type {boolean} 110 | * @public 111 | * @memberof Config 112 | * @example 113 | * const config = new Config(workspace.getConfiguration()); 114 | * console.log(config.turbo); 115 | */ 116 | turbo: boolean; 117 | /** 118 | * Whether to use experimental https or not. 119 | * @type {boolean} 120 | * @public 121 | * @memberof Config 122 | * @example 123 | * const config = new Config(workspace.getConfiguration()); 124 | * console.log(config.experimentalHttps); 125 | */ 126 | experimentalHttps: boolean; 127 | 128 | // ----------------------------------------------------------------- 129 | // Constructor 130 | // ----------------------------------------------------------------- 131 | 132 | /** 133 | * Constructor for the Config class. 134 | * 135 | * @constructor 136 | * @param {WorkspaceConfiguration} config - The workspace configuration 137 | * @public 138 | * @memberof Config 139 | */ 140 | constructor(readonly config: WorkspaceConfiguration) { 141 | this.alias = config.get('files.alias', ALIAS); 142 | this.extension = config.get('files.extension', EXTENSION); 143 | this.showType = config.get('files.showType', SHOW_TYPE); 144 | this.include = config.get('files.include', INCLUDE); 145 | this.exclude = config.get('files.exclude', EXCLUDE); 146 | this.watch = config.get('files.watch', WATCH); 147 | this.showPath = config.get('files.showPath', SHOW_PATH); 148 | this.turbo = config.get('server.turbo', TURBO); 149 | this.experimentalHttps = config.get( 150 | 'server.experimentalHttps', 151 | EXPERIMENTAL_HTTPS, 152 | ); 153 | } 154 | 155 | // ----------------------------------------------------------------- 156 | // Methods 157 | // ----------------------------------------------------------------- 158 | 159 | // Public methods 160 | /** 161 | * The update method. 162 | * 163 | * @function update 164 | * @param {WorkspaceConfiguration} config - The workspace configuration 165 | * @public 166 | * @memberof Config 167 | * @example 168 | * const config = new Config(workspace.getConfiguration()); 169 | * config.update(workspace.getConfiguration()); 170 | */ 171 | update(config: WorkspaceConfiguration): void { 172 | this.alias = config.get('files.alias', ALIAS); 173 | this.extension = config.get('files.extension', EXTENSION); 174 | this.showType = config.get('files.showType', SHOW_TYPE); 175 | this.include = config.get('files.include', INCLUDE); 176 | this.exclude = config.get('files.exclude', EXCLUDE); 177 | this.watch = config.get('files.watch', WATCH); 178 | this.showPath = config.get('files.showPath', SHOW_PATH); 179 | this.turbo = config.get('server.turbo', TURBO); 180 | this.experimentalHttps = config.get( 181 | 'server.experimentalHttps', 182 | EXPERIMENTAL_HTTPS, 183 | ); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/app/configs/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * EXTENSION_ID: The unique identifier of the extension. 3 | * @type {string} 4 | * @public 5 | * @memberof Constants 6 | * @example 7 | * console.log(EXTENSION_ID); 8 | * 9 | * @returns {string} - The unique identifier of the extension 10 | */ 11 | export const EXTENSION_ID: string = 'nextjs'; 12 | 13 | /** 14 | * EXTENSION_NAME: The name of the extension. 15 | * @type {string} 16 | * @public 17 | * @memberof Constants 18 | * @example 19 | * console.log(EXTENSION_NAME); 20 | * 21 | * @returns {string} - The name of the extension 22 | */ 23 | export const EXTENSION_NAME: string = 24 | 'T3 Stack / NextJS / ReactJS File Generator'; 25 | 26 | /** 27 | * EXTENSION_HOMEPAGE_URL: The homepage URL of the extension. 28 | * @type {string} 29 | * @public 30 | * @memberof Constants 31 | * @example 32 | * console.log(EXTENSION_HOMEPAGE_URL); 33 | * 34 | * @returns {string} - The homepage URL of the extension 35 | */ 36 | export const EXTENSION_HOMEPAGE_URL: string = 37 | 'https://github.com/ManuelGil/vscode-nextjs-generator#readme'; 38 | 39 | /** 40 | * EXTENSION_REPOSITORY_URL: The repository URL of the extension. 41 | * @type {string} 42 | * @public 43 | * @memberof Constants 44 | * @example 45 | * console.log(EXTENSION_REPOSITORY_URL); 46 | * 47 | * @returns {string} - The repository URL of the extension 48 | */ 49 | export const EXTENSION_REPOSITORY_URL: string = 50 | 'https://github.com/ManuelGil/vscode-nextjs-generator'; 51 | 52 | /** 53 | * EXTENSION_MARKETPLACE_URL: The marketplace URL of the extension. 54 | * @type {string} 55 | * @public 56 | * @memberof Constants 57 | * @example 58 | * console.log(EXTENSION_MARKETPLACE_URL); 59 | * 60 | * @returns {string} - The marketplace URL of the extension 61 | */ 62 | export const EXTENSION_MARKETPLACE_URL: string = 63 | 'https://marketplace.visualstudio.com/items?itemName=imgildev.vscode-nextjs-generator'; 64 | 65 | /** 66 | * EXTENSION_BUGS_URL: The bugs URL of the extension. 67 | * @type {string} 68 | * @public 69 | * @memberof Constants 70 | * @example 71 | * console.log(EXTENSION_BUGS_URL); 72 | * 73 | * @returns {string} - The bugs URL of the extension 74 | */ 75 | export const EXTENSION_BUGS_URL: string = 76 | 'https://github.com/ManuelGil/vscode-nextjs-generator/issues'; 77 | 78 | /** 79 | * EXTENSION_SPONSOR_URL: The sponsor URL of the extension. 80 | * @type {string} 81 | * @public 82 | * @memberof Constants 83 | * @example 84 | * console.log(EXTENSION_SPONSOR_URL); 85 | * 86 | * @returns {string} - The sponsor URL of the extension 87 | */ 88 | export const EXTENSION_SPONSOR_URL: string = 89 | 'https://github.com/sponsors/ManuelGil'; 90 | 91 | /** 92 | * EXTENSION_PAYPAL_URL: The PayPal URL of the extension. 93 | * @type {string} 94 | * @public 95 | * @memberof Constants 96 | * @example 97 | * console.log(EXTENSION_PAYPAL_URL); 98 | * 99 | * @returns {string} - The PayPal URL of the extension 100 | */ 101 | export const EXTENSION_PAYPAL_URL: string = 102 | 'https://www.paypal.com/paypalme/ManuelFGil'; 103 | 104 | /** 105 | * ALIAS: The default import alias. 106 | * @type {string} 107 | * @public 108 | * @memberof Constants 109 | * @example 110 | * console.log(ALIAS); 111 | * 112 | * @returns {string} - The default import alias 113 | */ 114 | export const ALIAS: string = '~'; 115 | 116 | /** 117 | * EXTENSION: The file extension. 118 | * @type {string} 119 | * @public 120 | * @memberof Constants 121 | * @example 122 | * console.log(EXTENSION); 123 | * 124 | * @returns {string} - The file extension 125 | */ 126 | export const EXTENSION: string = 'tsx'; 127 | 128 | /** 129 | * SHOW_TYPE: Whether to show the type or not. 130 | * @type {boolean} 131 | * @public 132 | * @memberof Constants 133 | * @example 134 | * console.log(SHOW_TYPE); 135 | * 136 | * @returns {boolean} - Whether to show the type or not 137 | */ 138 | export const SHOW_TYPE: boolean = true; 139 | 140 | /** 141 | * INCLUDE: The files to include. 142 | * @type {string[]} 143 | * @public 144 | * @memberof Constants 145 | * @example 146 | * console.log(INCLUDE); 147 | * 148 | * @returns {string[]} - The files to include 149 | */ 150 | export const INCLUDE: string[] = ['js', 'jsx', 'ts', 'tsx']; 151 | /** 152 | * EXCLUDE: The files to exclude. 153 | * @type {string[]} 154 | * @public 155 | * @memberof Constants 156 | * @example 157 | * console.log(EXCLUDE); 158 | * 159 | * @returns {string[]} - The files to exclude 160 | */ 161 | export const EXCLUDE: string[] = [ 162 | '**/node_modules/**', 163 | '**/dist/**', 164 | '**/out/**', 165 | '**/build/**', 166 | '**/.*/**', 167 | ]; 168 | 169 | /** 170 | * WATCH: The files to watch. 171 | * @type {string[]} 172 | * @public 173 | * @memberof Constants 174 | * @example 175 | * console.log(WATCH); 176 | * 177 | * @returns {string[]} - The files to watch 178 | */ 179 | export const WATCH: string[] = ['modules', 'components', 'services']; 180 | 181 | /** 182 | * SHOW_PATH: Whether to show the path or not. 183 | * @type {boolean} 184 | * @public 185 | * @memberof Constants 186 | * @example 187 | * console.log(SHOW_PATH); 188 | * 189 | * @returns {boolean} - Whether to show the path or not 190 | */ 191 | export const SHOW_PATH: boolean = true; 192 | 193 | /** 194 | * TURBO: Whether to use turbo or not. 195 | * @type {boolean} 196 | * @public 197 | * @memberof Constants 198 | * @example 199 | * console.log(TURBO); 200 | * 201 | * @returns {boolean} - Whether to use turbo or not 202 | */ 203 | export const TURBO: boolean = true; 204 | 205 | /** 206 | * EXPERIMENTAL_HTTPS: Whether to use experimental HTTPS or not. 207 | * @type {boolean} 208 | * @public 209 | * @memberof Constants 210 | * @example 211 | * console.log(EXPERIMENTAL_HTTPS); 212 | * 213 | * @returns {boolean} - Whether to use experimental HTTPS or not 214 | */ 215 | export const EXPERIMENTAL_HTTPS: boolean = false; 216 | -------------------------------------------------------------------------------- /src/app/configs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './constants'; 3 | -------------------------------------------------------------------------------- /src/app/controllers/feedback.controller.ts: -------------------------------------------------------------------------------- 1 | import { MessageItem, Uri, env, window } from 'vscode'; 2 | 3 | import { 4 | EXTENSION_BUGS_URL, 5 | EXTENSION_HOMEPAGE_URL, 6 | EXTENSION_MARKETPLACE_URL, 7 | EXTENSION_NAME, 8 | EXTENSION_PAYPAL_URL, 9 | EXTENSION_SPONSOR_URL, 10 | } from '../configs'; 11 | 12 | /** 13 | * The FeedbackController class. 14 | * 15 | * @class 16 | * @classdesc The class that represents the feedback controller. 17 | * @export 18 | * @public 19 | * @example 20 | * const controller = new FeedbackController(); 21 | */ 22 | export class FeedbackController { 23 | // ----------------------------------------------------------------- 24 | // Constructor 25 | // ----------------------------------------------------------------- 26 | 27 | /** 28 | * Constructor for the FeedbackController class. 29 | * 30 | * @constructor 31 | * @public 32 | * @memberof FeedbackController 33 | */ 34 | constructor() {} 35 | 36 | // ----------------------------------------------------------------- 37 | // Methods 38 | // ----------------------------------------------------------------- 39 | 40 | // Public methods 41 | /** 42 | * The aboutUs method. 43 | * 44 | * @function aboutUs 45 | * @public 46 | * @memberof FeedbackController 47 | * 48 | * @returns {void} - No return value 49 | */ 50 | aboutUs(): void { 51 | env.openExternal(Uri.parse(EXTENSION_HOMEPAGE_URL)); 52 | } 53 | 54 | /** 55 | * The reportIssues method. 56 | * 57 | * @function reportIssues 58 | * @public 59 | * @memberof FeedbackController 60 | * 61 | * @returns {void} - No return value 62 | */ 63 | reportIssues(): void { 64 | env.openExternal(Uri.parse(EXTENSION_BUGS_URL)); 65 | } 66 | 67 | /** 68 | * The rateUs method. 69 | * 70 | * @function rateUs 71 | * @public 72 | * @memberof FeedbackController 73 | * 74 | * @returns {void} - No return value 75 | */ 76 | rateUs(): void { 77 | env.openExternal( 78 | Uri.parse(`${EXTENSION_MARKETPLACE_URL}&ssr=false#review-details`), 79 | ); 80 | } 81 | 82 | /** 83 | * The supportUs method. 84 | * 85 | * @function supportUs 86 | * @public 87 | * @async 88 | * @memberof FeedbackController 89 | * 90 | * @returns {Promise} - The promise that resolves with no value 91 | */ 92 | async supportUs(): Promise { 93 | // Create the actions 94 | const actions: MessageItem[] = [ 95 | { title: 'Become a Sponsor' }, 96 | { title: 'Donate via PayPal' }, 97 | ]; 98 | 99 | // Show the message 100 | const option = await window.showInformationMessage( 101 | `Although ${EXTENSION_NAME} is offered at no cost, your support is 102 | deeply appreciated if you find it beneficial. Thank you for considering!`, 103 | ...actions, 104 | ); 105 | 106 | // Handle the actions 107 | switch (option?.title) { 108 | case actions[0].title: 109 | env.openExternal(Uri.parse(EXTENSION_SPONSOR_URL)); 110 | break; 111 | 112 | case actions[1].title: 113 | env.openExternal(Uri.parse(EXTENSION_PAYPAL_URL)); 114 | break; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/app/controllers/file.controller.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from 'vscode'; 2 | 3 | // Import the Config and helper functions 4 | import { Config } from '../configs'; 5 | import { 6 | dasherize, 7 | getName, 8 | getPath, 9 | getRelativePath, 10 | saveFile, 11 | titleize, 12 | } from '../helpers'; 13 | 14 | /** 15 | * The FileController class. 16 | * 17 | * @class 18 | * @classdesc The class that represents the example controller. 19 | * @export 20 | * @public 21 | * @property {Config} config - The configuration 22 | * @example 23 | * const controller = new FileController(config); 24 | */ 25 | export class FileController { 26 | // ----------------------------------------------------------------- 27 | // Constructor 28 | // ----------------------------------------------------------------- 29 | 30 | /** 31 | * Constructor for the FileController class. 32 | * 33 | * @constructor 34 | * @param {Config} config - The configuration 35 | * @public 36 | * @memberof FileController 37 | */ 38 | constructor(private readonly config: Config) {} 39 | 40 | // ----------------------------------------------------------------- 41 | // Methods 42 | // ----------------------------------------------------------------- 43 | 44 | // Public methods 45 | /** 46 | * Creates a new class. 47 | * 48 | * @function newClass 49 | * @param {Uri} [path] - The path to the folder 50 | * @public 51 | * @async 52 | * @memberof FileController 53 | * @example 54 | * await controller.newClass(); 55 | * 56 | * @returns {Promise} - No return value 57 | */ 58 | async newClass(path?: Uri): Promise { 59 | // Get the relative path 60 | const folderPath: string = path ? await getRelativePath(path.path) : ''; 61 | 62 | // Get the path to the folder 63 | const folder = await getPath( 64 | 'Folder name', 65 | 'Folder name. E.g. src, app...', 66 | folderPath, 67 | (path: string) => { 68 | if (!/^(?!\/)[^\sÀ-ÿ]+?$/.test(path)) { 69 | return 'The folder name must be a valid name'; 70 | } 71 | return; 72 | }, 73 | ); 74 | 75 | if (!folder) { 76 | return; 77 | } 78 | 79 | // Get the type 80 | let type = await getName( 81 | 'Type class name', 82 | 'E.g. class, interface, type, enum...', 83 | (type: string) => { 84 | if (!/[a-z]+/.test(type)) { 85 | return 'Invalid format!'; 86 | } 87 | return; 88 | }, 89 | ); 90 | 91 | if (!type) { 92 | return; 93 | } 94 | 95 | // Get the class name 96 | const className = await getName( 97 | 'Name', 98 | 'E.g. User, Role, Post...', 99 | (name: string) => { 100 | if (!/^[A-Z][A-Za-z]{2,}$/.test(name)) { 101 | return 'Invalid format! Entity names MUST be declared in PascalCase.'; 102 | } 103 | return; 104 | }, 105 | ); 106 | 107 | if (!className) { 108 | return; 109 | } 110 | 111 | const content = `export default ${type} ${className}${titleize(type)} { 112 | \t// ... your code goes here 113 | } 114 | `; 115 | 116 | if (this.config.showType) { 117 | type += '.'; 118 | } else { 119 | type = ''; 120 | } 121 | 122 | const filename = `${dasherize(className)}.${type}${this.config.extension}`; 123 | 124 | saveFile(folder, filename, content); 125 | } 126 | 127 | /** 128 | * Creates a new component. 129 | * 130 | * @function newComponent 131 | * @param {Uri} [path] - The path to the folder 132 | * @public 133 | * @async 134 | * @memberof FileController 135 | * @example 136 | * await controller.newComponent(); 137 | * 138 | * @returns {Promise} - No return value 139 | */ 140 | async newComponent(path?: Uri): Promise { 141 | // Get the relative path 142 | const folderPath: string = path ? await getRelativePath(path.path) : ''; 143 | 144 | // Get the path to the folder 145 | const folder = await getPath( 146 | 'Folder name', 147 | 'Folder name. E.g. src, app...', 148 | folderPath, 149 | (path: string) => { 150 | if (!/^(?!\/)[^\sÀ-ÿ]+?$/.test(path)) { 151 | return 'The folder name must be a valid name'; 152 | } 153 | return; 154 | }, 155 | ); 156 | 157 | if (!folder) { 158 | return; 159 | } 160 | 161 | // Get the function name 162 | const functionName = await getName( 163 | 'Component Name', 164 | 'E.g. Title, Header, Main, Footer...', 165 | (name: string) => { 166 | if (!/^[A-Z][A-Za-z]{2,}$/.test(name)) { 167 | return 'Invalid format! Entity names MUST be declared in PascalCase.'; 168 | } 169 | return; 170 | }, 171 | ); 172 | 173 | if (!functionName) { 174 | return; 175 | } 176 | 177 | const content = `interface ${functionName}Props { 178 | \tchildren: React.ReactNode; 179 | } 180 | 181 | export function ${functionName}({ children }: ${functionName}Props) { 182 | \treturn ( 183 | \t\t<> 184 | \t\t\t

${functionName}

185 | \t\t\t{children} 186 | \t\t 187 | \t); 188 | } 189 | `; 190 | 191 | let type = ''; 192 | 193 | if (this.config.showType) { 194 | type = 'component.'; 195 | } 196 | 197 | const filename = `${dasherize(functionName)}.${type}tsx`; 198 | 199 | saveFile(folder, filename, content); 200 | } 201 | 202 | /** 203 | * Creates a new layout. 204 | * 205 | * @function newLayout 206 | * @param {Uri} [path] - The path to the folder 207 | * @public 208 | * @async 209 | * @memberof FileController 210 | * @example 211 | * await controller.newLayout(); 212 | * 213 | * @returns {Promise} - No return value 214 | */ 215 | async newLayout(path?: Uri): Promise { 216 | // Get the relative path 217 | const folderPath: string = path ? await getRelativePath(path.path) : ''; 218 | 219 | // Get the path to the folder 220 | const folder = await getPath( 221 | 'Folder name', 222 | 'Folder name. E.g. src, app...', 223 | folderPath, 224 | (path: string) => { 225 | if (!/^(?!\/)[^\sÀ-ÿ]+?$/.test(path)) { 226 | return 'The folder name must be a valid name'; 227 | } 228 | return; 229 | }, 230 | ); 231 | 232 | if (!folder) { 233 | return; 234 | } 235 | 236 | const content = `import type { Metadata } from 'next' 237 | import { Inter } from 'next/font/google' 238 | import './globals.css' 239 | 240 | const inter = Inter({ subsets: ['latin'] }) 241 | 242 | export const metadata: Metadata = { 243 | \ttitle: 'Create Next App', 244 | \tdescription: 'Create Next App with TypeScript, Tailwind CSS, NextAuth, Prisma, tRPC, and more.', 245 | } 246 | 247 | export default function Layout({ 248 | \tchildren, 249 | }: { 250 | \tchildren: React.ReactNode 251 | }) { 252 | \treturn ( 253 | \t\t 254 | \t\t\t{children} 255 | \t\t 256 | \t) 257 | } 258 | `; 259 | 260 | const filename = `layout.tsx`; 261 | 262 | saveFile(folder, filename, content); 263 | } 264 | 265 | /** 266 | * Creates a new loading component. 267 | * 268 | * @function newLoading 269 | * @param {Uri} [path] - The path to the folder 270 | * @public 271 | * @async 272 | * @memberof FileController 273 | * @example 274 | * await controller.newLoading(); 275 | * 276 | * @returns {Promise} - No return value 277 | */ 278 | async newLoading(path?: Uri): Promise { 279 | // Get the relative path 280 | const folderPath: string = path ? await getRelativePath(path.path) : ''; 281 | 282 | // Get the path to the folder 283 | const folder = await getPath( 284 | 'Folder name', 285 | 'Folder name. E.g. src, app...', 286 | folderPath, 287 | (path: string) => { 288 | if (!/^(?!\/)[^\sÀ-ÿ]+?$/.test(path)) { 289 | return 'The folder name must be a valid name'; 290 | } 291 | return; 292 | }, 293 | ); 294 | 295 | if (!folder) { 296 | return; 297 | } 298 | 299 | const content = `export default function Loading() { 300 | \treturn

Loading...

301 | } 302 | `; 303 | 304 | const filename = `loading.tsx`; 305 | 306 | saveFile(folder, filename, content); 307 | } 308 | 309 | /** 310 | * Creates a new page. 311 | * 312 | * @function newPage 313 | * @param {Uri} [path] - The path to the folder 314 | * @public 315 | * @async 316 | * @memberof FileController 317 | * @example 318 | * await controller.newPage(); 319 | * 320 | * @returns {Promise} - No return value 321 | */ 322 | async newPage(path?: Uri): Promise { 323 | // Get the relative path 324 | const folderPath: string = path ? await getRelativePath(path.path) : ''; 325 | 326 | // Get the path to the folder 327 | const folder = await getPath( 328 | 'Folder name', 329 | 'Folder name. E.g. src, app...', 330 | folderPath, 331 | (path: string) => { 332 | if (!/^(?!\/)[^\sÀ-ÿ]+?$/.test(path)) { 333 | return 'The folder name must be a valid name'; 334 | } 335 | return; 336 | }, 337 | ); 338 | 339 | if (!folder) { 340 | return; 341 | } 342 | 343 | const content = `'use client' 344 | 345 | interface Props { 346 | \tparams: { 347 | \t\tid: string; 348 | \t}; 349 | } 350 | 351 | export default function Page({ params }: Props) { 352 | \tconst { id } = params; 353 | 354 | \treturn ( 355 | \t\t<> 356 | \t\t\t

Page { id }

357 | \t\t\t

Page content

358 | \t\t 359 | \t); 360 | } 361 | `; 362 | 363 | const filename = `page.tsx`; 364 | 365 | saveFile(folder, filename, content); 366 | } 367 | 368 | /** 369 | * Creates a new auth. 370 | * 371 | * @function newNextAuth 372 | * @param {Uri} [path] - The path to the folder 373 | * @public 374 | * @async 375 | * @memberof FileController 376 | * @example 377 | * await controller.newNextAuth(); 378 | * 379 | * @returns {Promise} - No return value 380 | */ 381 | async newNextAuth(path?: Uri): Promise { 382 | // Get the relative path 383 | const folderPath: string = path ? await getRelativePath(path.path) : ''; 384 | 385 | // Get the path to the folder 386 | const folder = await getPath( 387 | 'Folder name', 388 | 'Folder name. E.g. src, app...', 389 | folderPath, 390 | (path: string) => { 391 | if (!/^(?!\/)[^\sÀ-ÿ]+?$/.test(path)) { 392 | return 'The folder name must be a valid name'; 393 | } 394 | return; 395 | }, 396 | ); 397 | 398 | if (!folder) { 399 | return; 400 | } 401 | 402 | const content = `import NextAuth from 'next-auth'; 403 | 404 | export const authOptions = { 405 | \tproviders: [ 406 | \t\t// Providers... 407 | \t], 408 | }; 409 | 410 | export default NextAuth(authOptions); 411 | `; 412 | 413 | const filename = `[...nextauth].${this.config.extension}`; 414 | 415 | saveFile(folder, filename, content); 416 | } 417 | 418 | /** 419 | * Creates a new tRPC router. 420 | * 421 | * @function newTRPCRouter 422 | * @param {Uri} [path] - The path to the folder 423 | * @public 424 | * @async 425 | * @memberof FileController 426 | * @example 427 | * await controller.newTRPCRouter(); 428 | * 429 | * @returns {Promise} - No return value 430 | */ 431 | async newTRPCRouter(path?: Uri): Promise { 432 | // Get the relative path 433 | const folderPath: string = path ? await getRelativePath(path.path) : ''; 434 | 435 | // Get the path to the folder 436 | const folder = await getPath( 437 | 'Folder name', 438 | 'Folder name. E.g. src, app...', 439 | folderPath, 440 | (path: string) => { 441 | if (!/^(?!\/)[^\sÀ-ÿ]+?$/.test(path)) { 442 | return 'The folder name must be a valid name'; 443 | } 444 | return; 445 | }, 446 | ); 447 | 448 | if (!folder) { 449 | return; 450 | } 451 | 452 | // Get the entity name 453 | let entityName = await getName( 454 | 'Router name', 455 | 'E.g. user, subscription, auth...', 456 | (text: string) => { 457 | if (!/^[a-z][\w-]+$/.test(text)) { 458 | return 'Invalid format! Entity names MUST be declared in camelCase.'; 459 | } 460 | return; 461 | }, 462 | ); 463 | 464 | if (!entityName) { 465 | return; 466 | } 467 | 468 | const content = `import { z } from 'zod'; 469 | 470 | import { 471 | \tcreateTRPCRouter, 472 | \t// protectedProcedure, 473 | \tpublicProcedure, 474 | } from '${this.config.alias}/server/api/trpc'; 475 | 476 | export const ${entityName}Router = createTRPCRouter({ 477 | \t// prefix: t.procedure.input(callable).query(async (args) => handler(args)), 478 | }); 479 | `; 480 | 481 | let type = ''; 482 | 483 | if (this.config.showType) { 484 | type = 'router.'; 485 | } 486 | 487 | const filename = `${dasherize(entityName)}.${type}${this.config.extension}`; 488 | 489 | saveFile(folder, filename, content); 490 | } 491 | 492 | /** 493 | * Creates a new tRPC controller. 494 | * 495 | * @function newTRPCController 496 | * @param {Uri} [path] - The path to the folder 497 | * @public 498 | * @async 499 | * @memberof FileController 500 | * @example 501 | * await controller.newTRPCController(); 502 | * 503 | * @returns {Promise} - No return value 504 | */ 505 | async newTRPCController(path?: Uri): Promise { 506 | // Get the relative path 507 | const folderPath: string = path ? await getRelativePath(path.path) : ''; 508 | 509 | // Get the path to the folder 510 | const folder = await getPath( 511 | 'Folder name', 512 | 'Folder name. E.g. src, app...', 513 | folderPath, 514 | (path: string) => { 515 | if (!/^(?!\/)[^\sÀ-ÿ]+?$/.test(path)) { 516 | return 'The folder name must be a valid name'; 517 | } 518 | return; 519 | }, 520 | ); 521 | 522 | if (!folder) { 523 | return; 524 | } 525 | 526 | // Get the entity name 527 | let entityName = await getName( 528 | 'Controller name', 529 | 'E.g. user, subscription, auth...', 530 | (text: string) => { 531 | if (!/^[a-z][\w-]+$/.test(text)) { 532 | return 'Invalid format! Entity names MUST be declared in camelCase.'; 533 | } 534 | return; 535 | }, 536 | ); 537 | 538 | if (!entityName) { 539 | return; 540 | } 541 | 542 | const content = `import { TRPCError } from '@trpc/server'; 543 | import { z, ZodError } from 'zod'; 544 | 545 | import { 546 | \tcreateTRPCRouter, 547 | \t// protectedProcedure, 548 | \tpublicProcedure, 549 | } from '${this.config.alias}/server/api/trpc'; 550 | 551 | export const getAll = async () => { 552 | \ttry { 553 | \t\t// ... your code goes here 554 | \t} catch (error) { 555 | \t\tif (error instanceof ZodError) { 556 | \t\t\tthrow new TRPCError({ 557 | \t\t\t\tcode: 'BAD_REQUEST', 558 | \t\t\t\tmessage: error.message, 559 | \t\t\t}); 560 | \t\t} 561 | 562 | \t\tif (error instanceof TRPCError) { 563 | \t\t\tif (error.code === 'UNAUTHORIZED') { 564 | \t\t\t\tthrow new TRPCError({ 565 | \t\t\t\t\tcode: 'UNAUTHORIZED', 566 | \t\t\t\t\tmessage: 'Unauthorized', 567 | \t\t\t\t}); 568 | \t\t\t} 569 | 570 | \t\t\tthrow new TRPCError({ 571 | \t\t\t\tcode: 'INTERNAL_SERVER_ERROR', 572 | \t\t\t\tmessage: error.message, 573 | \t\t\t}); 574 | \t\t} 575 | \t} 576 | }; 577 | `; 578 | 579 | let type = ''; 580 | 581 | if (this.config.showType) { 582 | type = 'controller.'; 583 | } 584 | 585 | const filename = `${dasherize(entityName)}.${type}${this.config.extension}`; 586 | 587 | saveFile(folder, filename, content); 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /src/app/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './feedback.controller'; 2 | export * from './file.controller'; 3 | export * from './list-files.controller'; 4 | export * from './terminal.controller'; 5 | export * from './transform.controller'; 6 | -------------------------------------------------------------------------------- /src/app/controllers/list-files.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Position, 3 | Range, 4 | Selection, 5 | TextEditorRevealType, 6 | ThemeIcon, 7 | Uri, 8 | window, 9 | workspace, 10 | } from 'vscode'; 11 | 12 | import { Config, EXTENSION_ID } from '../configs'; 13 | import { directoryMap, getRelativePath } from '../helpers'; 14 | import { NodeModel } from '../models'; 15 | 16 | /** 17 | * The ListFilesController class. 18 | * 19 | * @class 20 | * @classdesc The class that represents the list files controller. 21 | * @export 22 | * @public 23 | * @example 24 | * const controller = new ListFilesController(); 25 | */ 26 | export class ListFilesController { 27 | // ----------------------------------------------------------------- 28 | // Properties 29 | // ----------------------------------------------------------------- 30 | 31 | // Public properties 32 | /** 33 | * The static config property. 34 | * 35 | * @static 36 | * @property 37 | * @public 38 | * @type {Config} 39 | * @memberof ListFilesController 40 | */ 41 | static config: Config; 42 | 43 | // ----------------------------------------------------------------- 44 | // Constructor 45 | // ----------------------------------------------------------------- 46 | 47 | /** 48 | * Constructor for the ListFilesController class 49 | * 50 | * @constructor 51 | * @param {Config} config - The configuration object 52 | * @public 53 | * @memberof ListFilesController 54 | */ 55 | constructor(config: Config) { 56 | ListFilesController.config = config; 57 | } 58 | 59 | // ----------------------------------------------------------------- 60 | // Methods 61 | // ----------------------------------------------------------------- 62 | 63 | // Public methods 64 | /** 65 | * The getFiles method. 66 | * 67 | * @function getFiles 68 | * @param {number} maxResults - The maximum number of results 69 | * @public 70 | * @async 71 | * @memberof ListFilesController 72 | * @example 73 | * controller.getFiles(); 74 | * 75 | * @returns {Promise} - The list of files 76 | */ 77 | static async getFiles( 78 | maxResults: number = Number.MAX_SAFE_INTEGER, 79 | ): Promise { 80 | // Get the files in the folder 81 | const files = await directoryMap('/', { 82 | extensions: this.config.include, 83 | ignore: this.config.exclude, 84 | maxResults, 85 | }); 86 | 87 | if (files.length !== 0) { 88 | let nodes: NodeModel[] = []; 89 | 90 | files.sort((a, b) => a.path.localeCompare(b.path)); 91 | 92 | for (const file of files) { 93 | const document = await workspace.openTextDocument(file); 94 | 95 | const path = await getRelativePath(document.fileName); 96 | let filename = path.split('/').pop(); 97 | 98 | if (filename && this.config.showPath) { 99 | const folder = path.split('/').slice(0, -1).join('/'); 100 | 101 | filename += folder ? ` (${folder})` : ' (root)'; 102 | } 103 | 104 | nodes.push( 105 | new NodeModel( 106 | filename ?? 'Untitled', 107 | new ThemeIcon('file'), 108 | { 109 | command: `${EXTENSION_ID}.list.openFile`, 110 | title: 'Open File', 111 | arguments: [document.uri], 112 | }, 113 | document.uri, 114 | document.fileName, 115 | ), 116 | ); 117 | } 118 | 119 | return nodes; 120 | } 121 | 122 | return; 123 | } 124 | 125 | /** 126 | * The openFile method. 127 | * 128 | * @function openFile 129 | * @param {Uri} uri - The file URI 130 | * @public 131 | * @memberof ListFilesController 132 | * @example 133 | * controller.openFile('file:///path/to/file'); 134 | * 135 | * @returns {Promise} - The promise 136 | */ 137 | openFile(uri: Uri) { 138 | workspace.openTextDocument(uri).then((filename) => { 139 | window.showTextDocument(filename); 140 | }); 141 | } 142 | 143 | /** 144 | * The gotoLine method. 145 | * 146 | * @function gotoLine 147 | * @param {Uri} uri - The file URI 148 | * @param {number} line - The line number 149 | * @public 150 | * @memberof ListFilesController 151 | * @example 152 | * controller.gotoLine('file:///path/to/file', 1); 153 | * 154 | * @returns {void} - The promise 155 | */ 156 | gotoLine(uri: Uri, line: number) { 157 | workspace.openTextDocument(uri).then((document) => { 158 | window.showTextDocument(document).then((editor) => { 159 | const pos = new Position(line, 0); 160 | editor.revealRange( 161 | new Range(pos, pos), 162 | TextEditorRevealType.InCenterIfOutsideViewport, 163 | ); 164 | editor.selection = new Selection(pos, pos); 165 | }); 166 | }); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/app/controllers/terminal.controller.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | 3 | // Import the Config and helper functions 4 | import { Config } from '../configs'; 5 | import { pickItem, runCommand } from '../helpers'; 6 | 7 | /** 8 | * The TerminalController class. 9 | * 10 | * @class 11 | * @classdesc The class that represents the example controller. 12 | * @export 13 | * @public 14 | * @property {Config} config - The configuration 15 | * @example 16 | * const controller = new TerminalController(config); 17 | */ 18 | export class TerminalController { 19 | // ----------------------------------------------------------------- 20 | // Constructor 21 | // ----------------------------------------------------------------- 22 | 23 | /** 24 | * Constructor for the TerminalController class. 25 | * 26 | * @constructor 27 | * @param {Config} config - The configuration 28 | * @public 29 | * @memberof TerminalController 30 | */ 31 | constructor(private readonly config: Config) {} 32 | 33 | // ----------------------------------------------------------------- 34 | // Methods 35 | // ----------------------------------------------------------------- 36 | 37 | // Public methods 38 | /** 39 | * Creates a new project. 40 | * 41 | * @function newProject 42 | * @public 43 | * @memberof TerminalController 44 | * @example 45 | * await controller.newProject(); 46 | * 47 | * @returns {Promise} - No return value 48 | */ 49 | async newProject(): Promise { 50 | const type = await window.showQuickPick( 51 | [ 52 | { 53 | label: 'create-t3-app', 54 | description: 'Create a new project with Create T3 App', 55 | }, 56 | { 57 | label: 'create-next-app', 58 | description: 'Create a new project with Create Next App', 59 | }, 60 | { 61 | label: 'create-vite-app', 62 | description: 'Create a new project with React and Vite', 63 | }, 64 | ], 65 | { 66 | placeHolder: 'What kind of project do you want to create?', 67 | }, 68 | ); 69 | 70 | if (!type) { 71 | return; 72 | } 73 | 74 | const manager = await pickItem( 75 | ['npm', 'yarn', 'pnpm', 'bun'], 76 | 'Which package manager do you want to use?', 77 | ); 78 | 79 | if (!manager) { 80 | return; 81 | } 82 | 83 | switch (type.label) { 84 | case 'create-vite-app': 85 | const viteType = await window.showQuickPick( 86 | [ 87 | { 88 | label: 'react', 89 | description: 'Create a new project with React and Vite', 90 | }, 91 | { 92 | label: 'react-ts', 93 | description: 94 | 'Create a new project with React, TypeScript and Vite', 95 | }, 96 | { 97 | label: 'react-swc', 98 | description: 'Create a new project with React, SWC and Vite', 99 | }, 100 | { 101 | label: 'react-swc-ts', 102 | description: 103 | 'Create a new project with React, TypeScript, SWC and Vite', 104 | }, 105 | ], 106 | { 107 | placeHolder: 'What kind of project do you want to create?', 108 | }, 109 | ); 110 | 111 | if (!viteType) { 112 | return; 113 | } 114 | 115 | switch (manager) { 116 | case 'npm': 117 | runCommand( 118 | type.label, 119 | `npm create vite@latest . -- --template ${viteType.label}`, 120 | ); 121 | break; 122 | 123 | case 'yarn': 124 | runCommand( 125 | type.label, 126 | `yarn create vite . --template ${viteType.label}`, 127 | ); 128 | break; 129 | 130 | case 'pnpm': 131 | runCommand( 132 | type.label, 133 | `pnpm create vite . --template ${viteType.label}`, 134 | ); 135 | break; 136 | 137 | case 'bun': 138 | runCommand( 139 | type.label, 140 | `bunx create-vite . --template ${viteType.label}`, 141 | ); 142 | break; 143 | } 144 | 145 | break; 146 | 147 | case 'create-next-app': 148 | switch (manager) { 149 | case 'npm': 150 | runCommand(type.label, 'npx create-next-app@latest .'); 151 | break; 152 | 153 | case 'yarn': 154 | runCommand(type.label, 'yarn create next-app .'); 155 | break; 156 | 157 | case 'pnpm': 158 | runCommand(type.label, 'pnpm create next-app .'); 159 | break; 160 | 161 | case 'bun': 162 | runCommand(type.label, 'bunx create-next-app .'); 163 | break; 164 | } 165 | 166 | break; 167 | 168 | case 'create-t3-app': 169 | switch (manager) { 170 | case 'npm': 171 | runCommand(type.label, 'npm create t3-app@latest .'); 172 | break; 173 | 174 | case 'yarn': 175 | runCommand(type.label, 'yarn create t3-app .'); 176 | break; 177 | 178 | case 'pnpm': 179 | runCommand(type.label, 'pnpm create t3-app@latest .'); 180 | break; 181 | 182 | case 'bun': 183 | runCommand(type.label, 'bun create t3-app@latest .'); 184 | break; 185 | } 186 | 187 | break; 188 | } 189 | } 190 | 191 | /** 192 | * Starts the server. 193 | * 194 | * @function start 195 | * @public 196 | * @memberof TerminalController 197 | * @example 198 | * controller.start(); 199 | * 200 | * @returns {void} - No return value 201 | */ 202 | start(): void { 203 | runCommand( 204 | 'start', 205 | 'npx next dev' + 206 | (this.config.turbo ? ' --turbo' : '') + 207 | (this.config.experimentalHttps ? ' --experimental-https' : ''), 208 | ); 209 | } 210 | 211 | /** 212 | * Executes the Prisma schema to the database. 213 | * 214 | * @function prismaExecute 215 | * @public 216 | * @memberof TerminalController 217 | * @example 218 | * await controller.prismaExecute(); 219 | * 220 | * @returns {Promise} - No return value 221 | */ 222 | async prismaExecute(): Promise { 223 | const confirm = await pickItem( 224 | ['Yes', 'No'], 225 | 'Are you sure you want to execute the Prisma schema to the database?', 226 | ); 227 | 228 | if (!confirm || confirm === 'No') { 229 | return; 230 | } 231 | 232 | runCommand('prisma db execute', 'npx prisma db execute'); 233 | } 234 | 235 | /** 236 | * Pulls the Prisma schema to the database. 237 | * 238 | * @function prismaPull 239 | * @public 240 | * @memberof TerminalController 241 | * @example 242 | * await controller.prismaPull(); 243 | * 244 | * @returns {Promise} - No return value 245 | */ 246 | async prismaPull(): Promise { 247 | const confirm = await pickItem( 248 | ['Yes', 'No'], 249 | 'Are you sure you want to pull the Prisma schema to the database?', 250 | ); 251 | 252 | if (!confirm || confirm === 'No') { 253 | return; 254 | } 255 | 256 | runCommand('prisma db pull', 'npx prisma db pull'); 257 | } 258 | 259 | /** 260 | * Pushes the Prisma schema to the database. 261 | * 262 | * @function prismaPush 263 | * @public 264 | * @memberof TerminalController 265 | * @example 266 | * await controller.prismaPush(); 267 | * 268 | * @returns {Promise} - No return value 269 | */ 270 | async prismaPush(): Promise { 271 | const confirm = await pickItem( 272 | ['Yes', 'No'], 273 | 'Are you sure you want to push the Prisma schema to the database?', 274 | ); 275 | 276 | if (!confirm || confirm === 'No') { 277 | return; 278 | } 279 | 280 | runCommand('prisma db push', 'npx prisma db push'); 281 | } 282 | 283 | /** 284 | * Seeds the Prisma schema to the database. 285 | * 286 | * @function prismaSeed 287 | * @public 288 | * @memberof TerminalController 289 | * @example 290 | * await controller.prismaSeed(); 291 | * 292 | * @returns {Promise} - No return value 293 | */ 294 | async prismaSeed(): Promise { 295 | const confirm = await pickItem( 296 | ['Yes', 'No'], 297 | 'Are you sure you want to seed the Prisma schema to the database?', 298 | ); 299 | 300 | if (!confirm || confirm === 'No') { 301 | return; 302 | } 303 | 304 | runCommand('prisma db seed', 'npx prisma db seed'); 305 | } 306 | 307 | /** 308 | * Formats the Prisma schema. 309 | * 310 | * @function prismaFormat 311 | * @public 312 | * @memberof TerminalController 313 | * @example 314 | * controller.prismaFormat(); 315 | * 316 | * @returns {void} - No return value 317 | */ 318 | prismaFormat(): void { 319 | runCommand('prisma format', 'npx prisma format'); 320 | } 321 | 322 | /** 323 | * Generates the Prisma schema. 324 | * 325 | * @function prismaGenerate 326 | * @public 327 | * @memberof TerminalController 328 | * @example 329 | * controller.prismaGenerate(); 330 | * 331 | * @returns {void} - No return value 332 | */ 333 | prismaGenerate(): void { 334 | runCommand('prisma generate', 'npx prisma generate'); 335 | } 336 | 337 | /** 338 | * Initializes Prisma. 339 | * 340 | * @function prismaInit 341 | * @public 342 | * @memberof TerminalController 343 | * @example 344 | * controller.prismaInit(); 345 | * 346 | * @returns {void} - No return value 347 | */ 348 | prismaInit(): void { 349 | runCommand('prisma init', 'npx prisma init'); 350 | } 351 | 352 | /** 353 | * Deploys the Prisma schema. 354 | * 355 | * @function prismaMigrateDeploy 356 | * @public 357 | * @memberof TerminalController 358 | * @example 359 | * await controller.prismaMigrateDeploy(); 360 | * 361 | * @returns {Promise} - No return value 362 | */ 363 | async prismaMigrateDeploy(): Promise { 364 | const confirm = await pickItem( 365 | ['Yes', 'No'], 366 | 'Are you sure you want to deploy the Prisma schema?', 367 | ); 368 | 369 | if (!confirm || confirm === 'No') { 370 | return; 371 | } 372 | 373 | runCommand('prisma migrate deploy', 'npx prisma migrate deploy'); 374 | } 375 | 376 | /** 377 | * Devs the Prisma schema. 378 | * 379 | * @function prismaMigrateDev 380 | * @public 381 | * @memberof TerminalController 382 | * @example 383 | * await controller.prismaMigrateDev(); 384 | * 385 | * @returns {Promise} - No return value 386 | */ 387 | async prismaMigrateDev(): Promise { 388 | const confirm = await pickItem( 389 | ['Yes', 'No'], 390 | 'Are you sure you want to migrate the Prisma schema?', 391 | ); 392 | 393 | if (!confirm || confirm === 'No') { 394 | return; 395 | } 396 | 397 | runCommand('prisma migrate dev', 'npx prisma migrate dev'); 398 | } 399 | 400 | /** 401 | * Resets the Prisma schema. 402 | * 403 | * @function prismaMigrateReset 404 | * @public 405 | * @memberof TerminalController 406 | * @example 407 | * await controller.prismaMigrateReset(); 408 | * 409 | * @returns {Promise} - No return value 410 | */ 411 | async prismaMigrateReset(): Promise { 412 | const confirm = await pickItem( 413 | ['Yes', 'No'], 414 | 'Are you sure you want to reset the Prisma schema?', 415 | ); 416 | 417 | if (!confirm || confirm === 'No') { 418 | return; 419 | } 420 | 421 | runCommand('prisma migrate reset', 'npx prisma migrate reset'); 422 | } 423 | 424 | /** 425 | * Shows the Prisma schema status. 426 | * 427 | * @function prismaMigrateStatus 428 | * @public 429 | * @memberof TerminalController 430 | * @example 431 | * controller.prismaMigrateStatus(); 432 | * 433 | * @returns {void} - No return value 434 | */ 435 | prismaMigrateStatus(): void { 436 | runCommand('prisma migrate status', 'npx prisma migrate status'); 437 | } 438 | 439 | /** 440 | * Opens the Prisma studio. 441 | * 442 | * @function prismaStudio 443 | * @public 444 | * @memberof TerminalController 445 | * @example 446 | * controller.prismaStudio(); 447 | * 448 | * @returns {void} - No return value 449 | */ 450 | prismaStudio(): void { 451 | runCommand('prisma studio', 'npx prisma studio'); 452 | } 453 | 454 | /** 455 | * Validates the Prisma schema. 456 | * 457 | * @function prismaValidate 458 | * @public 459 | * @memberof TerminalController 460 | * @example 461 | * controller.prismaValidate(); 462 | * 463 | * @returns {void} - No return value 464 | */ 465 | prismaValidate(): void { 466 | runCommand('prisma validate', 'npx prisma validate'); 467 | } 468 | 469 | /** 470 | * Generates the Drizzle schema migration. 471 | * 472 | * @function drizzleGenerate 473 | * @public 474 | * @memberof TerminalController 475 | * @example 476 | * await controller.drizzleGenerate(); 477 | * 478 | * @returns {Promise} - No return value 479 | */ 480 | async drizzleGenerate(): Promise { 481 | const type = await pickItem( 482 | ['pg', 'mysql', 'sqlite'], 483 | 'For which database do you want to the Drizzle schema migration?', 484 | ); 485 | 486 | if (!type) { 487 | return; 488 | } 489 | 490 | runCommand('drizzle-kit generate', `npx drizzle-kit generate:${type}`); 491 | } 492 | 493 | /** 494 | * Pulls the Drizzle schema. 495 | * 496 | * @function drizzlePull 497 | * @public 498 | * @memberof TerminalController 499 | * @example 500 | * await controller.drizzlePull(); 501 | * 502 | * @returns {Promise} - No return value 503 | */ 504 | async drizzlePull(): Promise { 505 | const type = await pickItem( 506 | ['pg', 'mysql', 'sqlite'], 507 | 'From which database do you want to pull the Drizzle schema?', 508 | ); 509 | 510 | if (!type) { 511 | return; 512 | } 513 | 514 | const confirm = await pickItem( 515 | ['Yes', 'No'], 516 | 'Are you sure you want to pull the Drizzle schema to the database?', 517 | ); 518 | 519 | if (!confirm || confirm === 'No') { 520 | return; 521 | } 522 | 523 | runCommand('drizzle-kit pull', `npx drizzle-kit pull:${type}`); 524 | } 525 | 526 | /** 527 | * Pushes the Drizzle schema. 528 | * 529 | * @function drizzlePush 530 | * @public 531 | * @memberof TerminalController 532 | * @example 533 | * await controller.drizzlePush(); 534 | * 535 | * @returns {Promise} - No return value 536 | */ 537 | async drizzlePush(): Promise { 538 | const type = await pickItem( 539 | ['pg', 'mysql', 'sqlite'], 540 | 'To which database do you want to push the Drizzle schema?', 541 | ); 542 | 543 | if (!type) { 544 | return; 545 | } 546 | 547 | const confirm = await pickItem( 548 | ['Yes', 'No'], 549 | 'Are you sure you want to push the Drizzle schema to the database?', 550 | ); 551 | 552 | if (!confirm || confirm === 'No') { 553 | return; 554 | } 555 | 556 | runCommand('drizzle-kit push', `npx drizzle-kit push:${type}`); 557 | } 558 | 559 | /** 560 | * Drops the Drizzle schema. 561 | * 562 | * @function drizzleDrop 563 | * @public 564 | * @memberof TerminalController 565 | * @example 566 | * await controller.drizzleDrop(); 567 | * 568 | * @returns {Promise} - No return value 569 | */ 570 | async drizzleDrop(): Promise { 571 | const confirm = await pickItem( 572 | ['Yes', 'No'], 573 | 'Are you sure you want to drop the Drizzle schema to the database?', 574 | ); 575 | 576 | if (!confirm || confirm === 'No') { 577 | return; 578 | } 579 | 580 | runCommand('drizzle-kit drop', 'npx drizzle-kit drop'); 581 | } 582 | 583 | /** 584 | * Migrates the Drizzle schema. 585 | * 586 | * @function drizzleMigrate 587 | * @public 588 | * @memberof TerminalController 589 | * @example 590 | * await controller.drizzleMigrate(); 591 | * 592 | * @returns {Promise} - No return value 593 | */ 594 | async drizzleUp(): Promise { 595 | const type = await pickItem( 596 | ['pg', 'mysql', 'sqlite'], 597 | 'To which database do you want to migrate the Drizzle schema?', 598 | ); 599 | 600 | if (!type) { 601 | return; 602 | } 603 | 604 | const confirm = await pickItem( 605 | ['Yes', 'No'], 606 | 'Are you sure you want to migrate the Drizzle schema to the database?', 607 | ); 608 | 609 | if (!confirm || confirm === 'No') { 610 | return; 611 | } 612 | 613 | runCommand('drizzle-kit up', `npx drizzle-kit up:${type}`); 614 | } 615 | 616 | /** 617 | * Migrates the Drizzle schema. 618 | * 619 | * @function drizzleMigrate 620 | * @public 621 | * @memberof TerminalController 622 | * @example 623 | * await controller.drizzleMigrate(); 624 | * 625 | * @returns {Promise} - No return value 626 | */ 627 | async drizzleCkeck(): Promise { 628 | const type = await pickItem( 629 | ['pg', 'mysql', 'sqlite'], 630 | 'For which database do you want to check the Drizzle schema migration?', 631 | ); 632 | 633 | if (!type) { 634 | return; 635 | } 636 | 637 | runCommand('drizzle-kit check', `npx drizzle-kit check:${type}`); 638 | } 639 | 640 | /** 641 | * Opens the Drizzle studio. 642 | * 643 | * @function drizzleStudio 644 | * @public 645 | * @memberof TerminalController 646 | * @example 647 | * controller.drizzleStudio(); 648 | * 649 | * @returns {void} - No return value 650 | */ 651 | drizzleStudio(): void { 652 | runCommand('drizzle-kit studio', 'npx drizzle-kit studio'); 653 | } 654 | } 655 | -------------------------------------------------------------------------------- /src/app/controllers/transform.controller.ts: -------------------------------------------------------------------------------- 1 | import jsonSchemaToZod from 'json-schema-to-zod'; 2 | import JsonToTS from 'json-to-ts'; 3 | import * as json5 from 'json5'; 4 | import { Range, TextEditor, window, workspace } from 'vscode'; 5 | 6 | // Import the helper functions 7 | import { showError } from '../helpers'; 8 | 9 | /** 10 | * The TransformController class. 11 | * 12 | * @class 13 | * @classdesc The class that represents the example controller. 14 | * @export 15 | * @public 16 | * @example 17 | * const controller = new TransformController(); 18 | */ 19 | export class TransformController { 20 | // ----------------------------------------------------------------- 21 | // Methods 22 | // ----------------------------------------------------------------- 23 | 24 | // Public methods 25 | /** 26 | * The json2ts method. 27 | * 28 | * @function json2ts 29 | * @public 30 | * @async 31 | * @memberof TransformController 32 | * @example 33 | * await controller.json2ts(); 34 | * 35 | * @returns {Promise} The result 36 | */ 37 | async json2ts(): Promise { 38 | let editor; 39 | 40 | if (workspace.workspaceFolders) { 41 | editor = window.activeTextEditor; 42 | } else { 43 | showError('No text editor is active.'); 44 | return; 45 | } 46 | 47 | const selection = editor?.selection; 48 | 49 | if (selection && !selection.isEmpty) { 50 | const selectionRange = new Range( 51 | selection.start.line, 52 | selection.start.character, 53 | selection.end.line, 54 | selection.end.character, 55 | ); 56 | 57 | let text = editor?.document.getText(selectionRange) || ''; 58 | 59 | const languageId = editor?.document.languageId || ''; 60 | 61 | if ( 62 | [ 63 | 'javascript', 64 | 'javascriptreact', 65 | 'typescript', 66 | 'typescriptreact', 67 | ].includes(languageId) 68 | ) { 69 | text = text 70 | .replace(/'([^']+)'/g, '"$1"') 71 | .replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2":') 72 | .replace(/,*\s*\n*\]/g, ']') 73 | .replace(/{\s*\n*/g, '{') 74 | .replace(/,*\s*\n*};*/g, '}'); 75 | } 76 | 77 | const jsonSchema = this.tryParseJSONObject(text); 78 | 79 | if (!jsonSchema) { 80 | showError('Invalid JSON Schema!'); 81 | return; 82 | } 83 | 84 | const tsSchema = JsonToTS(jsonSchema) 85 | .map((itf) => `export ${itf}\n`) 86 | .join('\n'); 87 | 88 | const document = await workspace.openTextDocument({ 89 | language: 'typescript', 90 | content: tsSchema, 91 | }); 92 | 93 | return await window.showTextDocument(document); 94 | } 95 | 96 | showError('No text is selected!'); 97 | return; 98 | } 99 | 100 | /** 101 | * The json2zod method. 102 | * 103 | * @function json2zod 104 | * @public 105 | * @async 106 | * @memberof TransformController 107 | * @example 108 | * await controller.json2zod(); 109 | * 110 | * @returns {Promise} The result 111 | */ 112 | async json2zod(): Promise { 113 | let editor; 114 | 115 | if (workspace.workspaceFolders) { 116 | editor = window.activeTextEditor; 117 | } else { 118 | showError('No text editor is active.'); 119 | return; 120 | } 121 | 122 | const selection = editor?.selection; 123 | 124 | if (selection && !selection.isEmpty) { 125 | const selectionRange = new Range( 126 | selection.start.line, 127 | selection.start.character, 128 | selection.end.line, 129 | selection.end.character, 130 | ); 131 | 132 | let text = editor?.document.getText(selectionRange) || ''; 133 | 134 | const languageId = editor?.document.languageId || ''; 135 | 136 | if ( 137 | [ 138 | 'javascript', 139 | 'javascriptreact', 140 | 'typescript', 141 | 'typescriptreact', 142 | ].includes(languageId) 143 | ) { 144 | text = text 145 | .replace(/'([^']+)'/g, '"$1"') 146 | .replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2":') 147 | .replace(/,*\s*\n*\]/g, ']') 148 | .replace(/{\s*\n*/g, '{') 149 | .replace(/,*\s*\n*};*/g, '}'); 150 | } 151 | 152 | const jsonSchema = this.tryParseJSONObject(text); 153 | 154 | if (!jsonSchema) { 155 | showError('Invalid JSON Schema!'); 156 | return; 157 | } 158 | 159 | const zodSchema = jsonSchemaToZod(jsonSchema); 160 | 161 | const content = `import { z } from 'zod'; 162 | 163 | export default ${zodSchema}; 164 | `; 165 | 166 | const document = await workspace.openTextDocument({ 167 | language: 'typescript', 168 | content, 169 | }); 170 | 171 | return await window.showTextDocument(document); 172 | } 173 | 174 | showError('No text is selected!'); 175 | return; 176 | } 177 | 178 | // Private methods 179 | /** 180 | * The tryParseJSONObject method. 181 | * 182 | * @private 183 | * @memberof TransformController 184 | * @param {string} str - The string to parse 185 | * @returns {boolean | object} The result 186 | * @example 187 | * const object = controller.tryParseJSONObject(str); 188 | * 189 | * @returns {boolean | object} The result 190 | */ 191 | private tryParseJSONObject(str: string): boolean | object { 192 | try { 193 | var object = json5.parse(str); 194 | 195 | if (object && typeof object === 'object') { 196 | return object; 197 | } 198 | } catch (e) { 199 | return false; 200 | } 201 | 202 | return false; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/app/helpers/command.helper.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | 3 | /** 4 | * Runs a command in the terminal 5 | * 6 | * @param {string} title - Title of the terminal 7 | * @param {string} command - Command to run 8 | * @example 9 | * runCommand('echo "Hello, World!"'); 10 | * 11 | * @returns {Promise} - No return value 12 | */ 13 | export const runCommand = async ( 14 | title: string, 15 | command: string, 16 | ): Promise => { 17 | const terminal = window.createTerminal(title); 18 | terminal.show(); 19 | terminal.sendText(command); 20 | }; 21 | -------------------------------------------------------------------------------- /src/app/helpers/dialog.helper.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | 3 | /** 4 | * Displays a message box with the provided message 5 | * 6 | * @param {string} prompt - The prompt to display 7 | * @param {string} placeHolder - The input placeholder 8 | * @param {string} currentPath - The current path 9 | * @param {string} validate - The validation function 10 | * @example 11 | * const path = await getPath('Enter a path', 'src/app', 'src/app', (path) => { 12 | * if (path.length === 0) { 13 | * return 'Path cannot be empty'; 14 | * }); 15 | * 16 | * @returns {Promise} - The selected path 17 | */ 18 | export const getPath = async ( 19 | prompt: string, 20 | placeHolder: string, 21 | currentPath: string, 22 | validate: (path: string) => string | undefined, 23 | ): Promise => { 24 | return await window.showInputBox({ 25 | prompt, 26 | placeHolder, 27 | value: currentPath, 28 | validateInput: validate, 29 | }); 30 | }; 31 | 32 | /** 33 | * Displays a message box with the provided message 34 | * 35 | * @param {string} prompt - The prompt to display 36 | * @param {string} placeHolder - The input placeholder 37 | * @param {string} validate - The validation function 38 | * @example 39 | * const name = await getName('Enter a name', 'foo', (name) => { 40 | * if (name.length === 0) { 41 | * return 'Name cannot be empty'; 42 | * }); 43 | * 44 | * @returns {Promise} - The selected name 45 | */ 46 | export const getName = async ( 47 | prompt: string, 48 | placeHolder: string, 49 | validate: (name: string) => string | undefined, 50 | ): Promise => { 51 | return await window.showInputBox({ 52 | prompt, 53 | placeHolder, 54 | validateInput: validate, 55 | }); 56 | }; 57 | 58 | /** 59 | * Displays a message box with the provided message 60 | * 61 | * @param {string[]} items - The list of items to select from 62 | * @param {string} placeHolder - The input placeholder 63 | * @example 64 | * const item = await pickItem(['foo', 'bar'], 'Select an item'); 65 | * 66 | * @returns {Promise} - The selected item 67 | */ 68 | export const pickItem = async ( 69 | items: string[], 70 | placeHolder: string, 71 | ): Promise => { 72 | return await window.showQuickPick(items, { 73 | placeHolder, 74 | }); 75 | }; 76 | 77 | /** 78 | * Displays a message box with the provided message 79 | * 80 | * @param {string} message - The message to display 81 | * @example 82 | * await showMessage('Hello, world!'); 83 | * 84 | * @returns {Promise} - No return value 85 | */ 86 | export const showMessage = async (message: string): Promise => { 87 | window.showInformationMessage(message); 88 | }; 89 | 90 | /** 91 | * Displays a message box with the provided message 92 | * 93 | * @param {string} message - The message to display 94 | * @example 95 | * await showError('An error occurred'); 96 | * 97 | * @returns {Promise} - No return value 98 | */ 99 | export const showError = async (message: string): Promise => { 100 | window.showErrorMessage(message); 101 | }; 102 | 103 | /** 104 | * Displays a message box with the provided message 105 | * 106 | * @param {string} message - The message to display 107 | * @example 108 | * await showWarning('This is a warning'); 109 | * 110 | * @returns {Promise} - No return value 111 | */ 112 | export const showWarning = async (message: string): Promise => { 113 | window.showWarningMessage(message); 114 | }; 115 | -------------------------------------------------------------------------------- /src/app/helpers/filesystem.helper.ts: -------------------------------------------------------------------------------- 1 | import { access, existsSync, mkdirSync, open, writeFile } from 'fs'; 2 | import { dirname, join } from 'path'; 3 | import { FilePermission, FileStat, Uri, window, workspace } from 'vscode'; 4 | 5 | /** 6 | * Reads the contents of the file specified in the path. 7 | * 8 | * @param {string} path - Path to the source directory 9 | * @param {object} [options] - Options for the directoryMap function 10 | * @param {string[]} [options.extensions] - File extensions to include 11 | * @param {string[]} [options.ignore] - Directories to ignore 12 | * @param {number} [options.maxResults] - An upper-bound for the result 13 | * @example 14 | * const files = await directoryMap('src', { 15 | * extensions: ['ts'], 16 | * ignore: ['**​/node_modules/**'], 17 | * maxResults: 100, 18 | * }); 19 | * 20 | * @returns {Promise} - Array of files 21 | */ 22 | export const directoryMap = async ( 23 | path: string, 24 | options?: { extensions?: string[]; ignore?: string[]; maxResults?: number }, 25 | ): Promise => { 26 | let includes = path === '/' ? '**/*' : `${path}/**/*`; 27 | let exclude = ''; 28 | 29 | if (options && options.extensions && options.extensions.length) { 30 | includes += `.{${options.extensions.join(',')}}`; 31 | } 32 | 33 | if (options && options.ignore && options.ignore.length) { 34 | exclude = `{${options.ignore.join(',')}}`; 35 | } 36 | 37 | return workspace.findFiles(includes, exclude, options?.maxResults); 38 | }; 39 | 40 | /** 41 | * Writes data to the file specified in the path. If the file does not exist then the function will create it. 42 | * 43 | * @param {string} path - Path to the file 44 | * @param {string} filename - Name of the file 45 | * @param {string} data - Data to write to the file 46 | * @example 47 | * await saveFile('src', 'file.ts', 'console.log("Hello World")'); 48 | * 49 | * @returns {Promise} - Confirmation of the write operation 50 | */ 51 | export const saveFile = async ( 52 | path: string, 53 | filename: string, 54 | data: string, 55 | ): Promise => { 56 | let folder: string = ''; 57 | 58 | if (workspace.workspaceFolders) { 59 | folder = workspace.workspaceFolders[0].uri.fsPath; 60 | } else { 61 | window.showErrorMessage('The file has not been created!'); 62 | return; 63 | } 64 | 65 | const file = join(folder, path, filename); 66 | 67 | if (!existsSync(dirname(file))) { 68 | mkdirSync(dirname(file), { recursive: true }); 69 | } 70 | 71 | access(file, (err: any) => { 72 | if (err) { 73 | open(file, 'w+', (err: any, fd: any) => { 74 | if (err) { 75 | throw err; 76 | } 77 | 78 | writeFile(fd, data, 'utf8', (err: any) => { 79 | if (err) { 80 | throw err; 81 | } 82 | 83 | const openPath = Uri.file(file); 84 | 85 | workspace.openTextDocument(openPath).then((filename) => { 86 | window.showTextDocument(filename); 87 | }); 88 | }); 89 | }); 90 | 91 | window.showInformationMessage('Successfully created the file!'); 92 | } else { 93 | window.showWarningMessage('Name already exist!'); 94 | } 95 | }); 96 | }; 97 | 98 | /** 99 | * Deletes ALL files contained in the supplied path. 100 | * 101 | * @param {string} path - Path to the directory 102 | * @param {object} [options] - Options for the deleteFiles function 103 | * @param {boolean} [options.recursive] - Delete the content recursively if a folder is denoted. 104 | * @param {boolean} [options.useTrash] - Use the trash instead of permanently deleting the files. 105 | * @example 106 | * await deleteFiles('src'); 107 | * 108 | * @returns {Promise} - No return value 109 | */ 110 | export const deleteFiles = async ( 111 | path: string, 112 | options?: { recursive?: boolean; useTrash?: boolean }, 113 | ): Promise => { 114 | const files = await workspace.findFiles(`${path}/**/*`); 115 | 116 | files.forEach((file) => { 117 | access(file.path, (err: any) => { 118 | if (err) { 119 | throw err; 120 | } 121 | 122 | workspace.fs.delete(file, options); 123 | }); 124 | }); 125 | }; 126 | 127 | /** 128 | * Returns an array of filenames in the supplied path. 129 | * 130 | * @param {string} path - Path to the directory 131 | * @param {object} [options] - Options for the directoryMap function 132 | * @param {string[]} [options.extensions] - File extensions to include 133 | * @param {string[]} [options.ignore] - Directories to ignore 134 | * @param {number} [options.maxResults] - An upper-bound for the result. 135 | * @example 136 | * const files = await getFilenames('src'); 137 | * 138 | * @returns {Promise} - Array of filenames 139 | */ 140 | export const getFilenames = async ( 141 | path: string, 142 | options?: { extensions?: string[]; ignore?: string[]; maxResults?: number }, 143 | ): Promise => { 144 | const files = await directoryMap(path, options); 145 | 146 | return files.map((file) => file.path); 147 | }; 148 | 149 | /** 150 | * Returns an object containing the file information for the supplied path. 151 | * 152 | * @param {string} path - Path to the file 153 | * @example 154 | * const fileInfo = await getFileInfo('src/file.ts'); 155 | * 156 | * @returns {Promise} - File information 157 | */ 158 | export const getFileInfo = async (path: string): Promise => { 159 | return await workspace.fs.stat(Uri.file(path)); 160 | }; 161 | 162 | /** 163 | * Returns an object containing the directory information for the supplied path. 164 | * 165 | * @param {string} path - Path to the directory 166 | * @example 167 | * const dirInfo = await getDirFileInfo('src'); 168 | * 169 | * @returns {Promise} - Directory information 170 | */ 171 | export const getDirFileInfo = async (path: string): Promise => { 172 | return await workspace.fs.stat(Uri.file(path)); 173 | }; 174 | 175 | /** 176 | * Returns the symbolic permissions for the supplied path. 177 | * 178 | * @param {string} path - Path to the file 179 | * @example 180 | * const permissions = await symbolicPermissions('src/file.ts'); 181 | * 182 | * @returns {Promise} - Symbolic permissions 183 | */ 184 | export const symbolicPermissions = async ( 185 | path: string, 186 | ): Promise => { 187 | return await workspace.fs 188 | .stat(Uri.file(path)) 189 | .then((file) => file.permissions); 190 | }; 191 | 192 | /** 193 | * Returns the octal permissions for the supplied path. 194 | * 195 | * @param {string} path - Path to the file 196 | * @example 197 | * const permissions = await octalPermissions('src/file.ts'); 198 | * 199 | * @returns {Promise} - Octal permissions 200 | */ 201 | export const octalPermissions = async ( 202 | path: string, 203 | ): Promise => { 204 | const file = await workspace.fs 205 | .stat(Uri.file(path)) 206 | .then((file) => file.permissions); 207 | 208 | return file?.toString(8); 209 | }; 210 | 211 | /** 212 | * Returns a boolean indicating whether the two supplied files are the same. 213 | * 214 | * @param {string} file1 - Path to the first file 215 | * @param {string} file2 - Path to the second file 216 | * @example 217 | * const isSame = await sameFile('src/file1.ts', 'src/file2.ts'); 218 | * 219 | * @returns {Promise} - Confirmation of the comparison 220 | */ 221 | export const sameFile = async ( 222 | file1: string, 223 | file2: string, 224 | ): Promise => { 225 | const file1Info = await getFileInfo(file1); 226 | const file2Info = await getFileInfo(file2); 227 | 228 | return file1Info === file2Info; 229 | }; 230 | 231 | /** 232 | * Sets the realpath for the supplied path. 233 | * 234 | * @param {string} path - Path to the file 235 | * @example 236 | * await setRealpath('src/file.ts'); 237 | * 238 | * @returns {Promise} - Uri or FileStat object for the file 239 | */ 240 | export const setRealpath = async (path: string): Promise => { 241 | return (await workspace.fs.stat) 242 | ? await workspace.fs.stat(Uri.file(path)) 243 | : await workspace 244 | .openTextDocument(Uri.file(path)) 245 | .then((filename) => filename.uri); 246 | }; 247 | 248 | /** 249 | * Returns the relative path from the workspace root to the supplied path. 250 | * 251 | * @param {string} path - Path to the file 252 | * @example 253 | * const relativePath = await getRelativePath('src/file.ts'); 254 | * 255 | * @returns {Promise} - Relative path 256 | */ 257 | export const getRelativePath = async (path: string): Promise => { 258 | return workspace.asRelativePath(path); 259 | }; 260 | 261 | /** 262 | * Returns the realpath for the supplied path. 263 | * 264 | * @param {string} path - Path to the file 265 | * @example 266 | * const realpath = await getRealpath('src/file.ts'); 267 | * 268 | * @returns {Promise} - Realpath 269 | */ 270 | export const getRealpath = async (path: string): Promise => { 271 | return Uri.file(path).fsPath; 272 | }; 273 | 274 | /** 275 | * Returns a boolean indicating whether the supplied path exists. 276 | * 277 | * @param {string} path - Path to the file or directory 278 | * @example 279 | * const fileExists = await exists('src/file.ts'); 280 | * 281 | * @returns {Promise} - Confirmation of the existence 282 | */ 283 | export const exists = async (path: string): Promise => { 284 | return existsSync(path); 285 | }; 286 | 287 | // isDirectory 288 | /** 289 | * Returns a boolean indicating whether the supplied path is a directory. 290 | * 291 | * @param {string} path - Path to the file or directory 292 | * @example 293 | * const isDir = await isDirectory('src'); 294 | * 295 | * @returns {Promise} - Confirmation of the directory 296 | */ 297 | export const isDirectory = async (path: string): Promise => { 298 | return (await workspace.fs.stat(Uri.file(path))).type === 2; 299 | }; 300 | -------------------------------------------------------------------------------- /src/app/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './command.helper'; 2 | export * from './dialog.helper'; 3 | export * from './filesystem.helper'; 4 | export * from './inflector.helper'; 5 | -------------------------------------------------------------------------------- /src/app/helpers/inflector.helper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Changes a string of words separated by spaces or underscores to camel case. 3 | * 4 | * @param {string} str - The string to camelize 5 | * @example 6 | * camelize('foo bar'); 7 | * 8 | * @returns {string} - The camelized string 9 | */ 10 | export const camelize = (str: string): string => { 11 | return str 12 | .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => 13 | index === 0 ? word.toLowerCase() : word.toUpperCase(), 14 | ) 15 | .replace(/\s+/g, ''); 16 | }; 17 | 18 | /** 19 | * Changes a string of words separated by spaces or underscores to pascal case. 20 | * 21 | * @param {string} str - The string to pascalize 22 | * @example 23 | * pascalize('foo bar'); 24 | * 25 | * @returns {string} - The pascalized string 26 | */ 27 | export const pascalize = (str: string): string => { 28 | return str 29 | .replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => word.toUpperCase()) 30 | .replace(/\s+/g, ''); 31 | }; 32 | 33 | /** 34 | * Changes a string of words separated by spaces or camel or pascal case. 35 | * 36 | * @param {string} str - The string to underscore 37 | * @example 38 | * underscore('foo bar'); 39 | * 40 | * @returns {string} - The underscored string 41 | */ 42 | export const underscore = (str: string): string => { 43 | return str 44 | .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => 45 | index === 0 ? word.toLowerCase() : `_${word.toLowerCase()}`, 46 | ) 47 | .replace(/\s+/g, '_'); 48 | }; 49 | 50 | /** 51 | * Changes a string of words separated by spaces or camel or pascal case to lowercase with underscores. 52 | * 53 | * @param {string} str - The string to decamelize 54 | * @example 55 | * decamelize('foo bar'); 56 | * 57 | * @returns {string} - The decamelized string 58 | */ 59 | export const decamelize = (str: string): string => { 60 | return str 61 | .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => 62 | index === 0 ? word.toLowerCase() : `_${word.toLowerCase()}`, 63 | ) 64 | .replace(/\s+/g, '_'); 65 | }; 66 | 67 | /** 68 | * Changes a string of words separated by spaces or camel or pascal case to human readable form. 69 | * 70 | * @param {string} str - The string to humanize 71 | * @example 72 | * humanize('foo bar'); 73 | * 74 | * @returns {string} - The humanized string 75 | */ 76 | export const humanize = (str: string): string => { 77 | return str 78 | .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => 79 | index === 0 ? word.toUpperCase() : ` ${word.toLowerCase()}`, 80 | ) 81 | .replace(/\s+/g, ' '); 82 | }; 83 | 84 | /** 85 | * Checks if a string is pluralizable. 86 | * 87 | * @param {string} str - The string to check 88 | * @example 89 | * isPluralizable('foo'); 90 | * 91 | * @returns {boolean} - Whether the string is pluralizable 92 | */ 93 | export const isPluralizable = (str: string): boolean => { 94 | return str.endsWith('s'); 95 | }; 96 | 97 | /** 98 | * Changes a string of words separated by spaces or camel or pascal case to lowercase with dashes. 99 | * 100 | * @param {string} str - The string to dasherize 101 | * @example 102 | * dasherize('foo bar'); 103 | * 104 | * @returns {string} - The dasherized string 105 | */ 106 | export const dasherize = (str: string): string => { 107 | return str 108 | .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => 109 | index === 0 ? word.toLowerCase() : `-${word.toLowerCase()}`, 110 | ) 111 | .replace(/\s+/g, '-'); 112 | }; 113 | 114 | /** 115 | * Changes a number to its ordinal form. 116 | * 117 | * @param {number} num - The number to ordinalize 118 | * @example 119 | * ordinalize(1); 120 | * 121 | * @returns {string} - The ordinalized number 122 | */ 123 | export const ordinal = (num: number): string => { 124 | const j = num % 10; 125 | const k = num % 100; 126 | 127 | if (j === 1 && k !== 11) { 128 | return `${num}st`; 129 | } 130 | if (j === 2 && k !== 12) { 131 | return `${num}nd`; 132 | } 133 | if (j === 3 && k !== 13) { 134 | return `${num}rd`; 135 | } 136 | return `${num}th`; 137 | }; 138 | 139 | /** 140 | * Changes a number to its ordinal form. 141 | * 142 | * @param {number} num - The number to ordinalize 143 | * @example 144 | * ordinalize(1); 145 | * 146 | * @returns {string} - The ordinalized number 147 | */ 148 | export const ordinalize = (num: number): string => { 149 | return `${num}${ordinal(num)}`; 150 | }; 151 | 152 | /** 153 | * Changes a string to its plural form. 154 | * 155 | * @param {string} str - The string to pluralize 156 | * @example 157 | * pluralize('foo'); 158 | * 159 | * @returns {string} - The pluralized string 160 | */ 161 | export const pluralize = (str: string): string => { 162 | if (str.endsWith('y')) { 163 | return str.slice(0, -1) + 'ies'; 164 | } 165 | if (str.endsWith('s')) { 166 | return str; 167 | } 168 | return str + 's'; 169 | }; 170 | 171 | /** 172 | * Changes a string to its singular form. 173 | * 174 | * @param {string} str - The string to singularize 175 | * @example 176 | * singularize('foos'); 177 | * 178 | * @returns {string} - The singularized string 179 | */ 180 | export const singularize = (str: string): string => { 181 | if (str.endsWith('ies')) { 182 | return str.slice(0, -3) + 'y'; 183 | } 184 | if (str.endsWith('s')) { 185 | return str.slice(0, -1); 186 | } 187 | return str; 188 | }; 189 | 190 | /** 191 | * Changes a string to its title case form. 192 | * 193 | * @param {string} str - The string to titleize 194 | * @example 195 | * titleize('foo bar'); 196 | * 197 | * @returns {string} - The titleized string 198 | */ 199 | export const titleize = (str: string): string => { 200 | return str 201 | .split(' ') 202 | .map((word) => word[0].toUpperCase() + word.slice(1)) 203 | .join(' '); 204 | }; 205 | -------------------------------------------------------------------------------- /src/app/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './node.model'; 2 | -------------------------------------------------------------------------------- /src/app/models/node.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Command, 3 | ThemeIcon, 4 | TreeItem, 5 | TreeItemCollapsibleState, 6 | TreeItemLabel, 7 | Uri, 8 | } from 'vscode'; 9 | 10 | /** 11 | * The Node class 12 | * 13 | * @class 14 | * @classdesc The class that represents a node in the tree view. 15 | * @export 16 | * @public 17 | * @extends {TreeItem} 18 | * @property {string | TreeItemLabel} label - The label 19 | * @property {string | Uri | { light: Uri; dark: Uri } | ThemeIcon} [iconPath] - The icon path 20 | * @property {Command} [command] - The command 21 | * @property {Uri} [resourceUri] - The resource URI 22 | * @property {string} [contextValue] - The context value 23 | * @property {Node[]} [children] - The children 24 | * @example 25 | * const node = new Node('About Us', TreeItemCollapsibleState.None, 'about', { 26 | * title: 'About Us', 27 | * command: 'angular.feedback.aboutUs', 28 | * }); 29 | * 30 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeItem 31 | */ 32 | export class NodeModel extends TreeItem { 33 | // ----------------------------------------------------------------- 34 | // Properties 35 | // ----------------------------------------------------------------- 36 | 37 | // Public properties 38 | /** 39 | * The children. 40 | * @type {NodeModel[]} 41 | * @public 42 | * @memberof NodeModel 43 | * @example 44 | * node.children = []; 45 | */ 46 | children?: NodeModel[]; 47 | 48 | // ----------------------------------------------------------------- 49 | // Constructor 50 | // ----------------------------------------------------------------- 51 | 52 | /** 53 | * The constructor 54 | * 55 | * @constructor 56 | * @param {string | TreeItemLabel} label - The label 57 | * @param {string | Uri | { light: Uri; dark: Uri } | ThemeIcon} [iconPath] - The icon path 58 | * @param {Command} [command] - The command 59 | * @param {Uri} [resourceUri] - The resource URI 60 | * @param {string} [contextValue] - The context value 61 | * @param {NodeModel[]} [children] - The children 62 | * @example 63 | * const node = new Node('About Us', new ThemeIcon('info'), { 64 | * title: 'About Us', 65 | * command: 'angular.feedback.aboutUs', 66 | * }); 67 | */ 68 | constructor( 69 | readonly label: string | TreeItemLabel, 70 | readonly iconPath?: string | Uri | { light: Uri; dark: Uri } | ThemeIcon, 71 | readonly command?: Command, 72 | readonly resourceUri?: Uri, 73 | readonly contextValue?: string, 74 | children?: NodeModel[], 75 | ) { 76 | super( 77 | label, 78 | children 79 | ? TreeItemCollapsibleState.Expanded 80 | : TreeItemCollapsibleState.None, 81 | ); 82 | this.iconPath = iconPath; 83 | this.resourceUri = resourceUri; 84 | this.command = command; 85 | this.contextValue = contextValue; 86 | this.children = children; 87 | } 88 | 89 | // ----------------------------------------------------------------- 90 | // Methods 91 | // ----------------------------------------------------------------- 92 | 93 | // Public methods 94 | /** 95 | * The setChildren method 96 | * 97 | * @function setChildren 98 | * @param {NodeModel[]} children - The children 99 | * @public 100 | * @memberof NodeModel 101 | * @example 102 | * node.setChildren([]); 103 | * 104 | * @returns {void} The result 105 | */ 106 | setChildren(children: NodeModel[]): void { 107 | this.collapsibleState = TreeItemCollapsibleState.Expanded; 108 | this.children = children; 109 | } 110 | 111 | /** 112 | * The hasChildren method 113 | * 114 | * @function hasChildren 115 | * @public 116 | * @memberof NodeModel 117 | * @example 118 | * const hasChildren = node.hasChildren(); 119 | * 120 | * @returns {boolean} The result 121 | */ 122 | hasChildren(): boolean { 123 | return !!(this.children && this.children.length); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/app/providers/feedback.provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Event, 3 | EventEmitter, 4 | ProviderResult, 5 | ThemeIcon, 6 | TreeDataProvider, 7 | TreeItem, 8 | } from 'vscode'; 9 | 10 | import { EXTENSION_ID } from '../configs'; 11 | import { FeedbackController } from '../controllers'; 12 | import { NodeModel } from '../models'; 13 | 14 | /** 15 | * The FeedbackProvider class 16 | * 17 | * @class 18 | * @classdesc The class that represents the feedback provider. 19 | * @export 20 | * @public 21 | * @implements {TreeDataProvider} 22 | * @property {EventEmitter} _onDidChangeTreeData - The onDidChangeTreeData event emitter 23 | * @property {Event} onDidChangeTreeData - The onDidChangeTreeData event 24 | * @property {FeedbackController} controller - The feedback controller 25 | * @example 26 | * const provider = new FeedbackProvider(); 27 | * 28 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 29 | */ 30 | export class FeedbackProvider implements TreeDataProvider { 31 | // ----------------------------------------------------------------- 32 | // Properties 33 | // ----------------------------------------------------------------- 34 | 35 | // Private properties 36 | /** 37 | * The onDidChangeTreeData event emitter. 38 | * @type {EventEmitter} 39 | * @private 40 | * @memberof FeedbackProvider 41 | * @example 42 | * this._onDidChangeTreeData = new EventEmitter(); 43 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 44 | * 45 | * @see https://code.visualstudio.com/api/references/vscode-api#EventEmitter 46 | */ 47 | private _onDidChangeTreeData: EventEmitter< 48 | NodeModel | undefined | null | void 49 | >; 50 | 51 | // Public properties 52 | /** 53 | * The onDidChangeTreeData event. 54 | * @type {Event} 55 | * @public 56 | * @memberof FeedbackProvider 57 | * @example 58 | * readonly onDidChangeTreeData: Event; 59 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 60 | * 61 | * @see https://code.visualstudio.com/api/references/vscode-api#Event 62 | */ 63 | readonly onDidChangeTreeData: Event; 64 | 65 | // ----------------------------------------------------------------- 66 | // Constructor 67 | // ----------------------------------------------------------------- 68 | 69 | /** 70 | * Constructor for the FeedbackProvider class 71 | * 72 | * @constructor 73 | * @param {FeedbackController} controller - The feedback controller 74 | * @public 75 | * @memberof FeedbackProvider 76 | */ 77 | constructor(readonly controller: FeedbackController) { 78 | this._onDidChangeTreeData = new EventEmitter< 79 | NodeModel | undefined | null | void 80 | >(); 81 | this.onDidChangeTreeData = this._onDidChangeTreeData.event; 82 | } 83 | 84 | // ----------------------------------------------------------------- 85 | // Methods 86 | // ----------------------------------------------------------------- 87 | 88 | // Public methods 89 | /** 90 | * Returns the tree item for the supplied element. 91 | * 92 | * @function getTreeItem 93 | * @param {NodeModel} element - The element 94 | * @public 95 | * @memberof FeedbackProvider 96 | * @example 97 | * const treeItem = provider.getTreeItem(element); 98 | * 99 | * @returns {TreeItem | Thenable} - The tree item 100 | * 101 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 102 | */ 103 | getTreeItem(element: NodeModel): TreeItem | Thenable { 104 | return element; 105 | } 106 | 107 | /** 108 | * Returns the children for the supplied element. 109 | * 110 | * @function getChildren 111 | * @param {NodeModel} [element] - The element 112 | * @public 113 | * @memberof FeedbackProvider 114 | * @example 115 | * const children = provider.getChildren(element); 116 | * 117 | * @returns {ProviderResult} - The children 118 | * 119 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 120 | */ 121 | getChildren(element?: NodeModel): ProviderResult { 122 | if (element) { 123 | return element.children; 124 | } 125 | 126 | return this.getFeedbacks(); 127 | } 128 | 129 | /** 130 | * Refreshes the tree data. 131 | * 132 | * @function refresh 133 | * @public 134 | * @memberof FeedbackProvider 135 | * @example 136 | * provider.refresh(); 137 | * 138 | * @returns {void} - No return value 139 | */ 140 | refresh(): void { 141 | this._onDidChangeTreeData.fire(); 142 | } 143 | 144 | // Private methods 145 | /** 146 | * Returns the feedbacks. 147 | * 148 | * @function getFeedbacks 149 | * @private 150 | * @memberof FeedbackProvider 151 | * @example 152 | * const feedbacks = this.getFeedbacks(); 153 | * 154 | * @returns {NodeModel[]} - The feedbacks 155 | */ 156 | private getFeedbacks(): NodeModel[] { 157 | return [ 158 | new NodeModel('About Us', new ThemeIcon('info'), { 159 | title: 'About Us', 160 | command: `${EXTENSION_ID}.feedback.aboutUs`, 161 | }), 162 | new NodeModel('Report Issues', new ThemeIcon('bug'), { 163 | title: 'Report Issues', 164 | command: `${EXTENSION_ID}.feedback.reportIssues`, 165 | }), 166 | new NodeModel('Rate Us', new ThemeIcon('star'), { 167 | title: 'Rate Us', 168 | command: `${EXTENSION_ID}.feedback.rateUs`, 169 | }), 170 | new NodeModel('Support Us', new ThemeIcon('heart'), { 171 | title: 'Support Us', 172 | command: `${EXTENSION_ID}.feedback.supportUs`, 173 | }), 174 | ]; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/app/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './feedback.provider'; 2 | export * from './list-components.providers'; 3 | export * from './list-files.providers'; 4 | export * from './list-hooks.providers'; 5 | export * from './list-routes.providers'; 6 | -------------------------------------------------------------------------------- /src/app/providers/list-components.providers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Event, 3 | EventEmitter, 4 | ProviderResult, 5 | ThemeIcon, 6 | TreeDataProvider, 7 | TreeItem, 8 | workspace, 9 | } from 'vscode'; 10 | 11 | import { EXTENSION_ID } from '../configs'; 12 | import { ListFilesController } from '../controllers'; 13 | import { NodeModel } from '../models'; 14 | 15 | /** 16 | * The ListComponentsProvider class 17 | * 18 | * @class 19 | * @classdesc The class that represents the list of files provider. 20 | * @export 21 | * @public 22 | * @implements {TreeDataProvider} 23 | * @property {EventEmitter} _onDidChangeTreeData - The onDidChangeTreeData event emitter 24 | * @property {Event} onDidChangeTreeData - The onDidChangeTreeData event 25 | * @property {ListFilesController} controller - The list of files controller 26 | * @example 27 | * const provider = new ListComponentsProvider(); 28 | * 29 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 30 | */ 31 | export class ListComponentsProvider implements TreeDataProvider { 32 | // ----------------------------------------------------------------- 33 | // Properties 34 | // ----------------------------------------------------------------- 35 | 36 | // Private properties 37 | /** 38 | * The onDidChangeTreeData event emitter. 39 | * @type {EventEmitter} 40 | * @private 41 | * @memberof ListComponentsProvider 42 | * @example 43 | * this._onDidChangeTreeData = new EventEmitter(); 44 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 45 | * 46 | * @see https://code.visualstudio.com/api/references/vscode-api#EventEmitter 47 | */ 48 | private _onDidChangeTreeData: EventEmitter< 49 | NodeModel | undefined | null | void 50 | >; 51 | 52 | // Public properties 53 | /** 54 | * The onDidChangeTreeData event. 55 | * @type {Event} 56 | * @public 57 | * @memberof ListComponentsProvider 58 | * @example 59 | * readonly onDidChangeTreeData: Event; 60 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 61 | * 62 | * @see https://code.visualstudio.com/api/references/vscode-api#Event 63 | */ 64 | readonly onDidChangeTreeData: Event; 65 | 66 | // ----------------------------------------------------------------- 67 | // Constructor 68 | // ----------------------------------------------------------------- 69 | 70 | /** 71 | * Constructor for the ListComponentsProvider class 72 | * 73 | * @constructor 74 | * @public 75 | * @memberof ListComponentsProvider 76 | */ 77 | constructor() { 78 | this._onDidChangeTreeData = new EventEmitter< 79 | NodeModel | undefined | null | void 80 | >(); 81 | this.onDidChangeTreeData = this._onDidChangeTreeData.event; 82 | } 83 | 84 | // ----------------------------------------------------------------- 85 | // Methods 86 | // ----------------------------------------------------------------- 87 | 88 | // Public methods 89 | /** 90 | * Returns the tree item for the supplied element. 91 | * 92 | * @function getTreeItem 93 | * @param {NodeModel} element - The element 94 | * @public 95 | * @memberof ListComponentsProvider 96 | * @example 97 | * const treeItem = provider.getTreeItem(element); 98 | * 99 | * @returns {TreeItem | Thenable} - The tree item 100 | * 101 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 102 | */ 103 | getTreeItem(element: NodeModel): TreeItem | Thenable { 104 | return element; 105 | } 106 | 107 | /** 108 | * Returns the children for the supplied element. 109 | * 110 | * @function getChildren 111 | * @param {NodeModel} [element] - The element 112 | * @public 113 | * @memberof ListComponentsProvider 114 | * @example 115 | * const children = provider.getChildren(element); 116 | * 117 | * @returns {ProviderResult} - The children 118 | * 119 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 120 | */ 121 | getChildren(element?: NodeModel): ProviderResult { 122 | if (element) { 123 | return element.children; 124 | } 125 | 126 | return this.getListComponents(); 127 | } 128 | 129 | /** 130 | * Refreshes the tree data. 131 | * 132 | * @function refresh 133 | * @public 134 | * @memberof FeedbackProvider 135 | * @example 136 | * provider.refresh(); 137 | * 138 | * @returns {void} - No return value 139 | */ 140 | refresh(): void { 141 | this._onDidChangeTreeData.fire(); 142 | } 143 | 144 | // Private methods 145 | /** 146 | * Returns the list of files. 147 | * 148 | * @function getListComponents 149 | * @private 150 | * @memberof ListComponentsProvider 151 | * @example 152 | * const files = provider.getListComponents(); 153 | * 154 | * @returns {Promise} - The list of files 155 | */ 156 | private async getListComponents(): Promise { 157 | const files = await ListFilesController.getFiles(); 158 | 159 | if (!files) { 160 | return; 161 | } 162 | 163 | for (const file of files) { 164 | const document = await workspace.openTextDocument( 165 | file.resourceUri?.path ?? '', 166 | ); 167 | 168 | const children = Array.from( 169 | { length: document.lineCount }, 170 | (_, index) => { 171 | const line = document.lineAt(index); 172 | 173 | let node: NodeModel | undefined; 174 | 175 | if (line.text.match(/\s<[A-Z]+[a-z]+/g)) { 176 | node = new NodeModel( 177 | line.text.trim(), 178 | new ThemeIcon('symbol-method'), 179 | { 180 | command: `${EXTENSION_ID}.list.gotoLine`, 181 | title: line.text, 182 | arguments: [file.resourceUri, index], 183 | }, 184 | ); 185 | } 186 | 187 | return node; 188 | }, 189 | ); 190 | 191 | file.setChildren( 192 | children.filter((child) => child !== undefined) as NodeModel[], 193 | ); 194 | } 195 | 196 | const nodes = files.filter( 197 | (file) => file.children && file.children.length !== 0, 198 | ); 199 | 200 | return nodes.length > 0 ? nodes : undefined; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/app/providers/list-files.providers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Event, 3 | EventEmitter, 4 | ProviderResult, 5 | ThemeIcon, 6 | TreeDataProvider, 7 | TreeItem, 8 | } from 'vscode'; 9 | 10 | import { ListFilesController } from '../controllers'; 11 | import { singularize, titleize } from '../helpers'; 12 | import { NodeModel } from '../models'; 13 | 14 | /** 15 | * The ListFilesProvider class 16 | * 17 | * @class 18 | * @classdesc The class that represents the list of files provider. 19 | * @export 20 | * @public 21 | * @implements {TreeDataProvider} 22 | * @property {EventEmitter} _onDidChangeTreeData - The onDidChangeTreeData event emitter 23 | * @property {Event} onDidChangeTreeData - The onDidChangeTreeData event 24 | * @property {ListFilesController} controller - The list of files controller 25 | * @example 26 | * const provider = new ListFilesProvider(); 27 | * 28 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 29 | */ 30 | export class ListFilesProvider implements TreeDataProvider { 31 | // ----------------------------------------------------------------- 32 | // Properties 33 | // ----------------------------------------------------------------- 34 | 35 | // Private properties 36 | /** 37 | * The onDidChangeTreeData event emitter. 38 | * @type {EventEmitter} 39 | * @private 40 | * @memberof ListFilesProvider 41 | * @example 42 | * this._onDidChangeTreeData = new EventEmitter(); 43 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 44 | * 45 | * @see https://code.visualstudio.com/api/references/vscode-api#EventEmitter 46 | */ 47 | private _onDidChangeTreeData: EventEmitter< 48 | NodeModel | undefined | null | void 49 | >; 50 | 51 | // Public properties 52 | /** 53 | * The onDidChangeTreeData event. 54 | * @type {Event} 55 | * @public 56 | * @memberof ListFilesProvider 57 | * @example 58 | * readonly onDidChangeTreeData: Event; 59 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 60 | * 61 | * @see https://code.visualstudio.com/api/references/vscode-api#Event 62 | */ 63 | readonly onDidChangeTreeData: Event; 64 | 65 | // ----------------------------------------------------------------- 66 | // Constructor 67 | // ----------------------------------------------------------------- 68 | 69 | /** 70 | * Constructor for the ListFilesProvider class 71 | * 72 | * @constructor 73 | * @public 74 | * @memberof ListFilesProvider 75 | */ 76 | constructor(readonly controller: ListFilesController) { 77 | this._onDidChangeTreeData = new EventEmitter< 78 | NodeModel | undefined | null | void 79 | >(); 80 | this.onDidChangeTreeData = this._onDidChangeTreeData.event; 81 | } 82 | 83 | // ----------------------------------------------------------------- 84 | // Methods 85 | // ----------------------------------------------------------------- 86 | 87 | // Public methods 88 | /** 89 | * Returns the tree item for the supplied element. 90 | * 91 | * @function getTreeItem 92 | * @param {NodeModel} element - The element 93 | * @public 94 | * @memberof ListFilesProvider 95 | * @example 96 | * const treeItem = provider.getTreeItem(element); 97 | * 98 | * @returns {TreeItem | Thenable} - The tree item 99 | * 100 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 101 | */ 102 | getTreeItem(element: NodeModel): TreeItem | Thenable { 103 | return element; 104 | } 105 | 106 | /** 107 | * Returns the children for the supplied element. 108 | * 109 | * @function getChildren 110 | * @param {NodeModel} [element] - The element 111 | * @public 112 | * @memberof ListFilesProvider 113 | * @example 114 | * const children = provider.getChildren(element); 115 | * 116 | * @returns {ProviderResult} - The children 117 | * 118 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 119 | */ 120 | getChildren(element?: NodeModel): ProviderResult { 121 | if (element) { 122 | return element.children; 123 | } 124 | 125 | return this.getListFiles(); 126 | } 127 | 128 | /** 129 | * Refreshes the tree data. 130 | * 131 | * @function refresh 132 | * @public 133 | * @memberof FeedbackProvider 134 | * @example 135 | * provider.refresh(); 136 | * 137 | * @returns {void} - No return value 138 | */ 139 | refresh(): void { 140 | this._onDidChangeTreeData.fire(); 141 | } 142 | 143 | // Private methods 144 | /** 145 | * Gets the list of files. 146 | * 147 | * @function getListFiles 148 | * @private 149 | * @memberof ListFilesProvider 150 | * @example 151 | * const files = provider.getListFiles(); 152 | * 153 | * @returns {Promise} - The list of files 154 | */ 155 | private async getListFiles(): Promise { 156 | const files = await ListFilesController.getFiles(); 157 | 158 | if (!files) { 159 | return; 160 | } 161 | 162 | const nodes: NodeModel[] = []; 163 | 164 | const fileTypes = ListFilesController.config.watch; 165 | 166 | for (const fileType of fileTypes) { 167 | const children = files.filter((file) => 168 | file.label.toString().includes(`${singularize(fileType)}`), 169 | ); 170 | 171 | if (children.length !== 0) { 172 | const node = new NodeModel( 173 | `${fileType}: ${children.length}`, 174 | new ThemeIcon('folder-opened'), 175 | undefined, 176 | undefined, 177 | fileType, 178 | children, 179 | ); 180 | 181 | nodes.push(node); 182 | } 183 | } 184 | 185 | if (nodes.length === 0) { 186 | return; 187 | } 188 | 189 | return nodes; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/app/providers/list-hooks.providers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Event, 3 | EventEmitter, 4 | ProviderResult, 5 | ThemeIcon, 6 | TreeDataProvider, 7 | TreeItem, 8 | workspace, 9 | } from 'vscode'; 10 | 11 | import { EXTENSION_ID } from '../configs'; 12 | import { ListFilesController } from '../controllers'; 13 | import { NodeModel } from '../models'; 14 | 15 | /** 16 | * The ListHooksProvider class 17 | * 18 | * @class 19 | * @classdesc The class that represents the list of files provider. 20 | * @export 21 | * @public 22 | * @implements {TreeDataProvider} 23 | * @property {EventEmitter} _onDidChangeTreeData - The onDidChangeTreeData event emitter 24 | * @property {Event} onDidChangeTreeData - The onDidChangeTreeData event 25 | * @property {ListFilesController} controller - The list of files controller 26 | * @example 27 | * const provider = new ListHooksProvider(); 28 | * 29 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 30 | */ 31 | export class ListHooksProvider implements TreeDataProvider { 32 | // ----------------------------------------------------------------- 33 | // Properties 34 | // ----------------------------------------------------------------- 35 | 36 | // Private properties 37 | /** 38 | * The onDidChangeTreeData event emitter. 39 | * @type {EventEmitter} 40 | * @private 41 | * @memberof ListHooksProvider 42 | * @example 43 | * this._onDidChangeTreeData = new EventEmitter(); 44 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 45 | * 46 | * @see https://code.visualstudio.com/api/references/vscode-api#EventEmitter 47 | */ 48 | private _onDidChangeTreeData: EventEmitter< 49 | NodeModel | undefined | null | void 50 | >; 51 | 52 | // Public properties 53 | /** 54 | * The onDidChangeTreeData event. 55 | * @type {Event} 56 | * @public 57 | * @memberof ListHooksProvider 58 | * @example 59 | * readonly onDidChangeTreeData: Event; 60 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 61 | * 62 | * @see https://code.visualstudio.com/api/references/vscode-api#Event 63 | */ 64 | readonly onDidChangeTreeData: Event; 65 | 66 | // ----------------------------------------------------------------- 67 | // Constructor 68 | // ----------------------------------------------------------------- 69 | 70 | /** 71 | * Constructor for the ListHooksProvider class 72 | * 73 | * @constructor 74 | * @public 75 | * @memberof ListHooksProvider 76 | */ 77 | constructor() { 78 | this._onDidChangeTreeData = new EventEmitter< 79 | NodeModel | undefined | null | void 80 | >(); 81 | this.onDidChangeTreeData = this._onDidChangeTreeData.event; 82 | } 83 | 84 | // ----------------------------------------------------------------- 85 | // Methods 86 | // ----------------------------------------------------------------- 87 | 88 | // Public methods 89 | /** 90 | * Returns the tree item for the supplied element. 91 | * 92 | * @function getTreeItem 93 | * @param {NodeModel} element - The element 94 | * @public 95 | * @memberof ListHooksProvider 96 | * @example 97 | * const treeItem = provider.getTreeItem(element); 98 | * 99 | * @returns {TreeItem | Thenable} - The tree item 100 | * 101 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 102 | */ 103 | getTreeItem(element: NodeModel): TreeItem | Thenable { 104 | return element; 105 | } 106 | 107 | /** 108 | * Returns the children for the supplied element. 109 | * 110 | * @function getChildren 111 | * @param {NodeModel} [element] - The element 112 | * @public 113 | * @memberof ListHooksProvider 114 | * @example 115 | * const children = provider.getChildren(element); 116 | * 117 | * @returns {ProviderResult} - The children 118 | * 119 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 120 | */ 121 | getChildren(element?: NodeModel): ProviderResult { 122 | if (element) { 123 | return element.children; 124 | } 125 | 126 | return this.getListHooks(); 127 | } 128 | 129 | /** 130 | * Refreshes the tree data. 131 | * 132 | * @function refresh 133 | * @public 134 | * @memberof FeedbackProvider 135 | * @example 136 | * provider.refresh(); 137 | * 138 | * @returns {void} - No return value 139 | */ 140 | refresh(): void { 141 | this._onDidChangeTreeData.fire(); 142 | } 143 | 144 | // Private methods 145 | /** 146 | * Returns the list of files. 147 | * 148 | * @function getListHooks 149 | * @private 150 | * @memberof ListHooksProvider 151 | * @example 152 | * const files = provider.getListHooks(); 153 | * 154 | * @returns {Promise} - The list of files 155 | */ 156 | private async getListHooks(): Promise { 157 | const files = await ListFilesController.getFiles(); 158 | 159 | if (!files) { 160 | return; 161 | } 162 | 163 | for (const file of files) { 164 | const document = await workspace.openTextDocument( 165 | file.resourceUri?.path ?? '', 166 | ); 167 | 168 | const children = Array.from( 169 | { length: document.lineCount }, 170 | (_, index) => { 171 | const line = document.lineAt(index); 172 | 173 | let node: NodeModel | undefined; 174 | 175 | if ( 176 | line.text.match( 177 | /(useCallback|useContext|useDebugValue|useDeferredValue|useEffect|useId|useImperativeHandle|useInsertionEffect|useLayoutEffect|useMemo|useReducer|useRef|useState|useSyncExternalStore|useTransition)/gi, 178 | ) 179 | ) { 180 | node = new NodeModel( 181 | line.text.trim(), 182 | new ThemeIcon('symbol-method'), 183 | { 184 | command: `${EXTENSION_ID}.list.gotoLine`, 185 | title: line.text, 186 | arguments: [file.resourceUri, index], 187 | }, 188 | ); 189 | } 190 | 191 | return node; 192 | }, 193 | ); 194 | 195 | file.setChildren( 196 | children.filter((child) => child !== undefined) as NodeModel[], 197 | ); 198 | } 199 | 200 | const nodes = files.filter( 201 | (file) => file.children && file.children.length !== 0, 202 | ); 203 | 204 | return nodes.length > 0 ? nodes : undefined; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/app/providers/list-routes.providers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Event, 3 | EventEmitter, 4 | ProviderResult, 5 | ThemeIcon, 6 | TreeDataProvider, 7 | TreeItem, 8 | workspace, 9 | } from 'vscode'; 10 | 11 | import { EXTENSION_ID } from '../configs'; 12 | import { ListFilesController } from '../controllers'; 13 | import { NodeModel } from '../models'; 14 | 15 | /** 16 | * The ListRoutesProvider class 17 | * 18 | * @class 19 | * @classdesc The class that represents the list of files provider. 20 | * @export 21 | * @public 22 | * @implements {TreeDataProvider} 23 | * @property {EventEmitter} _onDidChangeTreeData - The onDidChangeTreeData event emitter 24 | * @property {Event} onDidChangeTreeData - The onDidChangeTreeData event 25 | * @property {ListFilesController} controller - The list of files controller 26 | * @example 27 | * const provider = new ListRoutesProvider(); 28 | * 29 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 30 | */ 31 | export class ListRoutesProvider implements TreeDataProvider { 32 | // ----------------------------------------------------------------- 33 | // Properties 34 | // ----------------------------------------------------------------- 35 | 36 | // Private properties 37 | /** 38 | * The onDidChangeTreeData event emitter. 39 | * @type {EventEmitter} 40 | * @private 41 | * @memberof ListRoutesProvider 42 | * @example 43 | * this._onDidChangeTreeData = new EventEmitter(); 44 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 45 | * 46 | * @see https://code.visualstudio.com/api/references/vscode-api#EventEmitter 47 | */ 48 | private _onDidChangeTreeData: EventEmitter< 49 | NodeModel | undefined | null | void 50 | >; 51 | 52 | // Public properties 53 | /** 54 | * The onDidChangeTreeData event. 55 | * @type {Event} 56 | * @public 57 | * @memberof ListRoutesProvider 58 | * @example 59 | * readonly onDidChangeTreeData: Event; 60 | * this.onDidChangeTreeData = this._onDidChangeTreeData.event; 61 | * 62 | * @see https://code.visualstudio.com/api/references/vscode-api#Event 63 | */ 64 | readonly onDidChangeTreeData: Event; 65 | 66 | // ----------------------------------------------------------------- 67 | // Constructor 68 | // ----------------------------------------------------------------- 69 | 70 | /** 71 | * Constructor for the ListRoutesProvider class 72 | * 73 | * @constructor 74 | * @public 75 | * @memberof ListRoutesProvider 76 | */ 77 | constructor() { 78 | this._onDidChangeTreeData = new EventEmitter< 79 | NodeModel | undefined | null | void 80 | >(); 81 | this.onDidChangeTreeData = this._onDidChangeTreeData.event; 82 | } 83 | 84 | // ----------------------------------------------------------------- 85 | // Methods 86 | // ----------------------------------------------------------------- 87 | 88 | // Public methods 89 | /** 90 | * Returns the tree item for the supplied element. 91 | * 92 | * @function getTreeItem 93 | * @param {NodeModel} element - The element 94 | * @public 95 | * @memberof ListRoutesProvider 96 | * @example 97 | * const treeItem = provider.getTreeItem(element); 98 | * 99 | * @returns {TreeItem | Thenable} - The tree item 100 | * 101 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 102 | */ 103 | getTreeItem(element: NodeModel): TreeItem | Thenable { 104 | return element; 105 | } 106 | 107 | /** 108 | * Returns the children for the supplied element. 109 | * 110 | * @function getChildren 111 | * @param {NodeModel} [element] - The element 112 | * @public 113 | * @memberof ListRoutesProvider 114 | * @example 115 | * const children = provider.getChildren(element); 116 | * 117 | * @returns {ProviderResult} - The children 118 | * 119 | * @see https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider 120 | */ 121 | getChildren(element?: NodeModel): ProviderResult { 122 | if (element) { 123 | return element.children; 124 | } 125 | 126 | return this.getListRoutes(); 127 | } 128 | 129 | /** 130 | * Refreshes the tree data. 131 | * 132 | * @function refresh 133 | * @public 134 | * @memberof FeedbackProvider 135 | * @example 136 | * provider.refresh(); 137 | * 138 | * @returns {void} - No return value 139 | */ 140 | refresh(): void { 141 | this._onDidChangeTreeData.fire(); 142 | } 143 | 144 | // Private methods 145 | /** 146 | * Returns the list of files. 147 | * 148 | * @function getListRoutes 149 | * @private 150 | * @memberof ListRoutesProvider 151 | * @example 152 | * const files = provider.getListRoutes(); 153 | * 154 | * @returns {Promise} - The list of files 155 | */ 156 | private async getListRoutes(): Promise { 157 | const files = await ListFilesController.getFiles(); 158 | 159 | if (!files) { 160 | return; 161 | } 162 | 163 | for (const file of files) { 164 | const document = await workspace.openTextDocument( 165 | file.resourceUri?.path ?? '', 166 | ); 167 | 168 | const children = Array.from( 169 | { length: document.lineCount }, 170 | (_, index) => { 171 | const line = document.lineAt(index); 172 | 173 | let node: NodeModel | undefined; 174 | 175 | if (line.text.match(/:[\w\s]+procedure/gi)) { 176 | node = new NodeModel( 177 | line.text.trim(), 178 | new ThemeIcon('symbol-method'), 179 | { 180 | command: `${EXTENSION_ID}.list.gotoLine`, 181 | title: line.text, 182 | arguments: [file.resourceUri, index], 183 | }, 184 | ); 185 | } 186 | 187 | return node; 188 | }, 189 | ); 190 | 191 | file.setChildren( 192 | children.filter((child) => child !== undefined) as NodeModel[], 193 | ); 194 | } 195 | 196 | const nodes = files.filter( 197 | (file) => file.children && file.children.length !== 0, 198 | ); 199 | 200 | return nodes.length > 0 ? nodes : undefined; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 10 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 30 | 31 | 32 | 39 | 42 | 50 | 55 | 57 | 58 | 59 | 65 | 71 | 74 | 75 | 76 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tsconfig.doc.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.ts"], 3 | "exclude": ["src/test/**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "target": "ES2021", 5 | "outDir": "out", 6 | "lib": ["ES6"], 7 | "sourceMap": true, 8 | "rootDir": "src", 9 | "strict": true, /* enable all strict type-checking options */ 10 | /* Additional Checks */ 11 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 12 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 13 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", ".vscode-test"] 17 | } 18 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | - This folder contains all of the files necessary for your extension. 6 | - `package.json` - this is the manifest file in which you declare your extension and command. 7 | - The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | - `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | - The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | - We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | - Press `F5` to open a new window with your extension loaded. 15 | - Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | - Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | - Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | - You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | - You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | ## Explore the API 25 | 26 | - You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 27 | 28 | ## Run tests 29 | 30 | - Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 31 | - Press `F5` to run the tests in a new window with your extension loaded. 32 | - See the output of the test result in the debug console. 33 | - Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 34 | - The provided test runner will only consider files matching the name pattern `**.test.ts`. 35 | - You can create folders inside the `test` folder to structure your tests any way you want. 36 | 37 | ## Go further 38 | 39 | - Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 40 | - [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 41 | - Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 42 | --------------------------------------------------------------------------------