feat: Template Forge 1.0.0

- генерация файлов и папок из шаблонов с подстановкой переменных
- каскадный поиск .templates вверх по дереву каталогов
- подсветка синтаксиса и автодополнение переменных в шаблонах
- webview и inputBox режимы ввода переменных
- локализация ru/en
- ядро генерации через @gromlab/create
- Gitea Actions CI для автопубликации
This commit is contained in:
2026-04-02 19:12:35 +03:00
parent 3da37cd591
commit cfbe03e06e
22 changed files with 1019 additions and 823 deletions

View File

@@ -0,0 +1,26 @@
name: Publish Extension
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- name: Publish to VS Code Marketplace
run: npx vsce publish -p ${{ secrets.VSCE_TOKEN }}
- name: Publish to Open VSX
run: npx ovsx publish -p ${{ secrets.OVSX_TOKEN }}

209
.github/README.md vendored
View File

@@ -1,170 +1,85 @@
[🇬🇧 English](#english) | [🇷🇺 Русский](#russian)
# Template Forge
> [!WARNING]
> **This is a mirror!** Main repository: [https://gromlab.ru/gromov/MyTemplateGenerator](https://gromlab.ru/gromov/MyTemplateGenerator)
>
> GitHub has lost its status as a reliable platform for open-source, applying political repository blocks instead of maintaining neutrality. The blocks have affected developers from Russia (Donetsk, Luhansk, Crimea, Kherson Oblast, Zaporizhzhia Oblast), Iran, Syria, Cuba, and North Korea.
>
> - ⚠️ Create pull requests and issues only in the main repository
> - 🔄 Mirror is updated automatically
> - 🚫 Changes here will be overwritten
Расширение для VS Code. Генерирует файлы и папки из ваших шаблонов с подстановкой переменных.
<div id="english">🇬🇧 English</div>
Подсветка синтаксиса и автодополнение переменных прямо в файлах шаблонов.
# MyTemplateGenerator — template and component generator for React, Vue, Next.js, Angular, and more
## Как работает
myTemplateGenerator is a powerful VSCode extension for generating templates, components, and project structures for all popular frameworks: React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact. Instantly scaffold files, folders, and boilerplate code for any modern JavaScript or TypeScript project.
1. Создайте папку `.templates` в корне проекта
2. Внутри -- каждая подпапка это шаблон
3. Используйте переменные в именах файлов и содержимом: `{{name}}` присутствует всегда, дополнительно можно использовать любое количество своих -- `{{author}}`, `{{module}}` и т.д.
4. ПКМ по папке в проводнике -- **Создать из шаблона...**
## Features
- Syntax highlighting and autocomplete for template variables in template files (`{{name}}`, `{{name.camelCase}}`, etc.)
- Generate project structure, files, and folders for popular frameworks: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
- Quickly create components: create React components, Vue components, Next.js components, Angular components, and more
- Visual configurator and localization support (Russian/English)
- Flexible settings: templates folder path, variable input mode, overwrite protection
Расширение само найдёт все переменные в шаблоне и предложит заполнить каждую.
![Logo](https://raw.githubusercontent.com/gormov1122/MyTemplateGenerator/main/src/images/1.png)
![Logo](https://raw.githubusercontent.com/gormov1122/MyTemplateGenerator/main/src/images/3.png)
## Пример
**How to use:**
1. Create a folder with templates (default: `.templates`).
2. Use variables in templates: `{{name}}`, `{{name.pascalCase}}`, etc.
3. Right-click any folder in your project → **Create from template...**
4. Select a template, fill in variables — the structure is generated automatically.
Структура шаблона:
**Example template:**
```
.templates/
component/
{{name}}/
index.tsx
{{name.camelCase}}.module.css
{{name.pascalCase}}/
{{name.pascalCase}}.tsx
types/
{{name.kebabCase}}.type.ts
styles/
{{name.kebabCase}}.module.css
```
**Available modifiers:**
Содержимое `{{name.pascalCase}}.tsx`:
| Modifier | Example (`name = myComponent`) |
|-----------------------|-------------------------------|
| `{{name}}` | myComponent |
| `{{name.pascalCase}}` | MyComponent |
| `{{name.camelCase}}` | myComponent |
| `{{name.snakeCase}}` | my_component |
| `{{name.kebabCase}}` | my-component |
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
| `{{name.upperCase}}` | Mycomponent |
| `{{name.lowerCase}}` | mycomponent |
| `{{name.upperCaseAll}}` | MYCOMPONENT |
| `{{name.lowerCaseAll}}` | mycomponent |
```tsx
import cl from 'clsx'
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
import styles from './styles/{{name.kebabCase}}.module.css'
**Supported modifiers:** pascalCase, camelCase, snakeCase, kebabCase, upperCase, lowerCase, and more.
/**
* <Назначение компонента {{name.pascalCase}} в 1 строке>.
*
* Используется для:
* - <сценарий 1>
* - <сценарий 2>
*/
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
const { children, className, ...htmlAttr } = props
**Framework compatibility:**
This extension works with **any framework** — you define your own templates for any structure you need!
| Framework | Components | Store/State | Pages/Routes | Services | Utils |
|--------------|:----------:|:-----------:|:------------:|:--------:|:-----:|
| React | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
| Next.js | ✅ | ✅ | ✅ | ✅ | ✅ |
| Nuxt | ✅ | ✅ | ✅ | ✅ | ✅ |
| Solid | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vanilla JS/TS| ✅ | ✅ | ✅ | ✅ | ✅ |
Just create a template for your favorite stack — and generate any structure you want! 🎉
**Configuration:**
All settings are managed via the standard VSCode user settings (or the visual configurator).
To open the settings menu, press <kbd>Ctrl</kbd>+<kbd>P</kbd>, type `Configure myTemplateGenerator...` and select the menu item.
You can also find all options in VSCode settings under `myTemplateGenerator`.
**Keywords:**
template, templates, template generator, component generator, scaffold, scaffolding, boilerplate, starter template, project structure, file generator, folder generator, structure generator, react component generator, vue component generator, nextjs component generator, angular component generator, svelte component generator, nuxt component generator, nestjs generator, express generator, gatsby generator, remix generator, solidjs generator, preact generator, react templates, vue templates, nextjs templates, angular templates, svelte templates, nuxt templates, nestjs templates, express templates, gatsby templates, remix templates, solidjs templates, preact templates, project templates, framework templates, file templates, folder templates, create react component, create vue component, create nextjs component, create angular component, create svelte component, create nuxt component, create nestjs module, create express route, create gatsby page, create remix route, create solidjs component, create preact component, generate files, generate folders, generate structure
<div id="russian">🇷🇺 Русский</div>
> [!WARNING]
> **Это зеркало!** Основной репозиторий: [https://gromlab.ru/gromov/MyTemplateGenerator](https://gromlab.ru/gromov/MyTemplateGenerator)
>
> GitHub утратил статус надежной площадки для open-source, применяя политические блокировки репозиториев вместо сохранения нейтралитета. Блокировки коснулись разработчиков из России (Донецк, Луганск, Крым, Херсонская обл., Запорожская обл.), Ирана, Сирии, Кубы и Северной Кореи.
>
> - ⚠️ Pull requests и issues создавайте только в основном репозитории
> - 🔄 Зеркало обновляется автоматически
> - 🚫 Изменения здесь будут перезаписаны
# MyTemplateGenerator — генерация шаблонов и компонентов для React, Vue, Next.js, Angular и других
myTemplateGenerator — это расширение для VSCode, предназначенное для генерации шаблонов, компонентов и структуры проектов для всех популярных фреймворков: React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact. Мгновенно создавайте файлы, папки и boilerplate-код для любых современных проектов на JavaScript и TypeScript.
## Возможности
- Подсветка и автокомплит переменных в шаблонных файлах (`{{name}}`, `{{name.camelCase}}` и др.)
- Генерация структуры проекта, файлов и папок для популярных фреймворков: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
- Быстрое создание компонентов: создание React компонентов, Vue компонентов, Next.js компонентов, Angular компонентов и других
- Визуальный конфигуратор и поддержка локализации (русский/английский)
- Гибкая настройка: путь к шаблонам, режим ввода переменных, запрет/разрешение перезаписи файлов
![Logo](https://raw.githubusercontent.com/gormov1122/MyTemplateGenerator/main/src/images/1.png)
![Logo](https://raw.githubusercontent.com/gormov1122/MyTemplateGenerator/main/src/images/3.png)
**Как использовать:**
1. Создайте папку с шаблонами (по умолчанию `.templates`).
2. Используйте переменные в шаблонах: `{{name}}`, `{{name.pascalCase}}` и т.д.
3. Кликните правой кнопкой по папке в проекте → **Создать из шаблона...**
4. Выберите шаблон, заполните переменные — структура будет создана автоматически.
**Пример шаблона:**
```
.templates/
component/
{{name}}/
index.tsx
{{name.camelCase}}.module.css
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
{children}
</div>
)
}
```
**Доступные модификаторы:**
При вводе `user-profile` получите:
| Модификатор | Пример (`name = myComponent`) |
|----------------------|-------------------------------|
| `{{name}}` | myComponent |
| `{{name.pascalCase}}`| MyComponent |
| `{{name.camelCase}}` | myComponent |
| `{{name.snakeCase}}` | my_component |
| `{{name.kebabCase}}` | my-component |
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
| `{{name.upperCase}}` | Mycomponent |
| `{{name.lowerCase}}` | mycomponent |
| `{{name.upperCaseAll}}` | MYCOMPONENT |
| `{{name.lowerCaseAll}}` | mycomponent |
```
UserProfile/
UserProfile.tsx
types/
user-profile.type.ts
styles/
user-profile.module.css
```
**Поддерживаемые модификаторы:** pascalCase, camelCase, snakeCase, kebabCase, upperCase, lowerCase и др.
## Модификаторы
**Совместимость с фреймворками:**
| Модификатор | `user-profile` |
|---|---|
| `{{name}}` | user-profile |
| `{{name.pascalCase}}` | UserProfile |
| `{{name.camelCase}}` | userProfile |
| `{{name.snakeCase}}` | user_profile |
| `{{name.kebabCase}}` | user-profile |
| `{{name.screamingSnakeCase}}` | USER_PROFILE |
Плагин подходит для **любых фреймворков** — вы сами задаёте шаблоны для любой структуры!
## Настройки
| Фреймворк | Компоненты | Store/State | Страницы/Роуты | Сервисы | Утилиты |
|--------------|:----------:|:-----------:|:--------------:|:-------:|:-------:|
| React | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
| Next.js | ✅ | ✅ | ✅ | ✅ | ✅ |
| Nuxt | ✅ | ✅ | ✅ | ✅ | ✅ |
| Solid | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vanilla JS/TS| ✅ | ✅ | ✅ | ✅ | ✅ |
Создайте шаблон под свой стек — и генерируйте любые структуры! 🎉
**Настройка:**
Все параметры задаются через стандартные пользовательские настройки VSCode (или визуальный конфигуратор).
Чтобы открыть меню настроек, нажмите <kbd>Ctrl</kbd>+<kbd>P</kbd>, введите `Настроить myTemplateGenerator...` (или `Configure myTemplateGenerator...` для английского интерфейса) и выберите соответствующий пункт.
Также вы можете найти все параметры в настройках VSCode по ключу `myTemplateGenerator`.
**Ключевые слова:**
создание шаблонов, генерация шаблонов, шаблоны компонентов, создание react компонентов, создание vue компонентов, создание nextjs компонентов, создание angular компонентов, создание svelte компонентов, создание nuxt компонентов, создание nestjs модулей, создание express роутов, создание gatsby страниц, создание remix роутов, создание solidjs компонентов, создание preact компонентов, генератор компонентов, генератор react компонентов, генератор vue компонентов, генератор nextjs компонентов, генератор angular компонентов, генератор svelte компонентов, генератор nuxt компонентов, генератор nestjs модулей, генератор express роутов, генератор gatsby страниц, генератор remix роутов, генератор solidjs компонентов, генератор preact компонентов, react шаблоны, vue шаблоны, nextjs шаблоны, angular шаблоны, svelte шаблоны, nuxt шаблоны, nestjs шаблоны, express шаблоны, gatsby шаблоны, remix шаблоны, solidjs шаблоны, preact шаблоны, шаблоны для проектов, шаблоны для фреймворков, структура проекта, структура папок, генерация файлов, генерация папок, файловый генератор, генератор папок, стартер шаблон
| Ключ | Описание | По умолчанию |
|---|---|---|
| `templateForge.templatesPath` | Путь к шаблонам | `.templates` |
| `templateForge.overwriteFiles` | Перезапись существующих файлов | `false` |
| `templateForge.inputMode` | Режим ввода: `webview` или `inputBox` | `webview` |
| `templateForge.language` | Язык интерфейса (`ru` / `en`) | `en` |

15
.vscode/tasks.json vendored
View File

@@ -6,7 +6,20 @@
{
"type": "npm",
"script": "watch",
"problemMatcher": "$ts-webpack-watch",
"problemMatcher": {
"owner": "webpack",
"pattern": {
"regexp": "ERROR in (.+)\\((\\d+),(\\d+)\\)",
"file": 1,
"line": 2,
"column": 3
},
"background": {
"activeOnStart": true,
"beginsPattern": "webpack .+ compilat",
"endsPattern": "webpack .+ compiled"
}
},
"isBackground": true,
"presentation": {
"reveal": "never",

View File

@@ -1,6 +1,6 @@
# Change Log
All notable changes to the "mytemplategenerator" extension will be documented in this file.
All notable changes to the "templateforge" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

192
README.md
View File

@@ -1,151 +1,85 @@
[🇬🇧 English](#english) | [🇷🇺 Русский](#russian)
# Template Forge
<div id="english">🇬🇧 English</div>
Расширение для VS Code. Генерирует файлы и папки из ваших шаблонов с подстановкой переменных.
# MyTemplateGenerator — template and component generator for React, Vue, Next.js, Angular, and more
Подсветка синтаксиса и автодополнение переменных прямо в файлах шаблонов.
myTemplateGenerator is a powerful VSCode extension for generating templates, components, and project structures for all popular frameworks: React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact. Instantly scaffold files, folders, and boilerplate code for any modern JavaScript or TypeScript project.
## Как работает
## Features
- Syntax highlighting and autocomplete for template variables in template files (`{{name}}`, `{{name.camelCase}}`, etc.)
- Generate project structure, files, and folders for popular frameworks: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
- Quickly create components: create React components, Vue components, Next.js components, Angular components, and more
- Visual configurator and localization support (Russian/English)
- Flexible settings: templates folder path, variable input mode, overwrite protection
1. Создайте папку `.templates` в корне проекта
2. Внутри -- каждая подпапка это шаблон
3. Используйте переменные в именах файлов и содержимом: `{{name}}` присутствует всегда, дополнительно можно использовать любое количество своих -- `{{author}}`, `{{module}}` и т.д.
4. ПКМ по папке в проводнике -- **Создать из шаблона...**
![Logo](https://raw.githubusercontent.com/gormov1122/MyTemplateGenerator/main/src/images/1.png)
![Logo](https://raw.githubusercontent.com/gormov1122/MyTemplateGenerator/main/src/images/3.png)
Расширение само найдёт все переменные в шаблоне и предложит заполнить каждую.
**How to use:**
1. Create a folder with templates (default: `.templates`).
2. Use variables in templates: `{{name}}`, `{{name.pascalCase}}`, etc.
3. Right-click any folder in your project → **Create from template...**
4. Select a template, fill in variables — the structure is generated automatically.
## Пример
Структура шаблона:
**Example template:**
```
.templates/
component/
{{name}}/
index.tsx
{{name.camelCase}}.module.css
{{name.pascalCase}}/
{{name.pascalCase}}.tsx
types/
{{name.kebabCase}}.type.ts
styles/
{{name.kebabCase}}.module.css
```
**Available modifiers:**
Содержимое `{{name.pascalCase}}.tsx`:
| Modifier | Example (`name = myComponent`) |
|-----------------------|-------------------------------|
| `{{name}}` | myComponent |
| `{{name.pascalCase}}` | MyComponent |
| `{{name.camelCase}}` | myComponent |
| `{{name.snakeCase}}` | my_component |
| `{{name.kebabCase}}` | my-component |
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
| `{{name.upperCase}}` | Mycomponent |
| `{{name.lowerCase}}` | mycomponent |
| `{{name.upperCaseAll}}` | MYCOMPONENT |
| `{{name.lowerCaseAll}}` | mycomponent |
```tsx
import cl from 'clsx'
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
import styles from './styles/{{name.kebabCase}}.module.css'
**Supported modifiers:** pascalCase, camelCase, snakeCase, kebabCase, upperCase, lowerCase, and more.
/**
* <Назначение компонента {{name.pascalCase}} в 1 строке>.
*
* Используется для:
* - <сценарий 1>
* - <сценарий 2>
*/
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
const { children, className, ...htmlAttr } = props
**Framework compatibility:**
This extension works with **any framework** — you define your own templates for any structure you need!
| Framework | Components | Store/State | Pages/Routes | Services | Utils |
|--------------|:----------:|:-----------:|:------------:|:--------:|:-----:|
| React | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
| Next.js | ✅ | ✅ | ✅ | ✅ | ✅ |
| Nuxt | ✅ | ✅ | ✅ | ✅ | ✅ |
| Solid | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vanilla JS/TS| ✅ | ✅ | ✅ | ✅ | ✅ |
Just create a template for your favorite stack — and generate any structure you want! 🎉
**Configuration:**
All settings are managed via the standard VSCode user settings (or the visual configurator).
To open the settings menu, press <kbd>Ctrl</kbd>+<kbd>P</kbd>, type `Configure myTemplateGenerator...` and select the menu item.
You can also find all options in VSCode settings under `myTemplateGenerator`.
**Keywords / Ключевые слова:**
template, templates, template generator, component generator, scaffold, scaffolding, boilerplate, starter template, project structure, file generator, folder generator, structure generator, react component generator, vue component generator, nextjs component generator, angular component generator, svelte component generator, nuxt component generator, nestjs generator, express generator, gatsby generator, remix generator, solidjs generator, preact generator, react templates, vue templates, nextjs templates, angular templates, svelte templates, nuxt templates, nestjs templates, express templates, gatsby templates, remix templates, solidjs templates, preact templates, project templates, framework templates, file templates, folder templates, create react component, create vue component, create nextjs component, create angular component, create svelte component, create nuxt component, create nestjs module, create express route, create gatsby page, create remix route, create solidjs component, create preact component, generate files, generate folders, generate structure
<div id="russian">🇷🇺 Русский</div>
# MyTemplateGenerator — генерация шаблонов и компонентов для React, Vue, Next.js, Angular и других
myTemplateGenerator — это расширение для VSCode, предназначенное для генерации шаблонов, компонентов и структуры проектов для всех популярных фреймворков: React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact. Мгновенно создавайте файлы, папки и boilerplate-код для любых современных проектов на JavaScript и TypeScript.
## Возможности
- Подсветка и автокомплит переменных в шаблонных файлах (`{{name}}`, `{{name.camelCase}}` и др.)
- Генерация структуры проекта, файлов и папок для популярных фреймворков: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
- Быстрое создание компонентов: создание React компонентов, Vue компонентов, Next.js компонентов, Angular компонентов и других
- Визуальный конфигуратор и поддержка локализации (русский/английский)
- Гибкая настройка: путь к шаблонам, режим ввода переменных, запрет/разрешение перезаписи файлов
![Logo](https://raw.githubusercontent.com/gormov1122/MyTemplateGenerator/main/src/images/1.png)
![Logo](https://raw.githubusercontent.com/gormov1122/MyTemplateGenerator/main/src/images/3.png)
**Как использовать:**
1. Создайте папку с шаблонами (по умолчанию `.templates`).
2. Используйте переменные в шаблонах: `{{name}}`, `{{name.pascalCase}}` и т.д.
3. Кликните правой кнопкой по папке в проекте → **Создать из шаблона...**
4. Выберите шаблон, заполните переменные — структура будет создана автоматически.
**Пример шаблона:**
```
.templates/
component/
{{name}}/
index.tsx
{{name.camelCase}}.module.css
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
{children}
</div>
)
}
```
**Доступные модификаторы:**
При вводе `user-profile` получите:
| Модификатор | Пример (`name = myComponent`) |
|----------------------|-------------------------------|
| `{{name}}` | myComponent |
| `{{name.pascalCase}}`| MyComponent |
| `{{name.camelCase}}` | myComponent |
| `{{name.snakeCase}}` | my_component |
| `{{name.kebabCase}}` | my-component |
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
| `{{name.upperCase}}` | Mycomponent |
| `{{name.lowerCase}}` | mycomponent |
| `{{name.upperCaseAll}}` | MYCOMPONENT |
| `{{name.lowerCaseAll}}` | mycomponent |
```
UserProfile/
UserProfile.tsx
types/
user-profile.type.ts
styles/
user-profile.module.css
```
**Поддерживаемые модификаторы:** pascalCase, camelCase, snakeCase, kebabCase, upperCase, lowerCase и др.
## Модификаторы
**Совместимость с фреймворками:**
| Модификатор | `user-profile` |
|---|---|
| `{{name}}` | user-profile |
| `{{name.pascalCase}}` | UserProfile |
| `{{name.camelCase}}` | userProfile |
| `{{name.snakeCase}}` | user_profile |
| `{{name.kebabCase}}` | user-profile |
| `{{name.screamingSnakeCase}}` | USER_PROFILE |
Плагин подходит для **любых фреймворков** — вы сами задаёте шаблоны для любой структуры!
## Настройки
| Фреймворк | Компоненты | Store/State | Страницы/Роуты | Сервисы | Утилиты |
|--------------|:----------:|:-----------:|:--------------:|:-------:|:-------:|
| React | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
| Next.js | ✅ | ✅ | ✅ | ✅ | ✅ |
| Nuxt | ✅ | ✅ | ✅ | ✅ | ✅ |
| Solid | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vanilla JS/TS| ✅ | ✅ | ✅ | ✅ | ✅ |
Создайте шаблон под свой стек — и генерируйте любые структуры! 🎉
**Настройка:**
Все параметры задаются через стандартные пользовательские настройки VSCode (или визуальный конфигуратор).
Чтобы открыть меню настроек, нажмите <kbd>Ctrl</kbd>+<kbd>P</kbd>, введите `Настроить myTemplateGenerator...` (или `Configure myTemplateGenerator...` для английского интерфейса) и выберите соответствующий пункт.
Также вы можете найти все параметры в настройках VSCode по ключу `myTemplateGenerator`.
**Ключевые слова:**
создание шаблонов, генерация шаблонов, шаблоны компонентов, создание react компонентов, создание vue компонентов, создание nextjs компонентов, создание angular компонентов, создание svelte компонентов, создание nuxt компонентов, создание nestjs модулей, создание express роутов, создание gatsby страниц, создание remix роутов, создание solidjs компонентов, создание preact компонентов, генератор компонентов, генератор react компонентов, генератор vue компонентов, генератор nextjs компонентов, генератор angular компонентов, генератор svelte компонентов, генератор nuxt компонентов, генератор nestjs модулей, генератор express роутов, генератор gatsby страниц, генератор remix роутов, генератор solidjs компонентов, генератор preact компонентов, react шаблоны, vue шаблоны, nextjs шаблоны, angular шаблоны, svelte шаблоны, nuxt шаблоны, nestjs шаблоны, express шаблоны, gatsby шаблоны, remix шаблоны, solidjs шаблоны, preact шаблоны, шаблоны для проектов, шаблоны для фреймворков, структура проекта, структура папок, генерация файлов, генерация папок, файловый генератор, генератор папок, стартер шаблон
| Ключ | Описание | По умолчанию |
|---|---|---|
| `templateForge.templatesPath` | Путь к шаблонам | `.templates` |
| `templateForge.overwriteFiles` | Перезапись существующих файлов | `false` |
| `templateForge.inputMode` | Режим ввода: `webview` или `inputBox` | `webview` |
| `templateForge.language` | Язык интерфейса (`ru` / `en`) | `en` |

722
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
{
"name": "mytemplategenerator",
"displayName": "Template & Component Generator: React, Vue, Next.js, Angular",
"description": "Powerful VSCode extension for generating templates, components, and project structures for React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact, and more. Instantly scaffold files, folders, and boilerplate code with variable substitution.",
"version": "0.0.11",
"publisher": "MyTemplateGenerator",
"name": "templateforge",
"displayName": "Template Forge",
"description": "Generate files and folders from templates with variable substitution and case modifiers. Syntax highlighting and autocomplete for template variables.",
"version": "1.0.0",
"publisher": "TemplateForge",
"author": "Sergey Gromov",
"scripts": {
"build-app": "npm run build && npm run package-vsce",
@@ -19,193 +19,91 @@
},
"categories": [
"Snippets",
"Programming Languages",
"Other"
],
"keywords": [
"template",
"templates",
"template generator",
"component generator",
"scaffold",
"scaffolding",
"generator",
"boilerplate",
"starter template",
"project structure",
"component",
"file generator",
"folder generator",
"structure generator",
"react component generator",
"vue component generator",
"nextjs component generator",
"angular component generator",
"svelte component generator",
"nuxt component generator",
"nestjs generator",
"express generator",
"gatsby generator",
"remix generator",
"solidjs generator",
"preact generator",
"react templates",
"vue templates",
"nextjs templates",
"angular templates",
"svelte templates",
"nuxt templates",
"nestjs templates",
"express templates",
"gatsby templates",
"remix templates",
"solidjs templates",
"preact templates",
"project templates",
"framework templates",
"file templates",
"folder templates",
"create react component",
"create vue component",
"create nextjs component",
"create angular component",
"create svelte component",
"create nuxt component",
"create nestjs module",
"create express route",
"create gatsby page",
"create remix route",
"create solidjs component",
"create preact component",
"generate files",
"generate folders",
"generate structure",
"создание шаблонов",
"генерация шаблонов",
"шаблоны компонентов",
"создание react компонентов",
"создание vue компонентов",
"создание nextjs компонентов",
"создание angular компонентов",
"создание svelte компонентов",
"создание nuxt компонентов",
"создание nestjs модулей",
"создание express роутов",
"создание gatsby страниц",
"создание remix роутов",
"создание solidjs компонентов",
"создание preact компонентов",
"генератор компонентов",
"генератор react компонентов",
"генератор vue компонентов",
"генератор nextjs компонентов",
"генератор angular компонентов",
"генератор svelte компонентов",
"генератор nuxt компонентов",
"генератор nestjs модулей",
"генератор express роутов",
"генератор gatsby страниц",
"генератор remix роутов",
"генератор solidjs компонентов",
"генератор preact компонентов",
"react шаблоны",
"vue шаблоны",
"nextjs шаблоны",
"angular шаблоны",
"svelte шаблоны",
"nuxt шаблоны",
"nestjs шаблоны",
"express шаблоны",
"gatsby шаблоны",
"remix шаблоны",
"solidjs шаблоны",
"preact шаблоны",
"шаблоны для проектов",
"шаблоны для фреймворков",
"структура проекта",
"структура папок",
"генерация файлов",
"генерация папок",
"файловый генератор",
"генератор папок",
"стартер шаблон"
"project structure",
"code generation",
"snippets"
],
"icon": "logo.png",
"url": "https://github.com/gromov-io/MyTemplateGenerator",
"homepage": "https://github.com/gromov-io/MyTemplateGenerator",
"homepage": "https://gromlab.ru/gromov/templateforge",
"repository": {
"type": "git",
"url": "https://github.com/gromov-io/MyTemplateGenerator"
"url": "git@gromlab.ru:gromov/templateforge.git"
},
"bugs": {
"url": "https://github.com/gromov-io/MyTemplateGenerator/issues"
"url": "https://gromlab.ru/gromov/templateforge/issues"
},
"engines": {
"vscode": "^1.60.0"
},
"activationEvents": [
"onStartupFinished",
"onLanguage:javascript",
"onLanguage:typescript",
"onLanguage:css",
"onLanguage:plaintext",
"onFileSystem: file"
"onStartupFinished"
],
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "mytemplategenerator.createFromTemplate",
"title": "%mytemplategenerator.createFromTemplate.title%"
"command": "templateforge.createFromTemplate",
"title": "%templateforge.createFromTemplate.title%"
},
{
"command": "mytemplategenerator.configure",
"title": "%mytemplategenerator.configure.title%"
"command": "templateforge.configure",
"title": "%templateforge.configure.title%"
}
],
"menus": {
"explorer/context": [
{
"command": "mytemplategenerator.createFromTemplate",
"command": "templateforge.createFromTemplate",
"group": "navigation@10"
}
]
},
"configuration": {
"title": "myTemplateGenerator",
"title": "Template Forge",
"properties": {
"myTemplateGenerator.templatesPath": {
"templateForge.templatesPath": {
"type": "string",
"default": ".templates",
"description": "%mytemplategenerator.config.templatesPath.description%",
"markdownDescription": "%mytemplategenerator.config.templatesPath.description%",
"description": "%templateforge.config.templatesPath.description%",
"markdownDescription": "%templateforge.config.templatesPath.description%",
"scope": "application"
},
"myTemplateGenerator.overwriteFiles": {
"templateForge.overwriteFiles": {
"type": "boolean",
"default": false,
"description": "%mytemplategenerator.config.overwriteFiles.description%",
"markdownDescription": "%mytemplategenerator.config.overwriteFiles.description%",
"description": "%templateforge.config.overwriteFiles.description%",
"markdownDescription": "%templateforge.config.overwriteFiles.description%",
"scope": "application"
},
"myTemplateGenerator.inputMode": {
"templateForge.inputMode": {
"type": "string",
"enum": [
"webview",
"inputBox"
],
"default": "webview",
"description": "%mytemplategenerator.config.inputMode.description%",
"markdownDescription": "%mytemplategenerator.config.inputMode.description%",
"description": "%templateforge.config.inputMode.description%",
"markdownDescription": "%templateforge.config.inputMode.description%",
"scope": "application"
},
"myTemplateGenerator.language": {
"templateForge.language": {
"type": "string",
"enum": [
"ru",
"en"
],
"default": "en",
"description": "%mytemplategenerator.config.language.description%",
"markdownDescription": "%mytemplategenerator.config.language.description%",
"description": "%templateforge.config.language.description%",
"markdownDescription": "%templateforge.config.language.description%",
"scope": "application"
}
}
@@ -241,8 +139,6 @@
"webpack-cli": "^6.0.1"
},
"dependencies": {
"change-case": "^5.4.4",
"change-case-all": "^2.1.0",
"handlebars": "^4.7.8"
"@gromlab/create": "^0.2.0"
}
}

View File

@@ -1,8 +1,8 @@
{
"mytemplategenerator.createFromTemplate.title": "Create from template...",
"mytemplategenerator.configure.title": "Configure myTemplateGenerator...",
"mytemplategenerator.config.templatesPath.description": "Path to the templates folder (relative to the project root)",
"mytemplategenerator.config.overwriteFiles.description": "Overwrite existing files when generating from template",
"mytemplategenerator.config.inputMode.description": "Variable input mode: webview or inputBox",
"mytemplategenerator.config.language.description": "Extension interface language"
}
"templateforge.createFromTemplate.title": "Create from template...",
"templateforge.configure.title": "Configure Template Forge...",
"templateforge.config.templatesPath.description": "Path to the templates folder (relative to the project root)",
"templateforge.config.overwriteFiles.description": "Overwrite existing files when generating from template",
"templateforge.config.inputMode.description": "Variable input mode: webview or inputBox",
"templateforge.config.language.description": "Extension interface language"
}

View File

@@ -1,8 +1,8 @@
{
"mytemplategenerator.createFromTemplate.title": "Создать из шаблона...",
"mytemplategenerator.configure.title": "Настроить myTemplateGenerator...",
"mytemplategenerator.config.templatesPath.description": "Путь к папке с шаблонами (относительно корня проекта)",
"mytemplategenerator.config.overwriteFiles.description": "Перезаписывать ли существующие файлы при генерации",
"mytemplategenerator.config.inputMode.description": "Режим ввода переменных: webview или inputBox",
"mytemplategenerator.config.language.description": "Язык интерфейса расширения"
}
"templateforge.createFromTemplate.title": "Создать из шаблона...",
"templateforge.configure.title": "Настроить Template Forge...",
"templateforge.config.templatesPath.description": "Путь к папке с шаблонами (относительно корня проекта)",
"templateforge.config.overwriteFiles.description": "Перезаписывать ли существующие файлы при генерации",
"templateforge.config.inputMode.description": "Режим ввода переменных: webview или inputBox",
"templateforge.config.language.description": "Язык интерфейса расширения"
}

View File

@@ -3,15 +3,15 @@ import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
export interface MyTemplateGeneratorConfig {
export interface TemplateForgeConfig {
templatesPath: string;
overwriteFiles: boolean;
inputMode: 'webview' | 'inputBox';
language?: string;
}
export function readConfig(): MyTemplateGeneratorConfig {
const config = vscode.workspace.getConfiguration('myTemplateGenerator');
export function readConfig(): TemplateForgeConfig {
const config = vscode.workspace.getConfiguration('templateForge');
return {
templatesPath: config.get<string>('templatesPath', '.templates'),
overwriteFiles: config.get<boolean>('overwriteFiles', false),
@@ -20,8 +20,8 @@ export function readConfig(): MyTemplateGeneratorConfig {
};
}
export async function writeConfig(newConfig: Partial<MyTemplateGeneratorConfig>) {
const config = vscode.workspace.getConfiguration('myTemplateGenerator');
export async function writeConfig(newConfig: Partial<TemplateForgeConfig>) {
const config = vscode.workspace.getConfiguration('templateForge');
if (newConfig.templatesPath !== undefined) {
await config.update('templatesPath', newConfig.templatesPath, vscode.ConfigurationTarget.Global);
}
@@ -34,4 +34,4 @@ export async function writeConfig(newConfig: Partial<MyTemplateGeneratorConfig>)
if (newConfig.language !== undefined) {
await config.update('language', newConfig.language, vscode.ConfigurationTarget.Global);
}
}
}

View File

@@ -1,7 +1,4 @@
// Словари локализации и утилиты для i18n
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
// Словари локализации
export const I18N_DICTIONARIES: Record<string, Record<string, string>> = {
ru: {
@@ -40,7 +37,7 @@ export const I18N_DICTIONARIES: Record<string, Record<string, string>> = {
export const SETTINGS_I18N: Record<string, Record<string, string>> = {
ru: {
title: 'Настройки myTemplateGenerator',
title: 'Настройки Template Forge',
templatesPath: 'Путь к шаблонам:',
overwriteFiles: 'Перезаписывать существующие файлы',
inputMode: 'Способ ввода переменных:',
@@ -50,7 +47,7 @@ export const SETTINGS_I18N: Record<string, Record<string, string>> = {
save: 'Сохранить'
},
en: {
title: 'myTemplateGenerator Settings',
title: 'Template Forge Settings',
templatesPath: 'Templates path:',
overwriteFiles: 'Overwrite existing files',
inputMode: 'Variable input method:',
@@ -61,11 +58,4 @@ export const SETTINGS_I18N: Record<string, Record<string, string>> = {
}
};
export async function pickTemplate(templatesDir: string): Promise<string | undefined> {
const templates = fs.readdirSync(templatesDir).filter(f => fs.statSync(path.join(templatesDir, f)).isDirectory());
if (templates.length === 0) {
vscode.window.showWarningMessage('В папке templates нет шаблонов.');
return undefined;
}
return vscode.window.showQuickPick(templates, { placeHolder: 'Выберите шаблон' });
}

View File

@@ -0,0 +1,66 @@
// Каскадный поиск .templates по дереву каталогов
import * as fs from 'fs';
import * as path from 'path';
import { listTemplateNames } from '@gromlab/create';
/**
* Собирает все доступные шаблоны по каскаду вверх от startDir до rootDir.
* Ближайшие шаблоны имеют приоритет (shadowing).
* Возвращает Map: имя шаблона → абсолютный путь к его директории.
*/
export function discoverTemplates(
startDir: string,
rootDir: string,
templatesFolder: string = '.templates'
): Map<string, string> {
const result = new Map<string, string>();
const resolvedRoot = path.resolve(rootDir);
let current = path.resolve(startDir);
while (true) {
const templatesDir = path.join(current, templatesFolder);
if (fs.existsSync(templatesDir) && fs.statSync(templatesDir).isDirectory()) {
const names = listTemplateNames(templatesDir);
for (const name of names) {
if (!result.has(name)) {
result.set(name, path.join(templatesDir, name));
}
}
}
if (current === resolvedRoot) break;
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
return result;
}
/**
* Ищет конкретный шаблон по каскаду вверх от startDir до rootDir.
* Возвращает абсолютный путь к первому .templates, содержащему шаблон, или undefined.
*/
export function resolveTemplate(
templateName: string,
startDir: string,
rootDir: string,
templatesFolder: string = '.templates'
): string | undefined {
const resolvedRoot = path.resolve(rootDir);
let current = path.resolve(startDir);
while (true) {
const candidate = path.join(current, templatesFolder, templateName);
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
return candidate;
}
if (current === resolvedRoot) break;
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
return undefined;
}

View File

@@ -1,118 +0,0 @@
// Работа с шаблонами и преобразование кейсов
import * as fs from 'fs';
import * as path from 'path';
import * as Handlebars from 'handlebars';
// @ts-expect-error: Нет типов для change-case-all, но пакет работает корректно
import { camelCase, pascalCase, snakeCase, kebabCase, constantCase, upperCase, lowerCase } from 'change-case-all';
export const CASE_MODIFIERS: Record<string, (str: string) => string> = {
pascalCase,
camelCase,
snakeCase,
kebabCase,
screamingSnakeCase: constantCase,
upperCase,
lowerCase,
upperCaseAll: (s: string) => s.replace(/[-_\s]+/g, '').toUpperCase(),
lowerCaseAll: (s: string) => s.replace(/[-_\s]+/g, '').toLowerCase(),
};
export function readDirRecursive(src: string): string[] {
let results: string[] = [];
const list = fs.readdirSync(src);
list.forEach(function(file) {
const filePath = path.join(src, file);
const stat = fs.statSync(filePath);
if (stat && stat.isDirectory()) {
results = results.concat(readDirRecursive(filePath));
} else {
results.push(filePath);
}
});
return results;
}
export function copyTemplate(templateDir: string, targetDir: string, name: string) {
const vars = {
name,
nameUpperCase: CASE_MODIFIERS.upperCase(name),
nameLowerCase: CASE_MODIFIERS.lowerCase(name),
namePascalCase: CASE_MODIFIERS.pascalCase(name),
nameCamelCase: CASE_MODIFIERS.camelCase(name),
nameSnakeCase: CASE_MODIFIERS.snakeCase(name),
nameKebabCase: CASE_MODIFIERS.kebabCase(name),
nameScreamingSnakeCase: CASE_MODIFIERS.screamingSnakeCase(name),
nameUpperCaseAll: CASE_MODIFIERS.upperCaseAll(name),
nameLowerCaseAll: CASE_MODIFIERS.lowerCaseAll(name)
};
const files = readDirRecursive(templateDir);
for (const file of files) {
const relPath = path.relative(templateDir, file);
const relPathTmpl = Handlebars.compile(relPath);
const targetRelPath = relPathTmpl(vars);
const targetPath = path.join(targetDir, targetRelPath);
const content = fs.readFileSync(file, 'utf8');
const contentTmpl = Handlebars.compile(content);
const rendered = contentTmpl(vars);
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
fs.writeFileSync(targetPath, rendered, { flag: 'wx' });
}
}
export function getAllTemplateVariables(templateDir: string): Set<string> {
const files = readDirRecursive(templateDir);
const varRegex = /{{\s*([\w]+)(?:\.[\w]+)?\s*}}/g;
const vars = new Set<string>();
for (const file of files) {
let relPath = path.relative(templateDir, file);
let match;
while ((match = varRegex.exec(relPath)) !== null) {
vars.add(match[1]);
}
const content = fs.readFileSync(file, 'utf8');
while ((match = varRegex.exec(content)) !== null) {
vars.add(match[1]);
}
}
return vars;
}
export function applyTemplate(str: string, vars: Record<string, string>, modifiers: Record<string, (s: string) => string>): string {
return str.replace(/{{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\s*}}/g, (_, varName, mod) => {
let value = vars[varName];
if (value === undefined) return '';
if (mod && modifiers[mod]) {
return modifiers[mod](value);
}
return value;
});
}
export function copyTemplateWithVars(templateDir: string, targetDir: string, vars: Record<string, string>, overwriteFiles: boolean = false, dict?: Record<string, string>, templateName?: string): boolean {
const files = readDirRecursive(templateDir);
const firstLevelDirs = new Set<string>();
for (const file of files) {
const relPath = path.relative(templateDir, file);
const targetRelPath = applyTemplate(relPath, vars, CASE_MODIFIERS);
const firstLevel = targetRelPath.split(path.sep)[0];
firstLevelDirs.add(firstLevel);
}
if (!overwriteFiles && dict) {
for (const dir of firstLevelDirs) {
const checkPath = path.join(targetDir, dir);
if (fs.existsSync(checkPath)) {
return false;
}
}
}
for (const file of files) {
const relPath = path.relative(templateDir, file);
const targetRelPath = applyTemplate(relPath, vars, CASE_MODIFIERS);
const targetPath = path.join(targetDir, targetRelPath);
const content = fs.readFileSync(file, 'utf8');
const rendered = applyTemplate(content, vars, CASE_MODIFIERS);
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
fs.writeFileSync(targetPath, rendered, { flag: overwriteFiles ? 'w' : 'wx' });
}
return true;
}

View File

@@ -1,17 +1,4 @@
// Работа с переменными шаблонов
import { CASE_MODIFIERS } from './templateUtils';
export function buildVarsObject(userVars: Record<string, string>): Record<string, string> {
const result: Record<string, string> = {};
for (const [base, value] of Object.entries(userVars)) {
result[base] = value;
for (const [mod, fn] of Object.entries(CASE_MODIFIERS)) {
result[`${base}.${mod}`] = fn(value);
}
}
return result;
}
// Сбор переменных от пользователя через InputBox
import * as vscode from 'vscode';
export async function collectUserVars(baseVars: Set<string>): Promise<Record<string, string>> {
@@ -25,4 +12,4 @@ export async function collectUserVars(baseVars: Set<string>): Promise<Record<str
result[v] = input;
}
return result;
}
}

View File

@@ -1,57 +1,18 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import * as Handlebars from 'handlebars';
import { getAllTemplateVariables, copyTemplateWithVars } from './core/templateUtils';
import { buildVarsObject, collectUserVars } from './core/vars';
import { readConfig, writeConfig } from './core/config';
import { collectTemplateVariables, readDirRecursive, buildPlan, writePlan, getCollisions } from '@gromlab/create';
import { collectUserVars } from './core/vars';
import { readConfig } from './core/config';
import { showConfigWebview } from './webview/configWebview';
import { showTemplateAndVarsWebview } from './webview/templateVarsWebview';
import { registerTemplateCompletionAndHighlight } from './vscode/completion';
import { registerTemplateSemanticHighlight } from './vscode/semanticHighlight';
import { registerTemplateDecorations, clearDiagnosticsForTemplates } from './vscode/decorations';
import { I18N_DICTIONARIES, pickTemplate } from './core/i18n';
import { I18N_DICTIONARIES } from './core/i18n';
import { discoverTemplates, resolveTemplate } from './core/templateResolver';
// Регистрируем кастомный helper для Handlebars
Handlebars.registerHelper('getVar', function(this: Record<string, any>, varName: string, modifier?: string, options?: any) {
if (!varName) return '';
const vars = this;
if (modifier && typeof modifier === 'string') {
if (vars[`${varName}.${modifier}`]) return vars[`${varName}.${modifier}`];
if (vars[varName] && typeof vars[varName] === 'object' && vars[varName][modifier]) {
return vars[varName][modifier];
}
return '';
}
if (vars[varName]) {
if (typeof vars[varName] === 'object' && vars[varName].value) return vars[varName].value;
return vars[varName];
}
return '';
});
// === Декораторы для шаблонных переменных ===
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
const disposable = vscode.commands.registerCommand('mytemplategenerator.helloWorld', () => {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from myTemplateGenerator!');
});
const createFromTemplate = vscode.commands.registerCommand('mytemplategenerator.createFromTemplate', async (uri: vscode.Uri) => {
const createFromTemplate = vscode.commands.registerCommand('templateforge.createFromTemplate', async (uri: vscode.Uri) => {
const config = readConfig();
const dict = I18N_DICTIONARIES[config.language || 'ru'] || I18N_DICTIONARIES['ru'];
const workspaceFolders = vscode.workspace.workspaceFolders;
@@ -59,63 +20,76 @@ export function activate(context: vscode.ExtensionContext) {
vscode.window.showErrorMessage(dict.noFolders);
return;
}
const templatesDir = path.join(workspaceFolders[0].uri.fsPath, config.templatesPath);
if (!fs.existsSync(templatesDir) || !fs.statSync(templatesDir).isDirectory()) {
vscode.window.showErrorMessage(`${dict.templatesNotFound} ${templatesDir}`);
const workspaceRoot = workspaceFolders[0].uri.fsPath;
const targetDir = uri.fsPath;
// Каскадный поиск шаблонов вверх от точки вызова до корня workspace
const templates = discoverTemplates(targetDir, workspaceRoot, config.templatesPath);
if (templates.size === 0) {
vscode.window.showErrorMessage(dict.templatesNotFound);
return;
}
let template: string | undefined;
let templateName: string | undefined;
let userVars: Record<string, string> | undefined;
if (config.inputMode === 'webview') {
vscode.window.showInformationMessage('[DEBUG] Вызов webview создания шаблона...');
const result: { template: string, vars: Record<string, string> } | undefined = await showTemplateAndVarsWebview(context, templatesDir, uri.fsPath, config.language || 'ru');
if (!result) {
vscode.window.showInformationMessage('[DEBUG] Webview был закрыт или не вернул результат');
return;
}
template = result.template;
const result = await showTemplateAndVarsWebview(context, templates, targetDir, config.language || 'ru');
if (!result) return;
templateName = result.template;
userVars = result.vars;
} else {
vscode.window.showInformationMessage('[DEBUG] Вызов выбора шаблона через quickPick...');
template = await pickTemplate(templatesDir);
if (!template) {
vscode.window.showInformationMessage('[DEBUG] Шаблон не выбран');
return;
}
const templateDir = path.join(templatesDir, template);
const allVars = getAllTemplateVariables(templateDir);
const baseVars = Array.from(allVars);
userVars = await collectUserVars(new Set(baseVars));
const templateNames = Array.from(templates.keys());
templateName = await vscode.window.showQuickPick(templateNames, { placeHolder: dict.chooseTemplate });
if (!templateName) return;
const templateDir = templates.get(templateName)!;
const allVars = collectTemplateVariables(templateDir);
userVars = await collectUserVars(allVars);
}
if (!template || !userVars) {
vscode.window.showInformationMessage('[DEBUG] Не выбраны шаблон или переменные');
if (!templateName || !userVars) return;
// Резолвим шаблон по каскаду (первый .templates содержащий его)
const templateDir = resolveTemplate(templateName, targetDir, workspaceRoot, config.templatesPath);
if (!templateDir) {
vscode.window.showErrorMessage(`${dict.createError}: "${templateName}"`);
return;
}
const templateDir = path.join(templatesDir, template);
try {
const vars = buildVarsObject(userVars);
vscode.window.showInformationMessage('[DEBUG] Копирование шаблона...');
copyTemplateWithVars(templateDir, uri.fsPath, vars, config.overwriteFiles, dict, template);
const files = readDirRecursive(templateDir);
const plan = buildPlan(templateDir, targetDir, userVars, files);
if (!config.overwriteFiles) {
const collisions = getCollisions(plan);
if (collisions.length > 0) {
vscode.window.showWarningMessage(dict.fileExistsNoOverwrite);
return;
}
}
writePlan(plan, userVars, config.overwriteFiles);
vscode.window.setStatusBarMessage(
dict.createSuccess.replace('{{template}}', templateName), 5000
);
} catch (e: any) {
vscode.window.showErrorMessage(`${dict.createError}: ${e.message}`);
}
});
context.subscriptions.push(
disposable,
createFromTemplate,
vscode.commands.registerCommand('mytemplategenerator.configure', async () => {
vscode.commands.registerCommand('templateforge.configure', async () => {
await showConfigWebview(context);
})
);
registerTemplateCompletionAndHighlight(context);
let semanticHighlightDisposable: vscode.Disposable | undefined = registerTemplateSemanticHighlight(context);
registerTemplateDecorations(context); // <--- Добавить регистрацию декораторов
clearDiagnosticsForTemplates(context); // <--- Очищаем diagnostics для шаблонов
// === Отслеживание изменений конфига ===
// (Удалено: теперь все настройки глобальные через VSCode settings)
registerTemplateCompletionAndHighlight(context);
registerTemplateSemanticHighlight(context);
registerTemplateDecorations(context);
clearDiagnosticsForTemplates(context);
}
// This method is called when your extension is deactivated
export function deactivate() {}

View File

@@ -1,10 +1,6 @@
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
import { CASE_MODIFIERS } from '../core/templateUtils';
import { buildVarsObject } from '../core/vars';
import { CASE_MODIFIERS } from '@gromlab/create';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
@@ -23,17 +19,4 @@ suite('Template Variable Modifiers', () => {
assert.strictEqual(typeof fn(input), 'string');
}
});
test('buildVarsObject generates all keys', () => {
const vars = buildVarsObject({ name: input });
assert.strictEqual(vars['name'], input);
assert.strictEqual(vars['name.pascalCase'], 'MySuperName');
assert.strictEqual(vars['name.camelCase'], 'mySuperName');
assert.strictEqual(vars['name.snakeCase'], 'my_super_name');
assert.strictEqual(vars['name.kebabCase'], 'my-super-name');
assert.strictEqual(vars['name.screamingSnakeCase'], 'MY_SUPER_NAME');
assert.strictEqual(vars['name.upperCase'], 'My super-name');
assert.strictEqual(vars['name.lowerCase'], 'my super-name');
assert.strictEqual(vars['name.upperCaseAll'], 'MYSUPERNAME');
assert.strictEqual(vars['name.lowerCaseAll'], 'mysupername');
});
});

View File

@@ -1,9 +1,8 @@
// Регистрация и обработка автодополнения шаблонов
import * as vscode from 'vscode';
import * as path from 'path';
import { getAllTemplateVariables, CASE_MODIFIERS } from '../core/templateUtils';
import { collectTemplateVariables, CASE_MODIFIERS } from '@gromlab/create';
import { readConfig } from '../core/config';
import * as fs from 'fs';
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {
const rel = path.relative(templatesDir, filePath);
@@ -14,7 +13,7 @@ export function registerTemplateCompletionAndHighlight(context: vscode.Extension
const completionProvider = {
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
const config = readConfig();
const templatesPath = config.templatesPath || 'templates';
const templatesPath = config.templatesPath || '.templates';
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) return;
const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath);
@@ -25,7 +24,7 @@ export function registerTemplateCompletionAndHighlight(context: vscode.Extension
const textBefore = line.slice(0, position.character);
const match = /{{\s*([\w]+)?(?:\.([\w]*))?[^}]*$/.exec(textBefore);
if (!match) return undefined;
const allVars = getAllTemplateVariables(templatesDir);
const allVars = collectTemplateVariables(templatesDir);
const items = [];
if (match[2] !== undefined) {
for (const mod of Object.keys(CASE_MODIFIERS)) {
@@ -54,4 +53,4 @@ export function registerTemplateCompletionAndHighlight(context: vscode.Extension
'{', '.'
)
);
}
}

View File

@@ -1,7 +1,6 @@
// Декорации и диагностика шаблонов
import * as vscode from 'vscode';
import * as path from 'path';
import { getAllTemplateVariables } from '../core/templateUtils';
import { readConfig } from '../core/config';
const bracketDecoration = vscode.window.createTextEditorDecorationType({

View File

@@ -1,7 +1,6 @@
// Семантическая подсветка шаблонов
import * as vscode from 'vscode';
import * as path from 'path';
import { getAllTemplateVariables } from '../core/templateUtils';
import { readConfig } from '../core/config';
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {

View File

@@ -1,6 +1,6 @@
// Webview для конфигурации расширения
import * as vscode from 'vscode';
import { MyTemplateGeneratorConfig, readConfig, writeConfig } from '../core/config';
import { TemplateForgeConfig, readConfig, writeConfig } from '../core/config';
const LOCALIZATION: Record<'ru'|'en', {
title: string;
@@ -27,7 +27,7 @@ const LOCALIZATION: Record<'ru'|'en', {
english: 'English',
},
en: {
title: 'Template Generator Settings',
title: 'Template Forge Settings',
templatesPath: 'Templates path:',
overwriteFiles: 'Overwrite files',
inputMode: 'Input mode:',
@@ -42,8 +42,8 @@ const LOCALIZATION: Record<'ru'|'en', {
export async function showConfigWebview(context: vscode.ExtensionContext) {
const panel = vscode.window.createWebviewPanel(
'myTemplateGeneratorConfig',
'Настройки MyTemplateGenerator',
'templateForgeConfig',
'Template Forge Settings',
vscode.ViewColumn.One,
{ enableScripts: true }
);
@@ -123,7 +123,7 @@ export async function showConfigWebview(context: vscode.ExtensionContext) {
async msg => {
if (msg.type === 'save') {
await writeConfig(msg.data);
vscode.window.showInformationMessage('Настройки сохранены!');
vscode.window.setStatusBarMessage('Настройки сохранены!', 3000);
panel.dispose();
}
if (msg.type === 'setLanguage') {
@@ -136,4 +136,4 @@ export async function showConfigWebview(context: vscode.ExtensionContext) {
undefined,
context.subscriptions
);
}
}

View File

@@ -1,14 +1,12 @@
// Webview для выбора шаблона и переменных
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { getAllTemplateVariables } from '../core/templateUtils';
import { collectTemplateVariables } from '@gromlab/create';
import { I18N_DICTIONARIES } from '../core/i18n';
import { writeConfig, readConfig } from '../core/config';
export async function showTemplateAndVarsWebview(
context: vscode.ExtensionContext,
templatesDir: string,
templatesMap: Map<string, string>,
targetPath: string,
initialLanguage: string
): Promise<{ template: string, vars: Record<string, string> } | undefined> {
@@ -16,8 +14,7 @@ export async function showTemplateAndVarsWebview(
function getDict() {
return I18N_DICTIONARIES[language] || I18N_DICTIONARIES['ru'];
}
const templates = fs.readdirSync(templatesDir).filter(f => fs.statSync(path.join(templatesDir, f)).isDirectory());
// Стили теперь лежат в media/styles.css (папка для статики)
const templateNames = Array.from(templatesMap.keys());
const stylePath = vscode.Uri.joinPath(context.extensionUri, 'media', 'styles.css');
return new Promise((resolve) => {
const panel = vscode.window.createWebviewPanel(
@@ -28,8 +25,15 @@ export async function showTemplateAndVarsWebview(
);
const styleUri = panel.webview.asWebviewUri(stylePath);
let currentVars: string[] = [];
let currentTemplate = templates[0] || '';
let currentTemplate = templateNames[0] || '';
let disposed = false;
function getVarsForTemplate(name: string): string[] {
const dir = templatesMap.get(name);
if (!dir) return [];
return Array.from(collectTemplateVariables(dir));
}
function getVarsHtml(vars: string[], values: Record<string, string> = {}) {
const dict = getDict();
if (!vars.length) return '';
@@ -89,14 +93,12 @@ export async function showTemplateAndVarsWebview(
(function() {
const vscode = acquireVsCodeApi();
function initHandlers() {
// Template radio
const templateRadios = document.querySelectorAll('input[name="templateRadio"]');
templateRadios.forEach(radio => {
radio.addEventListener('change', (e) => {
vscode.postMessage({ type: 'selectTemplate', template: e.target.value, language: document.getElementById('languageSelect').value });
});
});
// Vars form
const varsForm = document.getElementById('varsForm');
if (varsForm) {
varsForm.addEventListener('submit', (e) => {
@@ -108,7 +110,6 @@ export async function showTemplateAndVarsWebview(
vscode.postMessage({ type: 'submit', template: document.querySelector('input[name="templateRadio"]:checked')?.value || '', data, language: document.getElementById('languageSelect').value });
});
}
// Language select
const langSel = document.getElementById('languageSelect');
if (langSel) {
langSel.addEventListener('change', (e) => {
@@ -123,52 +124,33 @@ export async function showTemplateAndVarsWebview(
</body>
</html>
`;
// После перерисовки HTML вызываем initHandlers
setTimeout(() => {
panel.webview.postMessage({ type: 'callInitHandlers' });
}, 0);
}
// Инициализация: сразу выбран первый шаблон и форма переменных
let initialVars: string[] = [];
if (currentTemplate) {
const templateDir = path.join(templatesDir, currentTemplate);
const allVars = getAllTemplateVariables(templateDir);
initialVars = Array.from(allVars);
currentVars = initialVars;
}
setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(initialVars));
// Обработка сообщений
// Инициализация
currentVars = getVarsForTemplate(currentTemplate);
setHtml(getTemplatesRadioHtml(templateNames, currentTemplate), getVarsHtml(currentVars));
panel.webview.onDidReceiveMessage(
async message => {
if (message.type === 'selectTemplate') {
currentTemplate = message.template;
if (message.language) language = message.language;
if (!currentTemplate) {
setHtml(getTemplatesRadioHtml(templates, ''), '');
setHtml(getTemplatesRadioHtml(templateNames, ''), '');
return;
}
// Получаем переменные для выбранного шаблона
const templateDir = path.join(templatesDir, currentTemplate);
const allVars = getAllTemplateVariables(templateDir);
currentVars = Array.from(allVars);
setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(currentVars));
currentVars = getVarsForTemplate(currentTemplate);
setHtml(getTemplatesRadioHtml(templateNames, currentTemplate), getVarsHtml(currentVars));
} else if (message.type === 'setLanguage') {
if (message.language) language = message.language;
// Сохраняем язык в конфиг
const oldConfig = readConfig();
await writeConfig({ ...oldConfig, language });
currentTemplate = message.template || templates[0] || '';
// Получаем переменные для выбранного шаблона
let baseVars: string[] = [];
if (currentTemplate) {
const templateDir = path.join(templatesDir, currentTemplate);
const allVars = getAllTemplateVariables(templateDir);
baseVars = Array.from(allVars);
currentVars = baseVars;
}
setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(currentVars));
} else if (message.type === 'changeLanguage') {
// legacy, не нужен
currentTemplate = message.template || templateNames[0] || '';
currentVars = getVarsForTemplate(currentTemplate);
setHtml(getTemplatesRadioHtml(templateNames, currentTemplate), getVarsHtml(currentVars));
} else if (message.type === 'submit') {
if (message.language) language = message.language;
if (!disposed) {
@@ -176,8 +158,6 @@ export async function showTemplateAndVarsWebview(
panel.dispose();
resolve({ template: message.template, vars: message.data });
}
} else if (message.type === 'callInitHandlers') {
// Ничего не делаем, скрипт внутри webview вызовет window.initHandlers
}
},
undefined,
@@ -190,4 +170,4 @@ export async function showTemplateAndVarsWebview(
}
}, null, context.subscriptions);
});
}
}

View File

@@ -20,8 +20,7 @@ const extensionConfig = {
libraryTarget: 'commonjs2'
},
externals: {
vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
handlebars: 'commonjs handlebars' // исключаем handlebars для избежания предупреждений о require.extensions
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
// modules added here also need to be added in the .vscodeignore file
},
resolve: {