feat: Template Forge 1.0.0
- генерация файлов и папок из шаблонов с подстановкой переменных - каскадный поиск .templates вверх по дереву каталогов - подсветка синтаксиса и автодополнение переменных в шаблонах - webview и inputBox режимы ввода переменных - локализация ru/en - ядро генерации через @gromlab/create - Gitea Actions CI для автопубликации
This commit is contained in:
26
.gitea/workflows/publish.yml
Normal file
26
.gitea/workflows/publish.yml
Normal 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
209
.github/README.md
vendored
@@ -1,170 +1,85 @@
|
|||||||
[🇬🇧 English](#english) | [🇷🇺 Русский](#russian)
|
# Template Forge
|
||||||
|
|
||||||
> [!WARNING]
|
Расширение для VS Code. Генерирует файлы и папки из ваших шаблонов с подстановкой переменных.
|
||||||
> **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
|
|
||||||
|
|
||||||
<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
|
|
||||||
|
|
||||||

|
## Пример
|
||||||

|
|
||||||
|
|
||||||
**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/
|
.templates/
|
||||||
component/
|
component/
|
||||||
{{name}}/
|
{{name.pascalCase}}/
|
||||||
index.tsx
|
{{name.pascalCase}}.tsx
|
||||||
{{name.camelCase}}.module.css
|
types/
|
||||||
|
{{name.kebabCase}}.type.ts
|
||||||
|
styles/
|
||||||
|
{{name.kebabCase}}.module.css
|
||||||
```
|
```
|
||||||
|
|
||||||
**Available modifiers:**
|
Содержимое `{{name.pascalCase}}.tsx`:
|
||||||
|
|
||||||
| Modifier | Example (`name = myComponent`) |
|
```tsx
|
||||||
|-----------------------|-------------------------------|
|
import cl from 'clsx'
|
||||||
| `{{name}}` | myComponent |
|
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
|
||||||
| `{{name.pascalCase}}` | MyComponent |
|
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||||
| `{{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 |
|
|
||||||
|
|
||||||
**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:**
|
return (
|
||||||
|
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||||
This extension works with **any framework** — you define your own templates for any structure you need!
|
{children}
|
||||||
|
</div>
|
||||||
| 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 компонентов и других
|
|
||||||
- Визуальный конфигуратор и поддержка локализации (русский/английский)
|
|
||||||
- Гибкая настройка: путь к шаблонам, режим ввода переменных, запрет/разрешение перезаписи файлов
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
**Как использовать:**
|
|
||||||
1. Создайте папку с шаблонами (по умолчанию `.templates`).
|
|
||||||
2. Используйте переменные в шаблонах: `{{name}}`, `{{name.pascalCase}}` и т.д.
|
|
||||||
3. Кликните правой кнопкой по папке в проекте → **Создать из шаблона...**
|
|
||||||
4. Выберите шаблон, заполните переменные — структура будет создана автоматически.
|
|
||||||
|
|
||||||
**Пример шаблона:**
|
|
||||||
```
|
|
||||||
.templates/
|
|
||||||
component/
|
|
||||||
{{name}}/
|
|
||||||
index.tsx
|
|
||||||
{{name.camelCase}}.module.css
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Доступные модификаторы:**
|
При вводе `user-profile` получите:
|
||||||
|
|
||||||
| Модификатор | Пример (`name = myComponent`) |
|
```
|
||||||
|----------------------|-------------------------------|
|
UserProfile/
|
||||||
| `{{name}}` | myComponent |
|
UserProfile.tsx
|
||||||
| `{{name.pascalCase}}`| MyComponent |
|
types/
|
||||||
| `{{name.camelCase}}` | myComponent |
|
user-profile.type.ts
|
||||||
| `{{name.snakeCase}}` | my_component |
|
styles/
|
||||||
| `{{name.kebabCase}}` | my-component |
|
user-profile.module.css
|
||||||
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
|
```
|
||||||
| `{{name.upperCase}}` | Mycomponent |
|
|
||||||
| `{{name.lowerCase}}` | mycomponent |
|
|
||||||
| `{{name.upperCaseAll}}` | MYCOMPONENT |
|
|
||||||
| `{{name.lowerCaseAll}}` | mycomponent |
|
|
||||||
|
|
||||||
**Поддерживаемые модификаторы:** 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 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `templateForge.templatesPath` | Путь к шаблонам | `.templates` |
|
||||||
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `templateForge.overwriteFiles` | Перезапись существующих файлов | `false` |
|
||||||
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `templateForge.inputMode` | Режим ввода: `webview` или `inputBox` | `webview` |
|
||||||
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `templateForge.language` | Язык интерфейса (`ru` / `en`) | `en` |
|
||||||
| 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 шаблоны, шаблоны для проектов, шаблоны для фреймворков, структура проекта, структура папок, генерация файлов, генерация папок, файловый генератор, генератор папок, стартер шаблон
|
|
||||||
|
|||||||
15
.vscode/tasks.json
vendored
15
.vscode/tasks.json
vendored
@@ -6,7 +6,20 @@
|
|||||||
{
|
{
|
||||||
"type": "npm",
|
"type": "npm",
|
||||||
"script": "watch",
|
"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,
|
"isBackground": true,
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "never",
|
"reveal": "never",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Change Log
|
# 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.
|
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||||
|
|
||||||
|
|||||||
192
README.md
192
README.md
@@ -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
|
1. Создайте папку `.templates` в корне проекта
|
||||||
- Syntax highlighting and autocomplete for template variables in template files (`{{name}}`, `{{name.camelCase}}`, etc.)
|
2. Внутри -- каждая подпапка это шаблон
|
||||||
- Generate project structure, files, and folders for popular frameworks: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
|
3. Используйте переменные в именах файлов и содержимом: `{{name}}` присутствует всегда, дополнительно можно использовать любое количество своих -- `{{author}}`, `{{module}}` и т.д.
|
||||||
- Quickly create components: create React components, Vue components, Next.js components, Angular components, and more
|
4. ПКМ по папке в проводнике -- **Создать из шаблона...**
|
||||||
- Visual configurator and localization support (Russian/English)
|
|
||||||
- Flexible settings: templates folder path, variable input mode, overwrite protection
|
|
||||||
|
|
||||||

|
Расширение само найдёт все переменные в шаблоне и предложит заполнить каждую.
|
||||||

|
|
||||||
|
|
||||||
**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/
|
.templates/
|
||||||
component/
|
component/
|
||||||
{{name}}/
|
{{name.pascalCase}}/
|
||||||
index.tsx
|
{{name.pascalCase}}.tsx
|
||||||
{{name.camelCase}}.module.css
|
types/
|
||||||
|
{{name.kebabCase}}.type.ts
|
||||||
|
styles/
|
||||||
|
{{name.kebabCase}}.module.css
|
||||||
```
|
```
|
||||||
|
|
||||||
**Available modifiers:**
|
Содержимое `{{name.pascalCase}}.tsx`:
|
||||||
|
|
||||||
| Modifier | Example (`name = myComponent`) |
|
```tsx
|
||||||
|-----------------------|-------------------------------|
|
import cl from 'clsx'
|
||||||
| `{{name}}` | myComponent |
|
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
|
||||||
| `{{name.pascalCase}}` | MyComponent |
|
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||||
| `{{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 |
|
|
||||||
|
|
||||||
**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:**
|
return (
|
||||||
|
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||||
This extension works with **any framework** — you define your own templates for any structure you need!
|
{children}
|
||||||
|
</div>
|
||||||
| 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 компонентов и других
|
|
||||||
- Визуальный конфигуратор и поддержка локализации (русский/английский)
|
|
||||||
- Гибкая настройка: путь к шаблонам, режим ввода переменных, запрет/разрешение перезаписи файлов
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
**Как использовать:**
|
|
||||||
1. Создайте папку с шаблонами (по умолчанию `.templates`).
|
|
||||||
2. Используйте переменные в шаблонах: `{{name}}`, `{{name.pascalCase}}` и т.д.
|
|
||||||
3. Кликните правой кнопкой по папке в проекте → **Создать из шаблона...**
|
|
||||||
4. Выберите шаблон, заполните переменные — структура будет создана автоматически.
|
|
||||||
|
|
||||||
**Пример шаблона:**
|
|
||||||
```
|
|
||||||
.templates/
|
|
||||||
component/
|
|
||||||
{{name}}/
|
|
||||||
index.tsx
|
|
||||||
{{name.camelCase}}.module.css
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Доступные модификаторы:**
|
При вводе `user-profile` получите:
|
||||||
|
|
||||||
| Модификатор | Пример (`name = myComponent`) |
|
```
|
||||||
|----------------------|-------------------------------|
|
UserProfile/
|
||||||
| `{{name}}` | myComponent |
|
UserProfile.tsx
|
||||||
| `{{name.pascalCase}}`| MyComponent |
|
types/
|
||||||
| `{{name.camelCase}}` | myComponent |
|
user-profile.type.ts
|
||||||
| `{{name.snakeCase}}` | my_component |
|
styles/
|
||||||
| `{{name.kebabCase}}` | my-component |
|
user-profile.module.css
|
||||||
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
|
```
|
||||||
| `{{name.upperCase}}` | Mycomponent |
|
|
||||||
| `{{name.lowerCase}}` | mycomponent |
|
|
||||||
| `{{name.upperCaseAll}}` | MYCOMPONENT |
|
|
||||||
| `{{name.lowerCaseAll}}` | mycomponent |
|
|
||||||
|
|
||||||
**Поддерживаемые модификаторы:** 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 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `templateForge.templatesPath` | Путь к шаблонам | `.templates` |
|
||||||
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `templateForge.overwriteFiles` | Перезапись существующих файлов | `false` |
|
||||||
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `templateForge.inputMode` | Режим ввода: `webview` или `inputBox` | `webview` |
|
||||||
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| `templateForge.language` | Язык интерфейса (`ru` / `en`) | `en` |
|
||||||
| 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 шаблоны, шаблоны для проектов, шаблоны для фреймворков, структура проекта, структура папок, генерация файлов, генерация папок, файловый генератор, генератор папок, стартер шаблон
|
|
||||||
|
|||||||
722
package-lock.json
generated
722
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
170
package.json
170
package.json
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "mytemplategenerator",
|
"name": "templateforge",
|
||||||
"displayName": "Template & Component Generator: React, Vue, Next.js, Angular",
|
"displayName": "Template Forge",
|
||||||
"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.",
|
"description": "Generate files and folders from templates with variable substitution and case modifiers. Syntax highlighting and autocomplete for template variables.",
|
||||||
"version": "0.0.11",
|
"version": "1.0.0",
|
||||||
"publisher": "MyTemplateGenerator",
|
"publisher": "TemplateForge",
|
||||||
"author": "Sergey Gromov",
|
"author": "Sergey Gromov",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-app": "npm run build && npm run package-vsce",
|
"build-app": "npm run build && npm run package-vsce",
|
||||||
@@ -19,193 +19,91 @@
|
|||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
"Snippets",
|
"Snippets",
|
||||||
"Programming Languages",
|
|
||||||
"Other"
|
"Other"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"template",
|
"template",
|
||||||
"templates",
|
|
||||||
"template generator",
|
|
||||||
"component generator",
|
|
||||||
"scaffold",
|
"scaffold",
|
||||||
"scaffolding",
|
"generator",
|
||||||
"boilerplate",
|
"boilerplate",
|
||||||
"starter template",
|
"component",
|
||||||
"project structure",
|
|
||||||
"file generator",
|
"file generator",
|
||||||
"folder generator",
|
"project structure",
|
||||||
"structure generator",
|
"code generation",
|
||||||
"react component generator",
|
"snippets"
|
||||||
"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 шаблоны",
|
|
||||||
"шаблоны для проектов",
|
|
||||||
"шаблоны для фреймворков",
|
|
||||||
"структура проекта",
|
|
||||||
"структура папок",
|
|
||||||
"генерация файлов",
|
|
||||||
"генерация папок",
|
|
||||||
"файловый генератор",
|
|
||||||
"генератор папок",
|
|
||||||
"стартер шаблон"
|
|
||||||
],
|
],
|
||||||
"icon": "logo.png",
|
"icon": "logo.png",
|
||||||
"url": "https://github.com/gromov-io/MyTemplateGenerator",
|
"homepage": "https://gromlab.ru/gromov/templateforge",
|
||||||
"homepage": "https://github.com/gromov-io/MyTemplateGenerator",
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/gromov-io/MyTemplateGenerator"
|
"url": "git@gromlab.ru:gromov/templateforge.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/gromov-io/MyTemplateGenerator/issues"
|
"url": "https://gromlab.ru/gromov/templateforge/issues"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.60.0"
|
"vscode": "^1.60.0"
|
||||||
},
|
},
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onStartupFinished",
|
"onStartupFinished"
|
||||||
"onLanguage:javascript",
|
|
||||||
"onLanguage:typescript",
|
|
||||||
"onLanguage:css",
|
|
||||||
"onLanguage:plaintext",
|
|
||||||
"onFileSystem: file"
|
|
||||||
],
|
],
|
||||||
"main": "./dist/extension.js",
|
"main": "./dist/extension.js",
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"commands": [
|
"commands": [
|
||||||
{
|
{
|
||||||
"command": "mytemplategenerator.createFromTemplate",
|
"command": "templateforge.createFromTemplate",
|
||||||
"title": "%mytemplategenerator.createFromTemplate.title%"
|
"title": "%templateforge.createFromTemplate.title%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "mytemplategenerator.configure",
|
"command": "templateforge.configure",
|
||||||
"title": "%mytemplategenerator.configure.title%"
|
"title": "%templateforge.configure.title%"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"menus": {
|
"menus": {
|
||||||
"explorer/context": [
|
"explorer/context": [
|
||||||
{
|
{
|
||||||
"command": "mytemplategenerator.createFromTemplate",
|
"command": "templateforge.createFromTemplate",
|
||||||
"group": "navigation@10"
|
"group": "navigation@10"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"configuration": {
|
"configuration": {
|
||||||
"title": "myTemplateGenerator",
|
"title": "Template Forge",
|
||||||
"properties": {
|
"properties": {
|
||||||
"myTemplateGenerator.templatesPath": {
|
"templateForge.templatesPath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": ".templates",
|
"default": ".templates",
|
||||||
"description": "%mytemplategenerator.config.templatesPath.description%",
|
"description": "%templateforge.config.templatesPath.description%",
|
||||||
"markdownDescription": "%mytemplategenerator.config.templatesPath.description%",
|
"markdownDescription": "%templateforge.config.templatesPath.description%",
|
||||||
"scope": "application"
|
"scope": "application"
|
||||||
},
|
},
|
||||||
"myTemplateGenerator.overwriteFiles": {
|
"templateForge.overwriteFiles": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false,
|
"default": false,
|
||||||
"description": "%mytemplategenerator.config.overwriteFiles.description%",
|
"description": "%templateforge.config.overwriteFiles.description%",
|
||||||
"markdownDescription": "%mytemplategenerator.config.overwriteFiles.description%",
|
"markdownDescription": "%templateforge.config.overwriteFiles.description%",
|
||||||
"scope": "application"
|
"scope": "application"
|
||||||
},
|
},
|
||||||
"myTemplateGenerator.inputMode": {
|
"templateForge.inputMode": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"webview",
|
"webview",
|
||||||
"inputBox"
|
"inputBox"
|
||||||
],
|
],
|
||||||
"default": "webview",
|
"default": "webview",
|
||||||
"description": "%mytemplategenerator.config.inputMode.description%",
|
"description": "%templateforge.config.inputMode.description%",
|
||||||
"markdownDescription": "%mytemplategenerator.config.inputMode.description%",
|
"markdownDescription": "%templateforge.config.inputMode.description%",
|
||||||
"scope": "application"
|
"scope": "application"
|
||||||
},
|
},
|
||||||
"myTemplateGenerator.language": {
|
"templateForge.language": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"ru",
|
"ru",
|
||||||
"en"
|
"en"
|
||||||
],
|
],
|
||||||
"default": "en",
|
"default": "en",
|
||||||
"description": "%mytemplategenerator.config.language.description%",
|
"description": "%templateforge.config.language.description%",
|
||||||
"markdownDescription": "%mytemplategenerator.config.language.description%",
|
"markdownDescription": "%templateforge.config.language.description%",
|
||||||
"scope": "application"
|
"scope": "application"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,8 +139,6 @@
|
|||||||
"webpack-cli": "^6.0.1"
|
"webpack-cli": "^6.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"change-case": "^5.4.4",
|
"@gromlab/create": "^0.2.0"
|
||||||
"change-case-all": "^2.1.0",
|
|
||||||
"handlebars": "^4.7.8"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mytemplategenerator.createFromTemplate.title": "Create from template...",
|
"templateforge.createFromTemplate.title": "Create from template...",
|
||||||
"mytemplategenerator.configure.title": "Configure myTemplateGenerator...",
|
"templateforge.configure.title": "Configure Template Forge...",
|
||||||
"mytemplategenerator.config.templatesPath.description": "Path to the templates folder (relative to the project root)",
|
"templateforge.config.templatesPath.description": "Path to the templates folder (relative to the project root)",
|
||||||
"mytemplategenerator.config.overwriteFiles.description": "Overwrite existing files when generating from template",
|
"templateforge.config.overwriteFiles.description": "Overwrite existing files when generating from template",
|
||||||
"mytemplategenerator.config.inputMode.description": "Variable input mode: webview or inputBox",
|
"templateforge.config.inputMode.description": "Variable input mode: webview or inputBox",
|
||||||
"mytemplategenerator.config.language.description": "Extension interface language"
|
"templateforge.config.language.description": "Extension interface language"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mytemplategenerator.createFromTemplate.title": "Создать из шаблона...",
|
"templateforge.createFromTemplate.title": "Создать из шаблона...",
|
||||||
"mytemplategenerator.configure.title": "Настроить myTemplateGenerator...",
|
"templateforge.configure.title": "Настроить Template Forge...",
|
||||||
"mytemplategenerator.config.templatesPath.description": "Путь к папке с шаблонами (относительно корня проекта)",
|
"templateforge.config.templatesPath.description": "Путь к папке с шаблонами (относительно корня проекта)",
|
||||||
"mytemplategenerator.config.overwriteFiles.description": "Перезаписывать ли существующие файлы при генерации",
|
"templateforge.config.overwriteFiles.description": "Перезаписывать ли существующие файлы при генерации",
|
||||||
"mytemplategenerator.config.inputMode.description": "Режим ввода переменных: webview или inputBox",
|
"templateforge.config.inputMode.description": "Режим ввода переменных: webview или inputBox",
|
||||||
"mytemplategenerator.config.language.description": "Язык интерфейса расширения"
|
"templateforge.config.language.description": "Язык интерфейса расширения"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
export interface MyTemplateGeneratorConfig {
|
export interface TemplateForgeConfig {
|
||||||
templatesPath: string;
|
templatesPath: string;
|
||||||
overwriteFiles: boolean;
|
overwriteFiles: boolean;
|
||||||
inputMode: 'webview' | 'inputBox';
|
inputMode: 'webview' | 'inputBox';
|
||||||
language?: string;
|
language?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readConfig(): MyTemplateGeneratorConfig {
|
export function readConfig(): TemplateForgeConfig {
|
||||||
const config = vscode.workspace.getConfiguration('myTemplateGenerator');
|
const config = vscode.workspace.getConfiguration('templateForge');
|
||||||
return {
|
return {
|
||||||
templatesPath: config.get<string>('templatesPath', '.templates'),
|
templatesPath: config.get<string>('templatesPath', '.templates'),
|
||||||
overwriteFiles: config.get<boolean>('overwriteFiles', false),
|
overwriteFiles: config.get<boolean>('overwriteFiles', false),
|
||||||
@@ -20,8 +20,8 @@ export function readConfig(): MyTemplateGeneratorConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeConfig(newConfig: Partial<MyTemplateGeneratorConfig>) {
|
export async function writeConfig(newConfig: Partial<TemplateForgeConfig>) {
|
||||||
const config = vscode.workspace.getConfiguration('myTemplateGenerator');
|
const config = vscode.workspace.getConfiguration('templateForge');
|
||||||
if (newConfig.templatesPath !== undefined) {
|
if (newConfig.templatesPath !== undefined) {
|
||||||
await config.update('templatesPath', newConfig.templatesPath, vscode.ConfigurationTarget.Global);
|
await config.update('templatesPath', newConfig.templatesPath, vscode.ConfigurationTarget.Global);
|
||||||
}
|
}
|
||||||
@@ -34,4 +34,4 @@ export async function writeConfig(newConfig: Partial<MyTemplateGeneratorConfig>)
|
|||||||
if (newConfig.language !== undefined) {
|
if (newConfig.language !== undefined) {
|
||||||
await config.update('language', newConfig.language, vscode.ConfigurationTarget.Global);
|
await config.update('language', newConfig.language, vscode.ConfigurationTarget.Global);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>> = {
|
export const I18N_DICTIONARIES: Record<string, Record<string, string>> = {
|
||||||
ru: {
|
ru: {
|
||||||
@@ -40,7 +37,7 @@ export const I18N_DICTIONARIES: Record<string, Record<string, string>> = {
|
|||||||
|
|
||||||
export const SETTINGS_I18N: Record<string, Record<string, string>> = {
|
export const SETTINGS_I18N: Record<string, Record<string, string>> = {
|
||||||
ru: {
|
ru: {
|
||||||
title: 'Настройки myTemplateGenerator',
|
title: 'Настройки Template Forge',
|
||||||
templatesPath: 'Путь к шаблонам:',
|
templatesPath: 'Путь к шаблонам:',
|
||||||
overwriteFiles: 'Перезаписывать существующие файлы',
|
overwriteFiles: 'Перезаписывать существующие файлы',
|
||||||
inputMode: 'Способ ввода переменных:',
|
inputMode: 'Способ ввода переменных:',
|
||||||
@@ -50,7 +47,7 @@ export const SETTINGS_I18N: Record<string, Record<string, string>> = {
|
|||||||
save: 'Сохранить'
|
save: 'Сохранить'
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
title: 'myTemplateGenerator Settings',
|
title: 'Template Forge Settings',
|
||||||
templatesPath: 'Templates path:',
|
templatesPath: 'Templates path:',
|
||||||
overwriteFiles: 'Overwrite existing files',
|
overwriteFiles: 'Overwrite existing files',
|
||||||
inputMode: 'Variable input method:',
|
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: 'Выберите шаблон' });
|
|
||||||
}
|
|
||||||
|
|||||||
66
src/core/templateResolver.ts
Normal file
66
src/core/templateResolver.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,4 @@
|
|||||||
// Работа с переменными шаблонов
|
// Сбор переменных от пользователя через InputBox
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
export async function collectUserVars(baseVars: Set<string>): Promise<Record<string, string>> {
|
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;
|
result[v] = input;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
136
src/extension.ts
136
src/extension.ts
@@ -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 vscode from 'vscode';
|
||||||
import * as fs from 'fs';
|
import { collectTemplateVariables, readDirRecursive, buildPlan, writePlan, getCollisions } from '@gromlab/create';
|
||||||
import * as path from 'path';
|
import { collectUserVars } from './core/vars';
|
||||||
import * as Handlebars from 'handlebars';
|
import { readConfig } from './core/config';
|
||||||
import { getAllTemplateVariables, copyTemplateWithVars } from './core/templateUtils';
|
|
||||||
import { buildVarsObject, collectUserVars } from './core/vars';
|
|
||||||
import { readConfig, writeConfig } from './core/config';
|
|
||||||
import { showConfigWebview } from './webview/configWebview';
|
import { showConfigWebview } from './webview/configWebview';
|
||||||
import { showTemplateAndVarsWebview } from './webview/templateVarsWebview';
|
import { showTemplateAndVarsWebview } from './webview/templateVarsWebview';
|
||||||
import { registerTemplateCompletionAndHighlight } from './vscode/completion';
|
import { registerTemplateCompletionAndHighlight } from './vscode/completion';
|
||||||
import { registerTemplateSemanticHighlight } from './vscode/semanticHighlight';
|
import { registerTemplateSemanticHighlight } from './vscode/semanticHighlight';
|
||||||
import { registerTemplateDecorations, clearDiagnosticsForTemplates } from './vscode/decorations';
|
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) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
|
||||||
// Use the console to output diagnostic information (console.log) and errors (console.error)
|
const createFromTemplate = vscode.commands.registerCommand('templateforge.createFromTemplate', async (uri: vscode.Uri) => {
|
||||||
// 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 config = readConfig();
|
const config = readConfig();
|
||||||
const dict = I18N_DICTIONARIES[config.language || 'ru'] || I18N_DICTIONARIES['ru'];
|
const dict = I18N_DICTIONARIES[config.language || 'ru'] || I18N_DICTIONARIES['ru'];
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
@@ -59,63 +20,76 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.window.showErrorMessage(dict.noFolders);
|
vscode.window.showErrorMessage(dict.noFolders);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const templatesDir = path.join(workspaceFolders[0].uri.fsPath, config.templatesPath);
|
|
||||||
if (!fs.existsSync(templatesDir) || !fs.statSync(templatesDir).isDirectory()) {
|
const workspaceRoot = workspaceFolders[0].uri.fsPath;
|
||||||
vscode.window.showErrorMessage(`${dict.templatesNotFound} ${templatesDir}`);
|
const targetDir = uri.fsPath;
|
||||||
|
|
||||||
|
// Каскадный поиск шаблонов вверх от точки вызова до корня workspace
|
||||||
|
const templates = discoverTemplates(targetDir, workspaceRoot, config.templatesPath);
|
||||||
|
if (templates.size === 0) {
|
||||||
|
vscode.window.showErrorMessage(dict.templatesNotFound);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let template: string | undefined;
|
|
||||||
|
let templateName: string | undefined;
|
||||||
let userVars: Record<string, string> | undefined;
|
let userVars: Record<string, string> | undefined;
|
||||||
|
|
||||||
if (config.inputMode === 'webview') {
|
if (config.inputMode === 'webview') {
|
||||||
vscode.window.showInformationMessage('[DEBUG] Вызов webview создания шаблона...');
|
const result = await showTemplateAndVarsWebview(context, templates, targetDir, config.language || 'ru');
|
||||||
const result: { template: string, vars: Record<string, string> } | undefined = await showTemplateAndVarsWebview(context, templatesDir, uri.fsPath, config.language || 'ru');
|
if (!result) return;
|
||||||
if (!result) {
|
templateName = result.template;
|
||||||
vscode.window.showInformationMessage('[DEBUG] Webview был закрыт или не вернул результат');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
template = result.template;
|
|
||||||
userVars = result.vars;
|
userVars = result.vars;
|
||||||
} else {
|
} else {
|
||||||
vscode.window.showInformationMessage('[DEBUG] Вызов выбора шаблона через quickPick...');
|
const templateNames = Array.from(templates.keys());
|
||||||
template = await pickTemplate(templatesDir);
|
templateName = await vscode.window.showQuickPick(templateNames, { placeHolder: dict.chooseTemplate });
|
||||||
if (!template) {
|
if (!templateName) return;
|
||||||
vscode.window.showInformationMessage('[DEBUG] Шаблон не выбран');
|
|
||||||
return;
|
const templateDir = templates.get(templateName)!;
|
||||||
}
|
const allVars = collectTemplateVariables(templateDir);
|
||||||
const templateDir = path.join(templatesDir, template);
|
userVars = await collectUserVars(allVars);
|
||||||
const allVars = getAllTemplateVariables(templateDir);
|
|
||||||
const baseVars = Array.from(allVars);
|
|
||||||
userVars = await collectUserVars(new Set(baseVars));
|
|
||||||
}
|
}
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
const templateDir = path.join(templatesDir, template);
|
|
||||||
try {
|
try {
|
||||||
const vars = buildVarsObject(userVars);
|
const files = readDirRecursive(templateDir);
|
||||||
vscode.window.showInformationMessage('[DEBUG] Копирование шаблона...');
|
const plan = buildPlan(templateDir, targetDir, userVars, files);
|
||||||
copyTemplateWithVars(templateDir, uri.fsPath, vars, config.overwriteFiles, dict, template);
|
|
||||||
|
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) {
|
} catch (e: any) {
|
||||||
vscode.window.showErrorMessage(`${dict.createError}: ${e.message}`);
|
vscode.window.showErrorMessage(`${dict.createError}: ${e.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
disposable,
|
|
||||||
createFromTemplate,
|
createFromTemplate,
|
||||||
vscode.commands.registerCommand('mytemplategenerator.configure', async () => {
|
vscode.commands.registerCommand('templateforge.configure', async () => {
|
||||||
await showConfigWebview(context);
|
await showConfigWebview(context);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
registerTemplateCompletionAndHighlight(context);
|
|
||||||
let semanticHighlightDisposable: vscode.Disposable | undefined = registerTemplateSemanticHighlight(context);
|
|
||||||
registerTemplateDecorations(context); // <--- Добавить регистрацию декораторов
|
|
||||||
clearDiagnosticsForTemplates(context); // <--- Очищаем diagnostics для шаблонов
|
|
||||||
|
|
||||||
// === Отслеживание изменений конфига ===
|
registerTemplateCompletionAndHighlight(context);
|
||||||
// (Удалено: теперь все настройки глобальные через VSCode settings)
|
registerTemplateSemanticHighlight(context);
|
||||||
|
registerTemplateDecorations(context);
|
||||||
|
clearDiagnosticsForTemplates(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is called when your extension is deactivated
|
|
||||||
export function deactivate() {}
|
export function deactivate() {}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import * as assert from 'assert';
|
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 * as vscode from 'vscode';
|
||||||
import { CASE_MODIFIERS } from '../core/templateUtils';
|
import { CASE_MODIFIERS } from '@gromlab/create';
|
||||||
import { buildVarsObject } from '../core/vars';
|
|
||||||
|
|
||||||
suite('Extension Test Suite', () => {
|
suite('Extension Test Suite', () => {
|
||||||
vscode.window.showInformationMessage('Start all tests.');
|
vscode.window.showInformationMessage('Start all tests.');
|
||||||
@@ -23,17 +19,4 @@ suite('Template Variable Modifiers', () => {
|
|||||||
assert.strictEqual(typeof fn(input), 'string');
|
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');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// Регистрация и обработка автодополнения шаблонов
|
// Регистрация и обработка автодополнения шаблонов
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as path from 'path';
|
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 { readConfig } from '../core/config';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {
|
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {
|
||||||
const rel = path.relative(templatesDir, filePath);
|
const rel = path.relative(templatesDir, filePath);
|
||||||
@@ -14,7 +13,7 @@ export function registerTemplateCompletionAndHighlight(context: vscode.Extension
|
|||||||
const completionProvider = {
|
const completionProvider = {
|
||||||
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
|
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
|
||||||
const config = readConfig();
|
const config = readConfig();
|
||||||
const templatesPath = config.templatesPath || 'templates';
|
const templatesPath = config.templatesPath || '.templates';
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
if (!workspaceFolders || workspaceFolders.length === 0) return;
|
if (!workspaceFolders || workspaceFolders.length === 0) return;
|
||||||
const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath);
|
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 textBefore = line.slice(0, position.character);
|
||||||
const match = /{{\s*([\w]+)?(?:\.([\w]*))?[^}]*$/.exec(textBefore);
|
const match = /{{\s*([\w]+)?(?:\.([\w]*))?[^}]*$/.exec(textBefore);
|
||||||
if (!match) return undefined;
|
if (!match) return undefined;
|
||||||
const allVars = getAllTemplateVariables(templatesDir);
|
const allVars = collectTemplateVariables(templatesDir);
|
||||||
const items = [];
|
const items = [];
|
||||||
if (match[2] !== undefined) {
|
if (match[2] !== undefined) {
|
||||||
for (const mod of Object.keys(CASE_MODIFIERS)) {
|
for (const mod of Object.keys(CASE_MODIFIERS)) {
|
||||||
@@ -54,4 +53,4 @@ export function registerTemplateCompletionAndHighlight(context: vscode.Extension
|
|||||||
'{', '.'
|
'{', '.'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Декорации и диагностика шаблонов
|
// Декорации и диагностика шаблонов
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { getAllTemplateVariables } from '../core/templateUtils';
|
|
||||||
import { readConfig } from '../core/config';
|
import { readConfig } from '../core/config';
|
||||||
|
|
||||||
const bracketDecoration = vscode.window.createTextEditorDecorationType({
|
const bracketDecoration = vscode.window.createTextEditorDecorationType({
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Семантическая подсветка шаблонов
|
// Семантическая подсветка шаблонов
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { getAllTemplateVariables } from '../core/templateUtils';
|
|
||||||
import { readConfig } from '../core/config';
|
import { readConfig } from '../core/config';
|
||||||
|
|
||||||
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {
|
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Webview для конфигурации расширения
|
// Webview для конфигурации расширения
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { MyTemplateGeneratorConfig, readConfig, writeConfig } from '../core/config';
|
import { TemplateForgeConfig, readConfig, writeConfig } from '../core/config';
|
||||||
|
|
||||||
const LOCALIZATION: Record<'ru'|'en', {
|
const LOCALIZATION: Record<'ru'|'en', {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -27,7 +27,7 @@ const LOCALIZATION: Record<'ru'|'en', {
|
|||||||
english: 'English',
|
english: 'English',
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
title: 'Template Generator Settings',
|
title: 'Template Forge Settings',
|
||||||
templatesPath: 'Templates path:',
|
templatesPath: 'Templates path:',
|
||||||
overwriteFiles: 'Overwrite files',
|
overwriteFiles: 'Overwrite files',
|
||||||
inputMode: 'Input mode:',
|
inputMode: 'Input mode:',
|
||||||
@@ -42,8 +42,8 @@ const LOCALIZATION: Record<'ru'|'en', {
|
|||||||
|
|
||||||
export async function showConfigWebview(context: vscode.ExtensionContext) {
|
export async function showConfigWebview(context: vscode.ExtensionContext) {
|
||||||
const panel = vscode.window.createWebviewPanel(
|
const panel = vscode.window.createWebviewPanel(
|
||||||
'myTemplateGeneratorConfig',
|
'templateForgeConfig',
|
||||||
'Настройки MyTemplateGenerator',
|
'Template Forge Settings',
|
||||||
vscode.ViewColumn.One,
|
vscode.ViewColumn.One,
|
||||||
{ enableScripts: true }
|
{ enableScripts: true }
|
||||||
);
|
);
|
||||||
@@ -123,7 +123,7 @@ export async function showConfigWebview(context: vscode.ExtensionContext) {
|
|||||||
async msg => {
|
async msg => {
|
||||||
if (msg.type === 'save') {
|
if (msg.type === 'save') {
|
||||||
await writeConfig(msg.data);
|
await writeConfig(msg.data);
|
||||||
vscode.window.showInformationMessage('Настройки сохранены!');
|
vscode.window.setStatusBarMessage('Настройки сохранены!', 3000);
|
||||||
panel.dispose();
|
panel.dispose();
|
||||||
}
|
}
|
||||||
if (msg.type === 'setLanguage') {
|
if (msg.type === 'setLanguage') {
|
||||||
@@ -136,4 +136,4 @@ export async function showConfigWebview(context: vscode.ExtensionContext) {
|
|||||||
undefined,
|
undefined,
|
||||||
context.subscriptions
|
context.subscriptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
// Webview для выбора шаблона и переменных
|
// Webview для выбора шаблона и переменных
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as fs from 'fs';
|
import { collectTemplateVariables } from '@gromlab/create';
|
||||||
import * as path from 'path';
|
|
||||||
import { getAllTemplateVariables } from '../core/templateUtils';
|
|
||||||
import { I18N_DICTIONARIES } from '../core/i18n';
|
import { I18N_DICTIONARIES } from '../core/i18n';
|
||||||
import { writeConfig, readConfig } from '../core/config';
|
import { writeConfig, readConfig } from '../core/config';
|
||||||
|
|
||||||
export async function showTemplateAndVarsWebview(
|
export async function showTemplateAndVarsWebview(
|
||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
templatesDir: string,
|
templatesMap: Map<string, string>,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
initialLanguage: string
|
initialLanguage: string
|
||||||
): Promise<{ template: string, vars: Record<string, string> } | undefined> {
|
): Promise<{ template: string, vars: Record<string, string> } | undefined> {
|
||||||
@@ -16,8 +14,7 @@ export async function showTemplateAndVarsWebview(
|
|||||||
function getDict() {
|
function getDict() {
|
||||||
return I18N_DICTIONARIES[language] || I18N_DICTIONARIES['ru'];
|
return I18N_DICTIONARIES[language] || I18N_DICTIONARIES['ru'];
|
||||||
}
|
}
|
||||||
const templates = fs.readdirSync(templatesDir).filter(f => fs.statSync(path.join(templatesDir, f)).isDirectory());
|
const templateNames = Array.from(templatesMap.keys());
|
||||||
// Стили теперь лежат в media/styles.css (папка для статики)
|
|
||||||
const stylePath = vscode.Uri.joinPath(context.extensionUri, 'media', 'styles.css');
|
const stylePath = vscode.Uri.joinPath(context.extensionUri, 'media', 'styles.css');
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const panel = vscode.window.createWebviewPanel(
|
const panel = vscode.window.createWebviewPanel(
|
||||||
@@ -28,8 +25,15 @@ export async function showTemplateAndVarsWebview(
|
|||||||
);
|
);
|
||||||
const styleUri = panel.webview.asWebviewUri(stylePath);
|
const styleUri = panel.webview.asWebviewUri(stylePath);
|
||||||
let currentVars: string[] = [];
|
let currentVars: string[] = [];
|
||||||
let currentTemplate = templates[0] || '';
|
let currentTemplate = templateNames[0] || '';
|
||||||
let disposed = false;
|
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> = {}) {
|
function getVarsHtml(vars: string[], values: Record<string, string> = {}) {
|
||||||
const dict = getDict();
|
const dict = getDict();
|
||||||
if (!vars.length) return '';
|
if (!vars.length) return '';
|
||||||
@@ -89,14 +93,12 @@ export async function showTemplateAndVarsWebview(
|
|||||||
(function() {
|
(function() {
|
||||||
const vscode = acquireVsCodeApi();
|
const vscode = acquireVsCodeApi();
|
||||||
function initHandlers() {
|
function initHandlers() {
|
||||||
// Template radio
|
|
||||||
const templateRadios = document.querySelectorAll('input[name="templateRadio"]');
|
const templateRadios = document.querySelectorAll('input[name="templateRadio"]');
|
||||||
templateRadios.forEach(radio => {
|
templateRadios.forEach(radio => {
|
||||||
radio.addEventListener('change', (e) => {
|
radio.addEventListener('change', (e) => {
|
||||||
vscode.postMessage({ type: 'selectTemplate', template: e.target.value, language: document.getElementById('languageSelect').value });
|
vscode.postMessage({ type: 'selectTemplate', template: e.target.value, language: document.getElementById('languageSelect').value });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Vars form
|
|
||||||
const varsForm = document.getElementById('varsForm');
|
const varsForm = document.getElementById('varsForm');
|
||||||
if (varsForm) {
|
if (varsForm) {
|
||||||
varsForm.addEventListener('submit', (e) => {
|
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 });
|
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');
|
const langSel = document.getElementById('languageSelect');
|
||||||
if (langSel) {
|
if (langSel) {
|
||||||
langSel.addEventListener('change', (e) => {
|
langSel.addEventListener('change', (e) => {
|
||||||
@@ -123,52 +124,33 @@ export async function showTemplateAndVarsWebview(
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
// После перерисовки HTML вызываем initHandlers
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
panel.webview.postMessage({ type: 'callInitHandlers' });
|
panel.webview.postMessage({ type: 'callInitHandlers' });
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
// Инициализация: сразу выбран первый шаблон и форма переменных
|
|
||||||
let initialVars: string[] = [];
|
// Инициализация
|
||||||
if (currentTemplate) {
|
currentVars = getVarsForTemplate(currentTemplate);
|
||||||
const templateDir = path.join(templatesDir, currentTemplate);
|
setHtml(getTemplatesRadioHtml(templateNames, currentTemplate), getVarsHtml(currentVars));
|
||||||
const allVars = getAllTemplateVariables(templateDir);
|
|
||||||
initialVars = Array.from(allVars);
|
|
||||||
currentVars = initialVars;
|
|
||||||
}
|
|
||||||
setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(initialVars));
|
|
||||||
// Обработка сообщений
|
|
||||||
panel.webview.onDidReceiveMessage(
|
panel.webview.onDidReceiveMessage(
|
||||||
async message => {
|
async message => {
|
||||||
if (message.type === 'selectTemplate') {
|
if (message.type === 'selectTemplate') {
|
||||||
currentTemplate = message.template;
|
currentTemplate = message.template;
|
||||||
if (message.language) language = message.language;
|
if (message.language) language = message.language;
|
||||||
if (!currentTemplate) {
|
if (!currentTemplate) {
|
||||||
setHtml(getTemplatesRadioHtml(templates, ''), '');
|
setHtml(getTemplatesRadioHtml(templateNames, ''), '');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Получаем переменные для выбранного шаблона
|
currentVars = getVarsForTemplate(currentTemplate);
|
||||||
const templateDir = path.join(templatesDir, currentTemplate);
|
setHtml(getTemplatesRadioHtml(templateNames, currentTemplate), getVarsHtml(currentVars));
|
||||||
const allVars = getAllTemplateVariables(templateDir);
|
|
||||||
currentVars = Array.from(allVars);
|
|
||||||
setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(currentVars));
|
|
||||||
} else if (message.type === 'setLanguage') {
|
} else if (message.type === 'setLanguage') {
|
||||||
if (message.language) language = message.language;
|
if (message.language) language = message.language;
|
||||||
// Сохраняем язык в конфиг
|
|
||||||
const oldConfig = readConfig();
|
const oldConfig = readConfig();
|
||||||
await writeConfig({ ...oldConfig, language });
|
await writeConfig({ ...oldConfig, language });
|
||||||
currentTemplate = message.template || templates[0] || '';
|
currentTemplate = message.template || templateNames[0] || '';
|
||||||
// Получаем переменные для выбранного шаблона
|
currentVars = getVarsForTemplate(currentTemplate);
|
||||||
let baseVars: string[] = [];
|
setHtml(getTemplatesRadioHtml(templateNames, currentTemplate), getVarsHtml(currentVars));
|
||||||
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, не нужен
|
|
||||||
} else if (message.type === 'submit') {
|
} else if (message.type === 'submit') {
|
||||||
if (message.language) language = message.language;
|
if (message.language) language = message.language;
|
||||||
if (!disposed) {
|
if (!disposed) {
|
||||||
@@ -176,8 +158,6 @@ export async function showTemplateAndVarsWebview(
|
|||||||
panel.dispose();
|
panel.dispose();
|
||||||
resolve({ template: message.template, vars: message.data });
|
resolve({ template: message.template, vars: message.data });
|
||||||
}
|
}
|
||||||
} else if (message.type === 'callInitHandlers') {
|
|
||||||
// Ничего не делаем, скрипт внутри webview вызовет window.initHandlers
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@@ -190,4 +170,4 @@ export async function showTemplateAndVarsWebview(
|
|||||||
}
|
}
|
||||||
}, null, context.subscriptions);
|
}, null, context.subscriptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ const extensionConfig = {
|
|||||||
libraryTarget: 'commonjs2'
|
libraryTarget: 'commonjs2'
|
||||||
},
|
},
|
||||||
externals: {
|
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/
|
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
|
|
||||||
// modules added here also need to be added in the .vscodeignore file
|
// modules added here also need to be added in the .vscodeignore file
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
Reference in New Issue
Block a user