Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot] 9e45de42c1 Auto-build: opencode (powershell) from cbde49e 2026-06-24 11:47:02 +00:00
2586 changed files with 61904 additions and 236030 deletions
-32
View File
@@ -1,32 +0,0 @@
{
"name": "cc-1c-skills",
"interface": {
"displayName": "1C Skills"
},
"plugins": [
{
"name": "1c-skills",
"source": {
"source": "url",
"url": "https://github.com/Nikolay-Shirokov/cc-1c-skills.git",
"ref": "port-codex"
},
"policy": {
"installation": "AVAILABLE"
},
"category": "Development"
},
{
"name": "1c-skills-py",
"source": {
"source": "url",
"url": "https://github.com/Nikolay-Shirokov/cc-1c-skills.git",
"ref": "port-codex-py"
},
"policy": {
"installation": "AVAILABLE"
},
"category": "Development"
}
]
}
-24
View File
@@ -1,24 +0,0 @@
{
"$schema": "https://json.schemastore.org/claude-code-marketplace-manifest.json",
"name": "cc-1c-skills",
"description": "Маркетплейс навыков для разработки на платформе 1С:Предприятие",
"owner": {
"name": "Nikolay Shirokov"
},
"plugins": [
{
"name": "1c-skills",
"source": "./",
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент."
},
{
"name": "1c-skills-py",
"source": {
"source": "github",
"repo": "Nikolay-Shirokov/cc-1c-skills",
"ref": "port-claude-code-py"
},
"description": "[Python] То же — для Linux/Mac или когда PowerShell недоступен."
}
]
}
-31
View File
@@ -1,31 +0,0 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "1c-skills",
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
"author": {
"name": "Nikolay Shirokov"
},
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"license": "MIT",
"keywords": [
"1c",
"1c-dev",
"cf",
"cfe",
"epf",
"erf",
"metadata",
"configuration",
"extension",
"form",
"report",
"skd",
"data-processor",
"mxl",
"web-client",
"testing",
"test-automation"
],
"skills": "./.claude/skills/"
}
-27
View File
@@ -1,27 +0,0 @@
# 1C Skills for {{PLATFORM_LABEL}} ({{RUNTIME_LABEL}})
Автоматическая сборка из [main]({{MAIN_REPO_URL}}) — навыки 1С:Предприятие 8.3 для AI-агента **{{PLATFORM_LABEL}}** с рантаймом **{{RUNTIME_LABEL}}**.
> Эта ветка генерируется CI на каждый push в main. **Не редактируйте напрямую** — все правки идут в [main]({{MAIN_REPO_URL}}).
## Установка
1. Скачайте ZIP этой ветки: **Code → Download ZIP** (или `git archive`).
2. Распакуйте в корень своего проекта — должна появиться папка `{{PLATFORM_DIR}}/`.
3. Запустите {{PLATFORM_LABEL}} из этого проекта — навыки станут доступны.
## Требования
- **Windows** с PowerShell 5.1+ (входит в Windows) — для PowerShell-сборки.
- **Python 3.10+** — для Python-сборки. Зависимости: `lxml>=4.9.0`, `psutil>=5.9.0` (для DOM- и web-навыков).
- **1С:Предприятие 8.3** — для сборки/разборки EPF/ERF и работы с базами.
- **Node.js 18+** — для `/web-test`.
## Документация
Полные гайды, спецификации и описание навыков — в [main]({{MAIN_REPO_URL}}).
---
Source: {{MAIN_REPO_URL}}
Build commit: `{{COMMIT_SHA}}`
-31
View File
@@ -1,31 +0,0 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "{{PLUGIN_NAME}}",
"description": "[Python] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент. Linux/Mac или когда PowerShell недоступен.",
"author": {
"name": "Nikolay Shirokov"
},
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"license": "MIT",
"keywords": [
"1c",
"1c-dev",
"cf",
"cfe",
"epf",
"erf",
"metadata",
"configuration",
"extension",
"form",
"report",
"skd",
"data-processor",
"mxl",
"web-client",
"testing",
"test-automation"
],
"skills": "./.claude/skills/"
}
-36
View File
@@ -1,36 +0,0 @@
{
"name": "{{PLUGIN_NAME}}",
"version": "{{VERSION}}",
"description": "[{{RUNTIME_LABEL}}] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
"author": {
"name": "Nikolay Shirokov"
},
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"license": "MIT",
"keywords": [
"1c",
"1c-dev",
"cf",
"cfe",
"epf",
"erf",
"metadata",
"configuration",
"extension",
"form",
"report",
"skd",
"data-processor",
"mxl",
"web-client",
"testing",
"test-automation"
],
"skills": "./.codex/skills/",
"interface": {
"displayName": "1C Skills ({{RUNTIME_LABEL}})",
"shortDescription": "{{SHORT_DESCRIPTION}}",
"category": "Development"
}
}
-234
View File
@@ -1,234 +0,0 @@
name: Build port branches
on:
push:
branches: [main]
paths:
- '.claude/skills/**'
- 'scripts/switch.py'
- '.github/templates/README.port.md.tmpl'
- '.github/templates/codex-plugin.json.tmpl'
- '.github/templates/claude-plugin.json.tmpl'
- '.github/workflows/build-ports.yml'
- 'LICENSE'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- platform: claude-code
runtime: python
branch: port-claude-code-py
label: Claude Code
target_dir: .claude/skills
- platform: cursor
runtime: powershell
branch: port-cursor
label: Cursor
target_dir: .cursor/skills
- platform: cursor
runtime: python
branch: port-cursor-py
label: Cursor
target_dir: .cursor/skills
- platform: codex
runtime: powershell
branch: port-codex
label: Codex
target_dir: .codex/skills
- platform: codex
runtime: python
branch: port-codex-py
label: Codex
target_dir: .codex/skills
- platform: copilot
runtime: powershell
branch: port-copilot
label: GitHub Copilot
target_dir: .github/skills
- platform: copilot
runtime: python
branch: port-copilot-py
label: GitHub Copilot
target_dir: .github/skills
- platform: augment
runtime: powershell
branch: port-augment
label: Augment
target_dir: .augment/skills
- platform: augment
runtime: python
branch: port-augment-py
label: Augment
target_dir: .augment/skills
- platform: cline
runtime: powershell
branch: port-cline
label: Cline
target_dir: .cline/skills
- platform: cline
runtime: python
branch: port-cline-py
label: Cline
target_dir: .cline/skills
- platform: kilo
runtime: powershell
branch: port-kilo
label: Kilo Code
target_dir: .kilocode/skills
- platform: kilo
runtime: python
branch: port-kilo-py
label: Kilo Code
target_dir: .kilocode/skills
- platform: kiro
runtime: powershell
branch: port-kiro
label: Kiro
target_dir: .kiro/skills
- platform: kiro
runtime: python
branch: port-kiro-py
label: Kiro
target_dir: .kiro/skills
- platform: gemini
runtime: powershell
branch: port-gemini
label: Gemini CLI
target_dir: .gemini/skills
- platform: gemini
runtime: python
branch: port-gemini-py
label: Gemini CLI
target_dir: .gemini/skills
- platform: opencode
runtime: powershell
branch: port-opencode
label: OpenCode
target_dir: .opencode/skills
- platform: opencode
runtime: python
branch: port-opencode-py
label: OpenCode
target_dir: .opencode/skills
- platform: roo
runtime: powershell
branch: port-roo
label: Roo Code
target_dir: .roo/skills
- platform: roo
runtime: python
branch: port-roo-py
label: Roo Code
target_dir: .roo/skills
- platform: windsurf
runtime: powershell
branch: port-windsurf
label: Windsurf
target_dir: .windsurf/skills
- platform: windsurf
runtime: python
branch: port-windsurf-py
label: Windsurf
target_dir: .windsurf/skills
- platform: codeassistant
runtime: powershell
branch: port-codeassistant
label: Yandex Code Assistant
target_dir: .codeassistant/skills
- platform: codeassistant
runtime: python
branch: port-codeassistant-py
label: Yandex Code Assistant
target_dir: .codeassistant/skills
- platform: agents
runtime: powershell
branch: port-agents
label: Agent Skills
target_dir: .agents/skills
- platform: agents
runtime: python
branch: port-agents-py
label: Agent Skills
target_dir: .agents/skills
steps:
- name: Checkout main
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Build skills tree for ${{ matrix.platform }} (${{ matrix.runtime }})
run: |
python scripts/switch.py "${{ matrix.platform }}" \
--project-dir build \
--runtime "${{ matrix.runtime }}"
- name: Render port README
env:
PLATFORM_LABEL: ${{ matrix.label }}
PLATFORM_DIR: ${{ matrix.target_dir }}
RUNTIME_LABEL: ${{ matrix.runtime == 'powershell' && 'PowerShell' || 'Python' }}
COMMIT_SHA: ${{ github.sha }}
MAIN_REPO_URL: https://github.com/${{ github.repository }}
run: |
sed \
-e "s|{{PLATFORM_LABEL}}|${PLATFORM_LABEL}|g" \
-e "s|{{PLATFORM_DIR}}|${PLATFORM_DIR}|g" \
-e "s|{{RUNTIME_LABEL}}|${RUNTIME_LABEL}|g" \
-e "s|{{COMMIT_SHA}}|${COMMIT_SHA}|g" \
-e "s|{{MAIN_REPO_URL}}|${MAIN_REPO_URL}|g" \
.github/templates/README.port.md.tmpl > build/README.md
- name: Render Codex plugin manifest
if: matrix.platform == 'codex'
env:
PLUGIN_NAME: ${{ matrix.runtime == 'python' && '1c-skills-py' || '1c-skills' }}
RUNTIME_LABEL: ${{ matrix.runtime == 'powershell' && 'PowerShell' || 'Python' }}
SHORT_DESCRIPTION: ${{ matrix.runtime == 'python' && 'Python runtime (Linux/Mac/Windows)' || 'PowerShell runtime (Windows-first)' }}
COMMIT_SHA: ${{ github.sha }}
run: |
VERSION="$(date -u +%Y.%-m.%-d)+${COMMIT_SHA::7}"
mkdir -p build/.codex-plugin
sed \
-e "s|{{PLUGIN_NAME}}|${PLUGIN_NAME}|g" \
-e "s|{{VERSION}}|${VERSION}|g" \
-e "s|{{RUNTIME_LABEL}}|${RUNTIME_LABEL}|g" \
-e "s|{{SHORT_DESCRIPTION}}|${SHORT_DESCRIPTION}|g" \
.github/templates/codex-plugin.json.tmpl > build/.codex-plugin/plugin.json
- name: Render Claude plugin manifest (Py variant)
if: matrix.platform == 'claude-code' && matrix.runtime == 'python'
env:
PLUGIN_NAME: 1c-skills-py
run: |
mkdir -p build/.claude-plugin
sed -e "s|{{PLUGIN_NAME}}|${PLUGIN_NAME}|g" \
.github/templates/claude-plugin.json.tmpl > build/.claude-plugin/plugin.json
- name: Copy LICENSE
run: cp LICENSE build/LICENSE
- name: Force-push orphan snapshot to ${{ matrix.branch }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd build
git init -q -b master
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -q -m "Auto-build: ${{ matrix.platform }} (${{ matrix.runtime }}) from ${GITHUB_SHA::7}"
git push --force \
"https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" \
"master:${{ matrix.branch }}"
-52
View File
@@ -1,52 +0,0 @@
# Реальные выгрузки обработок (примеры, не для версионирования)
upload/
# Результаты сборки
build/
base/
*.epf
*.log
# Временные файлы тестов
test-tmp/
# Локальные настройки Claude Code
.claude/settings.local.json
# Инструменты (portable Apache и т.д.)
tools/
# Отладка навыков (eval, trigger-test, run_loop результаты)
debug/
# Кэш тестов навыков
tests/skills/.cache/
# Python кэш
__pycache__/
# Локальный реестр баз данных 1С
.v8-project.json
# web-test: Node.js зависимости и runtime-артефакты
.claude/skills/web-test/scripts/node_modules/
.claude/skills/web-test/.browser-session.json
# Скриншоты и видео (артефакты тестирования web-test)
*.png
*.mp4
# Навыки, скопированные для других AI-платформ (генерируются scripts/switch.py)
.agents/skills/
.augment/
.cline/
.codex/
.cursor/
.gemini/
.github/skills/
.kilocode/
.kiro/
.opencode/
.roo/
.windsurf/
debug-templates.txt
@@ -1 +1 @@
__pycache__/ __pycache__/
@@ -1,60 +1,60 @@
--- ---
name: cf-edit name: cf-edit
description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию, поменять раскладку панелей, настроить начальную страницу description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию, поменять раскладку панелей, настроить начальную страницу
argument-hint: -ConfigPath <path> -Operation <op> -Value <value> argument-hint: -ConfigPath <path> -Operation <op> -Value <value>
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Write - Write
- Glob - Glob
--- ---
# /cf-edit — редактирование конфигурации 1С # /cf-edit — редактирование конфигурации 1С
Точечное редактирование Configuration.xml: свойства, состав ChildObjects, роли по умолчанию. Точечное редактирование Configuration.xml: свойства, состав ChildObjects, роли по умолчанию.
## Параметры и команда ## Параметры и команда
| Параметр | Описание | | Параметр | Описание |
|----------|----------| |----------|----------|
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки | | `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
| `Operation` | Операция (см. таблицу) | | `Operation` | Операция (см. таблицу) |
| `Value` | Значение для операции (batch через `;;`) | | `Value` | Значение для операции (batch через `;;`) |
| `DefinitionFile` | JSON-файл с массивом операций | | `DefinitionFile` | JSON-файл с массивом операций |
| `NoValidate` | Пропустить авто-валидацию | | `NoValidate` | Пропустить авто-валидацию |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-edit.ps1" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1' powershell.exe -NoProfile -File ".opencode/skills/cf-edit/scripts/cf-edit.ps1" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
``` ```
## Операции ## Операции
| Операция | Формат Value | Описание | | Операция | Формат Value | Описание |
|----------|-------------|----------| |----------|-------------|----------|
| `modify-property` | `Ключ=Значение` (batch `;;`) | Изменить свойство | | `modify-property` | `Ключ=Значение` (batch `;;`) | Изменить свойство |
| `add-childObject` | `Type.Name` (batch `;;`) | Зарегистрировать уже существующий файл объекта в ChildObjects. Для создания нового объекта используй `/meta-compile`, `/role-compile`, `/subsystem-compile` — они регистрируют автоматически | | `add-childObject` | `Type.Name` (batch `;;`) | Зарегистрировать уже существующий файл объекта в ChildObjects. Для создания нового объекта используй `/meta-compile`, `/role-compile`, `/subsystem-compile` — они регистрируют автоматически |
| `remove-childObject` | `Type.Name` (batch `;;`) | Удалить объект из ChildObjects | | `remove-childObject` | `Type.Name` (batch `;;`) | Удалить объект из ChildObjects |
| `add-defaultRole` | `Role.Name` или `Name` | Добавить роль по умолчанию | | `add-defaultRole` | `Role.Name` или `Name` | Добавить роль по умолчанию |
| `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию | | `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию |
| `set-defaultRoles` | Имена через `;;` | Заменить список ролей по умолчанию | | `set-defaultRoles` | Имена через `;;` | Заменить список ролей по умолчанию |
| `set-panels` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/ClientApplicationInterface.xml` (раскладка панелей) | | `set-panels` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/ClientApplicationInterface.xml` (раскладка панелей) |
| `set-home-page` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/HomePageWorkArea.xml` (начальная страница) | | `set-home-page` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/HomePageWorkArea.xml` (начальная страница) |
Допустимые значения свойств, формат DefinitionFile (JSON), каноничный порядок: [reference.md](reference.md) Допустимые значения свойств, формат DefinitionFile (JSON), каноничный порядок: [reference.md](reference.md)
## Примеры ## Примеры
```powershell ```powershell
# Изменить версию и поставщика # Изменить версию и поставщика
... -ConfigPath src -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С" ... -ConfigPath src -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С"
# Добавить объекты # Добавить объекты
... -ConfigPath src -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ" ... -ConfigPath src -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ"
# Удалить объект # Удалить объект
... -ConfigPath src -Operation remove-childObject -Value "Catalog.Устаревший" ... -ConfigPath src -Operation remove-childObject -Value "Catalog.Устаревший"
# Роли по умолчанию # Роли по умолчанию
... -ConfigPath src -Operation add-defaultRole -Value "ПолныеПрава" ... -ConfigPath src -Operation add-defaultRole -Value "ПолныеПрава"
... -ConfigPath src -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор" ... -ConfigPath src -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор"
``` ```
@@ -1,150 +1,150 @@
# cf-edit — справочник операций # cf-edit — справочник операций
## modify-property ## modify-property
Свойства для редактирования: Свойства для редактирования:
### Скалярные ### Скалярные
`Name`, `Version`, `Vendor`, `Comment`, `NamePrefix`, `UpdateCatalogAddress` `Name`, `Version`, `Vendor`, `Comment`, `NamePrefix`, `UpdateCatalogAddress`
### LocalString (многоязычные) ### LocalString (многоязычные)
`Synonym`, `BriefInformation`, `DetailedInformation`, `Copyright`, `VendorInformationAddress`, `ConfigurationInformationAddress` `Synonym`, `BriefInformation`, `DetailedInformation`, `Copyright`, `VendorInformationAddress`, `ConfigurationInformationAddress`
### Enum ### Enum
| Свойство | Допустимые значения | | Свойство | Допустимые значения |
|----------|---------------------| |----------|---------------------|
| `CompatibilityMode` | `Version8_3_20` ... `Version8_3_28`, `Version8_5_1`, `DontUse` | | `CompatibilityMode` | `Version8_3_20` ... `Version8_3_28`, `Version8_5_1`, `DontUse` |
| `ConfigurationExtensionCompatibilityMode` | то же | | `ConfigurationExtensionCompatibilityMode` | то же |
| `DefaultRunMode` | `ManagedApplication`, `OrdinaryApplication`, `Auto` | | `DefaultRunMode` | `ManagedApplication`, `OrdinaryApplication`, `Auto` |
| `ScriptVariant` | `Russian`, `English` | | `ScriptVariant` | `Russian`, `English` |
| `DataLockControlMode` | `Managed`, `Automatic`, `AutomaticAndManaged` | | `DataLockControlMode` | `Managed`, `Automatic`, `AutomaticAndManaged` |
| `ObjectAutonumerationMode` | `NotAutoFree`, `AutoFree` | | `ObjectAutonumerationMode` | `NotAutoFree`, `AutoFree` |
| `ModalityUseMode` | `DontUse`, `Use`, `UseWithWarnings` | | `ModalityUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
| `SynchronousPlatformExtensionAndAddInCallUseMode` | `DontUse`, `Use`, `UseWithWarnings` | | `SynchronousPlatformExtensionAndAddInCallUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
| `InterfaceCompatibilityMode` | `Version8_2`, `Version8_2EnableTaxi`, `Taxi`, `TaxiEnableVersion8_2`, `TaxiEnableVersion8_5`, `Version8_5EnableTaxi`, `Version8_5` | | `InterfaceCompatibilityMode` | `Version8_2`, `Version8_2EnableTaxi`, `Taxi`, `TaxiEnableVersion8_2`, `TaxiEnableVersion8_5`, `Version8_5EnableTaxi`, `Version8_5` |
| `DatabaseTablespacesUseMode` | `DontUse`, `Use` | | `DatabaseTablespacesUseMode` | `DontUse`, `Use` |
| `MainClientApplicationWindowMode` | `Normal`, `Fullscreen`, `Kiosk` | | `MainClientApplicationWindowMode` | `Normal`, `Fullscreen`, `Kiosk` |
### Ref ### Ref
`DefaultLanguage` — значение вида `Language.Русский` `DefaultLanguage` — значение вида `Language.Русский`
### Формат batch ### Формат batch
`"Version=1.0.0.1 ;; Vendor=Фирма 1С ;; Synonym=Тестовая конфигурация"` `"Version=1.0.0.1 ;; Vendor=Фирма 1С ;; Synonym=Тестовая конфигурация"`
## add-childObject / remove-childObject ## add-childObject / remove-childObject
Формат: `Type.Name` — XML-тип и имя объекта через точку. Формат: `Type.Name` — XML-тип и имя объекта через точку.
**Важно про `add-childObject`**: регистрирует в `<ChildObjects>` объект, **файл которого уже существует на диске**. Если файла нет — exit 1. Для создания нового объекта используй профильный навык — `/meta-compile` (Catalog, Document, Enum, Report, регистры и т.д.), `/role-compile` (Role), `/subsystem-compile` (Subsystem). Они создают файл И регистрируют его за один вызов. **Важно про `add-childObject`**: регистрирует в `<ChildObjects>` объект, **файл которого уже существует на диске**. Если файла нет — exit 1. Для создания нового объекта используй профильный навык — `/meta-compile` (Catalog, Document, Enum, Report, регистры и т.д.), `/role-compile` (Role), `/subsystem-compile` (Subsystem). Они создают файл И регистрируют его за один вызов.
Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"` Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"`
## add-defaultRole / remove-defaultRole / set-defaultRoles ## add-defaultRole / remove-defaultRole / set-defaultRoles
Имя роли: `ПолныеПрава` или `Role.ПолныеПрава` (префикс `Role.` добавляется автоматически). Имя роли: `ПолныеПрава` или `Role.ПолныеПрава` (префикс `Role.` добавляется автоматически).
`set-defaultRoles` полностью заменяет список ролей. `set-defaultRoles` полностью заменяет список ролей.
## set-panels ## set-panels
Перезаписывает `Ext/ClientApplicationInterface.xml` — раскладку панелей рабочего пространства Taxi. Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует на экране. Перезаписывает `Ext/ClientApplicationInterface.xml` — раскладку панелей рабочего пространства Taxi. Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует на экране.
`value` — объект с ключами `top`, `left`, `right`, `bottom`. Каждый ключ — массив записей. Ключ можно опустить (= пустая сторона). `value` — объект с ключами `top`, `left`, `right`, `bottom`. Каждый ключ — массив записей. Ключ можно опустить (= пустая сторона).
**Запись** — одна из: **Запись** — одна из:
- Строка-алиас (одна панель в этом слоте) - Строка-алиас (одна панель в этом слоте)
- Объект `{"group": [...]}` (стек: панели/подгруппы внутри располагаются друг под другом) - Объект `{"group": [...]}` (стек: панели/подгруппы внутри располагаются друг под другом)
**Алиасы панелей:** **Алиасы панелей:**
| Алиас | Панель | | Алиас | Панель |
|-------|--------| |-------|--------|
| `sections` | Панель разделов | | `sections` | Панель разделов |
| `open` | Панель открытых | | `open` | Панель открытых |
| `favorites` | Панель избранного | | `favorites` | Панель избранного |
| `history` | Панель истории | | `history` | Панель истории |
| `functions` | Панель функций текущего раздела | | `functions` | Панель функций текущего раздела |
**Семантика:** **Семантика:**
- Несколько записей в одной стороне → отдельные слоты «рядом» (несколько тегов `<top>`/...) - Несколько записей в одной стороне → отдельные слоты «рядом» (несколько тегов `<top>`/...)
- `{"group":[...]}` → один тег с `<group>`-обёрткой, элементы внутри идут стеком - `{"group":[...]}` → один тег с `<group>`-обёрткой, элементы внутри идут стеком
**Пример** (DefinitionFile): **Пример** (DefinitionFile):
```json ```json
[ [
{ {
"operation": "set-panels", "operation": "set-panels",
"value": { "value": {
"top": ["open"], "top": ["open"],
"left": ["sections"], "left": ["sections"],
"right": [{ "group": ["favorites", "history"] }], "right": [{ "group": ["favorites", "history"] }],
"bottom": ["functions"] "bottom": ["functions"]
} }
} }
] ]
``` ```
Через `-Value` (CLI): передай объект как JSON-строку — `... -Operation set-panels -Value '{"top":["open"]}'`. Через `-Value` (CLI): передай объект как JSON-строку — `... -Operation set-panels -Value '{"top":["open"]}'`.
## set-home-page ## set-home-page
Перезаписывает `Ext/HomePageWorkArea.xml` — раскладка форм на начальной странице (рабочая область). Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует. Перезаписывает `Ext/HomePageWorkArea.xml` — раскладка форм на начальной странице (рабочая область). Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует.
`value` — объект: `value` — объект:
| Ключ | Канонич. (XML) | Описание | | Ключ | Канонич. (XML) | Описание |
|------|----------------|----------| |------|----------------|----------|
| `template` | `WorkingAreaTemplate` | `OneColumn` / `TwoColumnsEqualWidth` (дефолт) / `TwoColumnsVariableWidth` | | `template` | `WorkingAreaTemplate` | `OneColumn` / `TwoColumnsEqualWidth` (дефолт) / `TwoColumnsVariableWidth` |
| `left` | `LeftColumn` | массив записей форм | | `left` | `LeftColumn` | массив записей форм |
| `right` | `RightColumn` | массив записей форм (запрещён при `OneColumn`) | | `right` | `RightColumn` | массив записей форм (запрещён при `OneColumn`) |
Принимаются и короткие и канонич. ключи (XML-имена) — оба работают. Принимаются и короткие и канонич. ключи (XML-имена) — оба работают.
**Запись формы** — одна из: **Запись формы** — одна из:
- Строка `"<form>"` — только имя формы, дефолты `height=10`, `visibility=true` - Строка `"<form>"` — только имя формы, дефолты `height=10`, `visibility=true`
- Объект `{form, height?, visibility?, roles?}` - Объект `{form, height?, visibility?, roles?}`
| Поле | Канонич. | Дефолт | Описание | | Поле | Канонич. | Дефолт | Описание |
|------|----------|--------|----------| |------|----------|--------|----------|
| `form` | `Form` | — | `CommonForm.X` или `Type.Object.Form.Name` (или UUID) | | `form` | `Form` | — | `CommonForm.X` или `Type.Object.Form.Name` (или UUID) |
| `height` | `Height` | `10` | Высота | | `height` | `Height` | `10` | Высота |
| `visibility` | `Visibility` | `true` | Общая видимость (`<xr:Common>`) | | `visibility` | `Visibility` | `true` | Общая видимость (`<xr:Common>`) |
| `roles` | — | — | `{"Role.Имя": true|false, ...}` — переопределения по ролям | | `roles` | — | — | `{"Role.Имя": true|false, ...}` — переопределения по ролям |
**Семантика visibility:** `visibility` = общее правило, `roles` — точечные исключения. Скрыть для всех кроме одной роли: `{"visibility": false, "roles": {"Role.Опер": true}}`. **Семантика visibility:** `visibility` = общее правило, `roles` — точечные исключения. Скрыть для всех кроме одной роли: `{"visibility": false, "roles": {"Role.Опер": true}}`.
**Пример:** **Пример:**
```json ```json
[ [
{ {
"operation": "set-home-page", "operation": "set-home-page",
"value": { "value": {
"template": "TwoColumnsVariableWidth", "template": "TwoColumnsVariableWidth",
"left": [ "left": [
"CommonForm.НачалоРаботы", "CommonForm.НачалоРаботы",
{ "form": "CommonForm.СписокЗадач", "height": 100, "visibility": false }, { "form": "CommonForm.СписокЗадач", "height": 100, "visibility": false },
{ "form": "Catalog.Контрагенты.Form.ФормаСписка", "height": 50 }, { "form": "Catalog.Контрагенты.Form.ФормаСписка", "height": 50 },
{ {
"form": "CommonForm.РабочийСтолОператора", "form": "CommonForm.РабочийСтолОператора",
"visibility": false, "visibility": false,
"roles": { "Role.Оператор": true, "Role.ПолныеПрава": false } "roles": { "Role.Оператор": true, "Role.ПолныеПрава": false }
} }
], ],
"right": [ "right": [
{ "form": "DataProcessor.Поиск.Form.ФормаПоиска", "height": 30 } { "form": "DataProcessor.Поиск.Form.ФормаПоиска", "height": 30 }
] ]
} }
} }
] ]
``` ```
## DefinitionFile (JSON) ## DefinitionFile (JSON)
```json ```json
[ [
{ "operation": "modify-property", "value": "Version=2.0.0.1 ;; Vendor=Test" }, { "operation": "modify-property", "value": "Version=2.0.0.1 ;; Vendor=Test" },
{ "operation": "add-childObject", "value": "Catalog.Товары ;; Document.Заказ" }, { "operation": "add-childObject", "value": "Catalog.Товары ;; Document.Заказ" },
{ "operation": "add-defaultRole", "value": "ПолныеПрава" } { "operation": "add-defaultRole", "value": "ПолныеПрава" }
] ]
``` ```
@@ -1,54 +1,54 @@
--- ---
name: cf-info name: cf-info
description: Анализ структуры конфигурации 1С — свойства, состав, счётчики объектов. Используй для обзора конфигурации — какие объекты есть, сколько их, какие настройки description: Анализ структуры конфигурации 1С — свойства, состав, счётчики объектов. Используй для обзора конфигурации — какие объекты есть, сколько их, какие настройки
argument-hint: <ConfigPath> [-Mode overview|brief|full] [-Section home-page] argument-hint: <ConfigPath> [-Mode overview|brief|full] [-Section home-page]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cf-info — Структура конфигурации 1С # /cf-info — Структура конфигурации 1С
Читает Configuration.xml из выгрузки конфигурации и выводит компактное описание структуры. Читает Configuration.xml из выгрузки конфигурации и выводит компактное описание структуры.
## Параметры и команда ## Параметры и команда
| Параметр | Описание | | Параметр | Описание |
|----------|----------| |----------|----------|
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки | | `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
| `Mode` | Режим: `overview` (default), `brief`, `full` | | `Mode` | Режим: `overview` (default), `brief`, `full` |
| `Section` | Drill-down по разделу (alias: `Name`). Сейчас: `home-page` | | `Section` | Drill-down по разделу (alias: `Name`). Сейчас: `home-page` |
| `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) | | `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) |
| `OutFile` | Записать результат в файл (UTF-8 BOM) | | `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-info.ps1" -ConfigPath "<путь>" powershell.exe -NoProfile -File ".opencode/skills/cf-info/scripts/cf-info.ps1" -ConfigPath "<путь>"
``` ```
## Три режима ## Три режима
| Режим | Что показывает | | Режим | Что показывает |
|---|---| |---|---|
| `overview` *(default)* | Заголовок + ключевые свойства + таблица счётчиков объектов по типам | | `overview` *(default)* | Заголовок + ключевые свойства + таблица счётчиков объектов по типам |
| `brief` | Одна строка: Имя — "Синоним" vВерсия \| N объектов \| совместимость | | `brief` | Одна строка: Имя — "Синоним" vВерсия \| N объектов \| совместимость |
| `full` | Все свойства по категориям + полный список ChildObjects + DefaultRoles + мобильные функциональности | | `full` | Все свойства по категориям + полный список ChildObjects + DefaultRoles + мобильные функциональности |
## Примеры ## Примеры
```powershell ```powershell
# Обзор пустой конфигурации # Обзор пустой конфигурации
... -ConfigPath src ... -ConfigPath src
# Краткая сводка реальной конфигурации # Краткая сводка реальной конфигурации
... -ConfigPath src -Mode brief ... -ConfigPath src -Mode brief
# Полная информация # Полная информация
... -ConfigPath src -Mode full ... -ConfigPath src -Mode full
# С пагинацией # С пагинацией
... -ConfigPath src -Mode full -Limit 50 -Offset 100 ... -ConfigPath src -Mode full -Limit 50 -Offset 100
# Drill-down: только начальная страница (раскладка форм с ролями) # Drill-down: только начальная страница (раскладка форм с ролями)
... -ConfigPath src -Section home-page ... -ConfigPath src -Section home-page
``` ```
@@ -1,49 +1,49 @@
--- ---
name: cf-init name: cf-init
description: Создать пустую конфигурацию 1С (scaffold XML-исходников). Используй когда нужно начать новую конфигурацию с нуля description: Создать пустую конфигурацию 1С (scaffold XML-исходников). Используй когда нужно начать новую конфигурацию с нуля
argument-hint: <Name> [-Synonym <name>] [-OutputDir src] argument-hint: <Name> [-Synonym <name>] [-OutputDir src]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cf-init — Создание пустой конфигурации 1С # /cf-init — Создание пустой конфигурации 1С
Создаёт scaffold исходников пустой конфигурации 1С: `Configuration.xml`, `Languages/Русский.xml`. Создаёт scaffold исходников пустой конфигурации 1С: `Configuration.xml`, `Languages/Русский.xml`.
## Параметры и команда ## Параметры и команда
| Параметр | Описание | | Параметр | Описание |
|----------|----------| |----------|----------|
| `Name` | Имя конфигурации (обязат.) | | `Name` | Имя конфигурации (обязат.) |
| `Synonym` | Синоним (= Name если не указан) | | `Synonym` | Синоним (= Name если не указан) |
| `OutputDir` | Каталог для создания (default: `src`) | | `OutputDir` | Каталог для создания (default: `src`) |
| `Version` | Версия конфигурации | | `Version` | Версия конфигурации |
| `Vendor` | Поставщик | | `Vendor` | Поставщик |
| `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) | | `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-init.ps1" -Name "МояКонфигурация" powershell.exe -NoProfile -File ".opencode/skills/cf-init/scripts/cf-init.ps1" -Name "МояКонфигурация"
``` ```
## Примеры ## Примеры
```powershell ```powershell
# Базовая конфигурация # Базовая конфигурация
... -Name МояКонфигурация -Synonym "Моя конфигурация" -OutputDir test-tmp/cf ... -Name МояКонфигурация -Synonym "Моя конфигурация" -OutputDir test-tmp/cf
# С версией и поставщиком # С версией и поставщиком
... -Name TestCfg -Synonym "Тестовая" -Version "1.0.0.1" -Vendor "Фирма 1С" -OutputDir test-tmp/cf2 ... -Name TestCfg -Synonym "Тестовая" -Version "1.0.0.1" -Vendor "Фирма 1С" -OutputDir test-tmp/cf2
# Другой режим совместимости # Другой режим совместимости
... -Name TestCfg -CompatibilityMode Version8_3_27 -OutputDir test-tmp/cf3 ... -Name TestCfg -CompatibilityMode Version8_3_27 -OutputDir test-tmp/cf3
``` ```
## Верификация ## Верификация
``` ```
/cf-init TestConfig -OutputDir test-tmp/cf /cf-init TestConfig -OutputDir test-tmp/cf
/cf-info test-tmp/cf — проверить созданное /cf-info test-tmp/cf — проверить созданное
/cf-validate test-tmp/cf — валидировать /cf-validate test-tmp/cf — валидировать
``` ```
@@ -1,249 +1,249 @@
# cf-init v1.2 — Create empty 1C configuration scaffold # cf-init v1.2 — Create empty 1C configuration scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$Name, [string]$Name,
[string]$Synonym = $Name, [string]$Synonym = $Name,
[string]$OutputDir = "src", [string]$OutputDir = "src",
[string]$Version, [string]$Version,
[string]$Vendor, [string]$Vendor,
[string]$CompatibilityMode = "Version8_3_24" [string]$CompatibilityMode = "Version8_3_24"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve output dir --- # --- Resolve output dir ---
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) { if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
$OutputDir = Join-Path (Get-Location).Path $OutputDir $OutputDir = Join-Path (Get-Location).Path $OutputDir
} }
# --- Check existing --- # --- Check existing ---
$cfgFile = Join-Path $OutputDir "Configuration.xml" $cfgFile = Join-Path $OutputDir "Configuration.xml"
if (Test-Path $cfgFile) { if (Test-Path $cfgFile) {
Write-Error "Configuration.xml already exists: $cfgFile" Write-Error "Configuration.xml already exists: $cfgFile"
exit 1 exit 1
} }
# --- Generate UUIDs --- # --- Generate UUIDs ---
$uuidCfg = [guid]::NewGuid().ToString() $uuidCfg = [guid]::NewGuid().ToString()
$uuidLang = [guid]::NewGuid().ToString() $uuidLang = [guid]::NewGuid().ToString()
# 7 ContainedObject ObjectIds # 7 ContainedObject ObjectIds
$co1 = [guid]::NewGuid().ToString() $co1 = [guid]::NewGuid().ToString()
$co2 = [guid]::NewGuid().ToString() $co2 = [guid]::NewGuid().ToString()
$co3 = [guid]::NewGuid().ToString() $co3 = [guid]::NewGuid().ToString()
$co4 = [guid]::NewGuid().ToString() $co4 = [guid]::NewGuid().ToString()
$co5 = [guid]::NewGuid().ToString() $co5 = [guid]::NewGuid().ToString()
$co6 = [guid]::NewGuid().ToString() $co6 = [guid]::NewGuid().ToString()
$co7 = [guid]::NewGuid().ToString() $co7 = [guid]::NewGuid().ToString()
# --- Mobile functionalities --- # --- Mobile functionalities ---
$mobileFuncs = @( $mobileFuncs = @(
@("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"), @("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"),
@("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"), @("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"),
@("Calendars","false"), @("PushNotifications","false"), @("LocalNotifications","false"), @("Calendars","false"), @("PushNotifications","false"), @("LocalNotifications","false"),
@("InAppPurchases","false"), @("PersonalComputerFileExchange","false"), @("Ads","false"), @("InAppPurchases","false"), @("PersonalComputerFileExchange","false"), @("Ads","false"),
@("NumberDialing","false"), @("CallProcessing","false"), @("CallLog","false"), @("NumberDialing","false"), @("CallProcessing","false"), @("CallLog","false"),
@("AutoSendSMS","false"), @("ReceiveSMS","false"), @("SMSLog","false"), @("AutoSendSMS","false"), @("ReceiveSMS","false"), @("SMSLog","false"),
@("Camera","false"), @("Microphone","false"), @("MusicLibrary","false"), @("Camera","false"), @("Microphone","false"), @("MusicLibrary","false"),
@("PictureAndVideoLibraries","false"), @("AudioPlaybackAndVibration","false"), @("PictureAndVideoLibraries","false"), @("AudioPlaybackAndVibration","false"),
@("BackgroundAudioPlaybackAndVibration","false"), @("InstallPackages","false"), @("BackgroundAudioPlaybackAndVibration","false"), @("InstallPackages","false"),
@("OSBackup","true"), @("ApplicationUsageStatistics","false"), @("OSBackup","true"), @("ApplicationUsageStatistics","false"),
@("BarcodeScanning","false"), @("BackgroundAudioRecording","false"), @("BarcodeScanning","false"), @("BackgroundAudioRecording","false"),
@("AllFilesAccess","false"), @("Videoconferences","false"), @("NFC","false"), @("AllFilesAccess","false"), @("Videoconferences","false"), @("NFC","false"),
@("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"), @("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"),
@("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false") @("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false")
) )
$mobileXml = "" $mobileXml = ""
foreach ($mf in $mobileFuncs) { foreach ($mf in $mobileFuncs) {
$mobileXml += "`r`n`t`t`t`t<app:functionality>`r`n`t`t`t`t`t<app:functionality>$($mf[0])</app:functionality>`r`n`t`t`t`t`t<app:use>$($mf[1])</app:use>`r`n`t`t`t`t</app:functionality>" $mobileXml += "`r`n`t`t`t`t<app:functionality>`r`n`t`t`t`t`t<app:functionality>$($mf[0])</app:functionality>`r`n`t`t`t`t`t<app:use>$($mf[1])</app:use>`r`n`t`t`t`t</app:functionality>"
} }
# --- Synonym XML --- # --- Synonym XML ---
$synonymXml = "" $synonymXml = ""
if ($Synonym) { if ($Synonym) {
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t" $synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
} }
# --- Optional properties --- # --- Optional properties ---
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" } $vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" } $versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
# --- Configuration.xml --- # --- Configuration.xml ---
$cfgXml = @" $cfgXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="$uuidCfg"> <Configuration uuid="$uuidCfg">
<InternalInfo> <InternalInfo>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId> <xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>$co1</xr:ObjectId> <xr:ObjectId>$co1</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId> <xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>$co2</xr:ObjectId> <xr:ObjectId>$co2</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId> <xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>$co3</xr:ObjectId> <xr:ObjectId>$co3</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId> <xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>$co4</xr:ObjectId> <xr:ObjectId>$co4</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId> <xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>$co5</xr:ObjectId> <xr:ObjectId>$co5</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId> <xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>$co6</xr:ObjectId> <xr:ObjectId>$co6</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId> <xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>$co7</xr:ObjectId> <xr:ObjectId>$co7</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
</InternalInfo> </InternalInfo>
<Properties> <Properties>
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name> <Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
<Synonym>$synonymXml</Synonym> <Synonym>$synonymXml</Synonym>
<Comment/> <Comment/>
<NamePrefix/> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/> <DefaultRoles/>
<Vendor>$vendorXml</Vendor> <Vendor>$vendorXml</Vendor>
<Version>$versionXml</Version> <Version>$versionXml</Version>
<UpdateCatalogAddress/> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication> <UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries/> <AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage/> <CommonSettingsStorage/>
<ReportsUserSettingsStorage/> <ReportsUserSettingsStorage/>
<ReportsVariantsStorage/> <ReportsVariantsStorage/>
<FormDataSettingsStorage/> <FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage/> <DynamicListsUserSettingsStorage/>
<URLExternalDataStorage/> <URLExternalDataStorage/>
<Content/> <Content/>
<DefaultReportForm/> <DefaultReportForm/>
<DefaultReportVariantForm/> <DefaultReportVariantForm/>
<DefaultReportSettingsForm/> <DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate/> <DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm/> <DefaultDynamicListSettingsForm/>
<DefaultSearchForm/> <DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm/> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm/> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm/> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm/> <DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions/> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities>$mobileXml <UsedMobileApplicationFunctionalities>$mobileXml
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles/> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs/> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes/> <AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface/> <DefaultInterface/>
<DefaultStyle/> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/> <BriefInformation/>
<DetailedInformation/> <DetailedInformation/>
<Copyright/> <Copyright/>
<VendorInformationAddress/> <VendorInformationAddress/>
<ConfigurationInformationAddress/> <ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode> <SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>$CompatibilityMode</CompatibilityMode> <CompatibilityMode>$CompatibilityMode</CompatibilityMode>
<DefaultConstantsForm/> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
</ChildObjects> </ChildObjects>
</Configuration> </Configuration>
</MetaDataObject> </MetaDataObject>
"@ "@
# --- Languages/Русский.xml --- # --- Languages/Русский.xml ---
$langXml = @" $langXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="$uuidLang"> <Language uuid="$uuidLang">
<Properties> <Properties>
<Name>Русский</Name> <Name>Русский</Name>
<Synonym> <Synonym>
<v8:item> <v8:item>
<v8:lang>ru</v8:lang> <v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content> <v8:content>Русский</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment/> <Comment/>
<LanguageCode>ru</LanguageCode> <LanguageCode>ru</LanguageCode>
</Properties> </Properties>
</Language> </Language>
</MetaDataObject> </MetaDataObject>
"@ "@
# --- Ext/ClientApplicationInterface.xml (default ERP-style panel layout) --- # --- Ext/ClientApplicationInterface.xml (default ERP-style panel layout) ---
# Open panel on top, Sections panel on left; Functions/Favorites/History declared # Open panel on top, Sections panel on left; Functions/Favorites/History declared
# via panelDef but not placed by default. Without this file the web client renders # via panelDef but not placed by default. Without this file the web client renders
# section icons without labels (icon-only mode). # section icons without labels (icon-only mode).
$openPanelInst = [guid]::NewGuid().ToString() $openPanelInst = [guid]::NewGuid().ToString()
$sectionsPanelInst = [guid]::NewGuid().ToString() $sectionsPanelInst = [guid]::NewGuid().ToString()
$caiXml = @" $caiXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter"> <ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top> <top>
<panel id="$openPanelInst"> <panel id="$openPanelInst">
<uuid>cbab57f2-a0f3-4f0a-89ea-4cb19570ab75</uuid> <uuid>cbab57f2-a0f3-4f0a-89ea-4cb19570ab75</uuid>
</panel> </panel>
</top> </top>
<left> <left>
<panel id="$sectionsPanelInst"> <panel id="$sectionsPanelInst">
<uuid>b553047f-c9aa-4157-978d-448ecad24248</uuid> <uuid>b553047f-c9aa-4157-978d-448ecad24248</uuid>
</panel> </panel>
</left> </left>
<panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/> <panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/>
<panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/> <panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/>
<panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/> <panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/>
<panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/> <panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/>
<panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/> <panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/>
</ClientApplicationInterface> </ClientApplicationInterface>
"@ "@
# --- Create directories --- # --- Create directories ---
if (-not (Test-Path $OutputDir)) { if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
} }
$langDir = Join-Path $OutputDir "Languages" $langDir = Join-Path $OutputDir "Languages"
if (-not (Test-Path $langDir)) { if (-not (Test-Path $langDir)) {
New-Item -ItemType Directory -Path $langDir -Force | Out-Null New-Item -ItemType Directory -Path $langDir -Force | Out-Null
} }
$extDir = Join-Path $OutputDir "Ext" $extDir = Join-Path $OutputDir "Ext"
if (-not (Test-Path $extDir)) { if (-not (Test-Path $extDir)) {
New-Item -ItemType Directory -Path $extDir -Force | Out-Null New-Item -ItemType Directory -Path $extDir -Force | Out-Null
} }
# --- Write files with UTF-8 BOM --- # --- Write files with UTF-8 BOM ---
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc) [System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
$langFile = Join-Path $langDir "Русский.xml" $langFile = Join-Path $langDir "Русский.xml"
[System.IO.File]::WriteAllText($langFile, $langXml, $enc) [System.IO.File]::WriteAllText($langFile, $langXml, $enc)
$caiFile = Join-Path $extDir "ClientApplicationInterface.xml" $caiFile = Join-Path $extDir "ClientApplicationInterface.xml"
[System.IO.File]::WriteAllText($caiFile, $caiXml, $enc) [System.IO.File]::WriteAllText($caiFile, $caiXml, $enc)
# --- Output --- # --- Output ---
Write-Host "[OK] Создана конфигурация: $Name" Write-Host "[OK] Создана конфигурация: $Name"
Write-Host " Каталог: $OutputDir" Write-Host " Каталог: $OutputDir"
Write-Host " Configuration.xml: $cfgFile" Write-Host " Configuration.xml: $cfgFile"
Write-Host " Languages: $langFile" Write-Host " Languages: $langFile"
Write-Host " Ext/CAI: $caiFile" Write-Host " Ext/CAI: $caiFile"
@@ -1,29 +1,29 @@
--- ---
name: cf-validate name: cf-validate
description: Валидация конфигурации 1С. Используй после создания или модификации конфигурации для проверки корректности description: Валидация конфигурации 1С. Используй после создания или модификации конфигурации для проверки корректности
argument-hint: <ConfigPath> [-Detailed] [-MaxErrors 30] argument-hint: <ConfigPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cf-validate — валидация конфигурации 1С # /cf-validate — валидация конфигурации 1С
Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов. Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов.
## Параметры ## Параметры
| Параметр | Обяз. | Умолч. | Описание | | Параметр | Обяз. | Умолч. | Описание |
|------------|:-----:|---------|-------------------------------------------------| |------------|:-----:|---------|-------------------------------------------------|
| ConfigPath | да | — | Путь к Configuration.xml или каталогу выгрузки | | ConfigPath | да | — | Путь к Configuration.xml или каталогу выгрузки |
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) | | Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty" powershell.exe -NoProfile -File ".opencode/skills/cf-validate/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty/Configuration.xml" powershell.exe -NoProfile -File ".opencode/skills/cf-validate/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty/Configuration.xml"
``` ```
@@ -1,101 +1,101 @@
--- ---
name: cfe-borrow name: cfe-borrow
description: Заимствование объектов из конфигурации 1С в расширение (CFE). Используй когда нужно перехватить метод, изменить форму или добавить реквизит к существующему объекту конфигурации description: Заимствование объектов из конфигурации 1С в расширение (CFE). Используй когда нужно перехватить метод, изменить форму или добавить реквизит к существующему объекту конфигурации
argument-hint: -ExtensionPath <path> -ConfigPath <path> -Object "Catalog.Контрагенты.Form.ФормаЭлемента" -BorrowMainAttribute argument-hint: -ExtensionPath <path> -ConfigPath <path> -Object "Catalog.Контрагенты.Form.ФормаЭлемента" -BorrowMainAttribute
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cfe-borrow — Заимствование объектов из конфигурации # /cfe-borrow — Заимствование объектов из конфигурации
Заимствует объекты из основной конфигурации в расширение. Создаёт XML-файлы с `ObjectBelonging=Adopted` и `ExtendedConfigurationObject`, добавляет запись в ChildObjects расширения. Заимствует объекты из основной конфигурации в расширение. Создаёт XML-файлы с `ObjectBelonging=Adopted` и `ExtendedConfigurationObject`, добавляет запись в ChildObjects расширения.
## Предусловие ## Предусловие
Расширение должно быть создано (`/cfe-init`) и содержать валидный `Configuration.xml`. Расширение должно быть создано (`/cfe-init`) и содержать валидный `Configuration.xml`.
### Авто-определение ConfigPath ### Авто-определение ConfigPath
Если пользователь не указал `-ConfigPath` — попробуй определить автоматически: Если пользователь не указал `-ConfigPath` — попробуй определить автоматически:
1. Прочитай `.v8-project.json` из корня проекта 1. Прочитай `.v8-project.json` из корня проекта
2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`) 2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`)
3. Если у базы есть поле `configSrc` — используй как `-ConfigPath` 3. Если у базы есть поле `configSrc` — используй как `-ConfigPath`
4. Если `configSrc` нет — спроси у пользователя 4. Если `configSrc` нет — спроси у пользователя
## Параметры ## Параметры
| Параметр | Описание | | Параметр | Описание |
|----------|----------| |----------|----------|
| `ExtensionPath` | Путь к каталогу расширения (обязат.) | | `ExtensionPath` | Путь к каталогу расширения (обязат.) |
| `ConfigPath` | Путь к конфигурации-источнику (обязат.) | | `ConfigPath` | Путь к конфигурации-источнику (обязат.) |
| `Object` | Что заимствовать (обязат.), batch через `;;` | | `Object` | Что заимствовать (обязат.), batch через `;;` |
| `BorrowMainAttribute` | Заимствовать основной реквизит формы. Без параметра — не заимствует. `Form` — реквизиты, используемые на форме. `All` — все реквизиты объекта. Требует форму в -Object | | `BorrowMainAttribute` | Заимствовать основной реквизит формы. Без параметра — не заимствует. `Form` — реквизиты, используемые на форме. `All` — все реквизиты объекта. Требует форму в -Object |
## Формат -Object ## Формат -Object
- `Catalog.Контрагенты` — справочник - `Catalog.Контрагенты` — справочник
- `CommonModule.РаботаСФайлами` — общий модуль - `CommonModule.РаботаСФайлами` — общий модуль
- `Document.РеализацияТоваров` — документ - `Document.РеализацияТоваров` — документ
- `Enum.ВидыОплат` — перечисление - `Enum.ВидыОплат` — перечисление
- `Catalog.Контрагенты.Form.ФормаЭлемента` — форма объекта (заимствование формы) - `Catalog.Контрагенты.Form.ФормаЭлемента` — форма объекта (заимствование формы)
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов - `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
Поддерживаются все 44 типа объектов конфигурации. Поддерживаются все 44 типа объектов конфигурации.
### Заимствование форм ### Заимствование форм
Формат `Тип.Имя.Form.ИмяФормы` заимствует форму конкретного объекта. Если родительский объект ещё не заимствован — он будет заимствован автоматически. Формат `Тип.Имя.Form.ИмяФормы` заимствует форму конкретного объекта. Если родительский объект ещё не заимствован — он будет заимствован автоматически.
Создаётся: Создаётся:
1. **Метаданные формы**`Forms/ИмяФормы.xml` с `ObjectBelonging=Adopted`, `FormType=Managed` 1. **Метаданные формы**`Forms/ИмяФормы.xml` с `ObjectBelonging=Adopted`, `FormType=Managed`
2. **Form.xml**`Forms/ИмяФормы/Ext/Form.xml` с копией исходной формы + `<BaseForm>` (начальное состояние) 2. **Form.xml**`Forms/ИмяФормы/Ext/Form.xml` с копией исходной формы + `<BaseForm>` (начальное состояние)
3. **Module.bsl** — пустой файл `Forms/ИмяФормы/Ext/Form/Module.bsl` 3. **Module.bsl** — пустой файл `Forms/ИмяФормы/Ext/Form/Module.bsl`
4. **Регистрация**`<Form>` в ChildObjects родительского объекта 4. **Регистрация**`<Form>` в ChildObjects родительского объекта
### Заимствование основного реквизита формы (-BorrowMainAttribute) ### Заимствование основного реквизита формы (-BorrowMainAttribute)
**Когда нужно**: пользователь хочет добавить новый реквизит в существующий объект конфигурации и вывести его на заимствованную форму. Без `-BorrowMainAttribute` форма заимствуется "пустой" — только визуальные элементы, без привязки к данным объекта. С `-BorrowMainAttribute` форма сохраняет привязки к реквизитам объекта (DataPath), что позволяет затем добавить на неё новые элементы через `/form-edit`. **Когда нужно**: пользователь хочет добавить новый реквизит в существующий объект конфигурации и вывести его на заимствованную форму. Без `-BorrowMainAttribute` форма заимствуется "пустой" — только визуальные элементы, без привязки к данным объекта. С `-BorrowMainAttribute` форма сохраняет привязки к реквизитам объекта (DataPath), что позволяет затем добавить на неё новые элементы через `/form-edit`.
**Два режима**: **Два режима**:
- `Form` (по умолчанию) — заимствует только те реквизиты объекта, которые уже выведены на форму. Оптимальный выбор для большинства случаев - `Form` (по умолчанию) — заимствует только те реквизиты объекта, которые уже выведены на форму. Оптимальный выбор для большинства случаев
- `All` — заимствует все реквизиты и табличные части объекта. Используй если планируешь выводить на форму реквизиты, которых на ней ещё нет - `All` — заимствует все реквизиты и табличные части объекта. Используй если планируешь выводить на форму реквизиты, которых на ней ещё нет
**Типовой сценарий** (добавление реквизита + вывод на форму): **Типовой сценарий** (добавление реквизита + вывод на форму):
1. `/cfe-borrow` с `-BorrowMainAttribute` — заимствовать форму с реквизитами 1. `/cfe-borrow` с `-BorrowMainAttribute` — заимствовать форму с реквизитами
2. `/meta-edit` — добавить новый реквизит в объект расширения 2. `/meta-edit` — добавить новый реквизит в объект расширения
3. `/form-edit` — вывести реквизит на заимствованную форму 3. `/form-edit` — вывести реквизит на заимствованную форму
**Защита существующих данных**: если зависимый объект уже заимствован с содержимым (реквизитами, формами) — скрипт не перезаписывает его, а добавляет только недостающее. **Защита существующих данных**: если зависимый объект уже заимствован с содержимым (реквизитами, формами) — скрипт не перезаписывает его, а добавляет только недостающее.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-borrow.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты" powershell.exe -NoProfile -File ".opencode/skills/cfe-borrow/scripts/cfe-borrow.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
``` ```
## Примеры ## Примеры
```powershell ```powershell
# Заимствовать один объект # Заимствовать один объект
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты" ... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
# Заимствовать форму (автоматически заимствует родительский объект) # Заимствовать форму (автоматически заимствует родительский объект)
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты.Form.ФормаЭлемента" ... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты.Form.ФормаЭлемента"
# Несколько объектов за раз # Несколько объектов за раз
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат" ... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
# Заимствовать форму с основным реквизитом (реквизиты по DataPath формы) # Заимствовать форму с основным реквизитом (реквизиты по DataPath формы)
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute ... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute
# Заимствовать форму с ВСЕМИ реквизитами объекта # Заимствовать форму с ВСЕМИ реквизитами объекта
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All ... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All
``` ```
## Верификация ## Верификация
``` ```
/cfe-validate <ExtensionPath> /cfe-validate <ExtensionPath>
``` ```
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cfe-borrow v1.3 — Borrow objects from configuration into extension (CFE) # cfe-borrow v1.8 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -14,6 +14,36 @@ XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
V8_NS = "http://v8.1c.ru/8.1/data/core" V8_NS = "http://v8.1c.ru/8.1/data/core"
# Form data-binding tags (value = attribute path). A binding survives only if its root
# attribute is borrowed into the form's <Attributes>; otherwise it must be stripped or the
# platform rejects the form with "Неверный путь к данным" on load.
FORM_BINDING_DATA_TAGS = ["DataPath", "TitleDataPath", "FooterDataPath", "HeaderDataPath", "MultipleValueDataPath", "MultipleValuePresentDataPath"]
# Picture-path binding tags (value = picture index path, never a data attribute) — always stripped in the skeleton.
FORM_BINDING_PICTURE_TAGS = ["RowPictureDataPath", "MultipleValuePictureDataPath"]
def strip_form_bindings(xml, keep_objekt):
"""Strip data-binding tags whose root attribute isn't borrowed.
keep_objekt=True (BorrowMainAttribute): keep Объект.* data bindings, strip the rest.
keep_objekt=False (default skeleton): strip all bindings. Picture-path tags are always stripped."""
for tag in FORM_BINDING_DATA_TAGS:
if keep_objekt:
xml = re.sub(rf'\s*<{tag}>(?!Объект\.)[^<]*</{tag}>', '', xml)
else:
xml = re.sub(rf'\s*<{tag}>[^<]*</{tag}>', '', xml)
for tag in FORM_BINDING_PICTURE_TAGS:
xml = re.sub(rf'\s*<{tag}>[^<]*</{tag}>', '', xml)
return xml
def decode_numeric_entities(s):
"""lxml emits numeric character refs (&#xNNNN;) for non-ASCII in some self-closed
elements where the PowerShell port writes literal characters. Normalize numeric refs
back to literal so PSPY output matches. Named entities (&amp; &lt; ...) are left intact."""
s = re.sub(r'&#x([0-9A-Fa-f]+);', lambda m: chr(int(m.group(1), 16)), s)
s = re.sub(r'&#(\d+);', lambda m: chr(int(m.group(1))), s)
return s
def localname(el): def localname(el):
return etree.QName(el.tag).localname return etree.QName(el.tag).localname
@@ -462,6 +492,13 @@ def main():
prop_node = props_node.find(f"{{{MD_NS}}}{prop_name}") prop_node = props_node.find(f"{{{MD_NS}}}{prop_name}")
if prop_node is not None: if prop_node is not None:
src_props[prop_name] = (prop_node.text or "").strip() src_props[prop_name] = (prop_node.text or "").strip()
# DefinedType: carry the <Type> definition. A type alias is meaningless as a bare shell —
# the platform needs its underlying type (e.g. to know a column is a summable Number for totals).
if type_name == "DefinedType":
type_node = props_node.find(f"{{{MD_NS}}}Type")
if type_node is not None:
type_xml = etree.tostring(type_node, encoding="unicode")
src_props["__TypeXml"] = re.sub(r'\s+xmlns(?::\w+)?="[^"]*"', '', type_xml)
return {"Uuid": src_uuid, "Properties": src_props, "Element": src_el} return {"Uuid": src_uuid, "Properties": src_props, "Element": src_el}
@@ -533,6 +570,10 @@ def main():
prop_val = source_props.get(prop_name, "false") prop_val = source_props.get(prop_name, "false")
lines.append(f"\t\t\t<{prop_name}>{prop_val}</{prop_name}>") lines.append(f"\t\t\t<{prop_name}>{prop_val}</{prop_name}>")
# DefinedType: emit the carried <Type> definition (needed for the alias to resolve, e.g. totals)
if type_name == "DefinedType" and "__TypeXml" in source_props:
lines.append(f"\t\t\t{source_props['__TypeXml']}")
lines.append("\t\t</Properties>") lines.append("\t\t</Properties>")
if type_name in TYPES_WITH_CHILD_OBJECTS: if type_name in TYPES_WITH_CHILD_OBJECTS:
@@ -644,7 +685,26 @@ def main():
first_level = {} first_level = {}
deep_paths = [] deep_paths = []
for m in re.finditer(r'<DataPath>[^<]*\b\u041e\u0431\u044a\u0435\u043a\u0442\.(\w+(?:\.\w+)*)</DataPath>', content): # Scan every data-binding tag (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*)
# for Объект.* references — picture-path tags carry picture indices, not data attributes.
for tag in FORM_BINDING_DATA_TAGS:
for m in re.finditer(r'<' + tag + r'>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</' + tag + r'>', content):
path = m.group(1)
segments = path.split(".")
seg0 = segments[0]
if seg0 in STANDARD_FIELDS:
continue
first_level[seg0] = True
if len(segments) >= 2:
seg1 = segments[1]
if seg1 in STANDARD_FIELDS:
continue
seg2 = segments[2] if len(segments) >= 3 else None
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2})
# Also scan <Field>Объект.X</Field> — object attributes referenced by filter/conditional-appearance
# fields (and dynamic lists), not via a *DataPath binding (e.g. УдалитьЮрФизЛицо). Designer borrows these too.
for m in re.finditer(r'<Field>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</Field>', content):
path = m.group(1) path = m.group(1)
segments = path.split(".") segments = path.split(".")
seg0 = segments[0] seg0 = segments[0]
@@ -655,22 +715,14 @@ def main():
seg1 = segments[1] seg1 = segments[1]
if seg1 in STANDARD_FIELDS: if seg1 in STANDARD_FIELDS:
continue continue
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1}) seg2 = segments[2] if len(segments) >= 3 else None
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2})
# Also collect from TitleDataPath
for m in re.finditer(r'<TitleDataPath>[^<]*\b\u041e\u0431\u044a\u0435\u043a\u0442\.(\w+(?:\.\w+)*)</TitleDataPath>', content):
path = m.group(1)
segments = path.split(".")
seg0 = segments[0]
if seg0 in STANDARD_FIELDS:
continue
first_level[seg0] = True
# Deduplicate deep paths # Deduplicate deep paths
seen = set() seen = set()
unique_deep = [] unique_deep = []
for dp in deep_paths: for dp in deep_paths:
key = f"{dp['ObjectAttr']}.{dp['SubAttr']}" key = f"{dp['ObjectAttr']}.{dp['SubAttr']}.{dp.get('SubSubAttr')}"
if key not in seen: if key not in seen:
seen.add(key) seen.add(key)
unique_deep.append(dp) unique_deep.append(dp)
@@ -941,26 +993,40 @@ def main():
# Step 3: Build the adopted content and insert into main object XML # Step 3: Build the adopted content and insert into main object XML
obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml") obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml")
# Generate full object XML with attributes and TS # Read existing object XML (needed for dedup + enrichment)
content_parts = []
for attr in src_attrs:
attr_xml = build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t")
content_parts.append(attr_xml)
for ts in src_ts:
ts_xml = build_adopted_tabular_section_xml(ts["Name"], ts["Uuid"], ts["GeneratedTypes"], ts["Attributes"], "\t\t\t")
content_parts.append(ts_xml)
adopted_content = "\n".join(content_parts).rstrip()
# Read existing object XML and inject
with open(obj_file, "r", encoding="utf-8-sig") as fh: with open(obj_file, "r", encoding="utf-8-sig") as fh:
obj_content = fh.read() obj_content = fh.read()
# Inject extra properties after ExtendedConfigurationObject # Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
existing_child_names = set()
m_co = re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content)
if m_co:
for nm in re.findall(r'<Name>(\w+)</Name>', m_co.group(1)):
existing_child_names.add(nm)
insert_attrs = [a for a in src_attrs if a["Name"] not in existing_child_names]
insert_ts = [t for t in src_ts if t["Name"] not in existing_child_names]
# Generate full object XML with attributes and TS
content_parts = []
for attr in insert_attrs:
content_parts.append(build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t"))
for ts in insert_ts:
content_parts.append(build_adopted_tabular_section_xml(ts["Name"], ts["Uuid"], ts["GeneratedTypes"], ts["Attributes"], "\t\t\t"))
adopted_content = "\n".join(content_parts).rstrip()
# Inject extra properties into the object's OWN Properties only — idempotent and anchored to the
# first ExtendedConfigurationObject (the object's). On re-borrow, adopted attributes each have their
# own ExtendedConfigurationObject; a global replace would push object props inside every <Attribute>.
if extra_props: if extra_props:
m_props = re.search(r'(?s)<Properties>(.*?)</Properties>', obj_content)
obj_props_block = m_props.group(1) if m_props else ""
props_xml = "" props_xml = ""
for p_name, p_val in extra_props.items(): for p_name, p_val in extra_props.items():
if f"<{p_name}>" in obj_props_block:
continue
props_xml += f"\r\n\t\t\t<{p_name}>{p_val}</{p_name}>" props_xml += f"\r\n\t\t\t<{p_name}>{p_val}</{p_name}>"
obj_content = obj_content.replace("</ExtendedConfigurationObject>", f"</ExtendedConfigurationObject>{props_xml}") if props_xml:
obj_content = obj_content.replace("</ExtendedConfigurationObject>", f"</ExtendedConfigurationObject>{props_xml}", 1)
# Replace empty ChildObjects with adopted content # Replace empty ChildObjects with adopted content
if adopted_content: if adopted_content:
@@ -1012,79 +1078,93 @@ def main():
# Step 5: Handle deep paths (Form mode only) # Step 5: Handle deep paths (Form mode only)
if mode == "Form" and deep_paths: if mode == "Form" and deep_paths:
# Filter out deep paths where ObjectAttr is a TabularSection # Top-level ref deep paths: Объект.<Ref>.<Sub> — borrow the ref attribute's catalog with the sub-attribute
real_deep = [dp for dp in deep_paths if dp["ObjectAttr"] not in ts_names] deep_by_attr = {}
for dp in deep_paths:
if real_deep: if dp["ObjectAttr"] in ts_names:
info(f" Processing {len(real_deep)} deep path(s)...") continue
deep_by_attr.setdefault(dp["ObjectAttr"], [])
# Group by ObjectAttr -> target catalog if dp["SubAttr"] not in deep_by_attr[dp["ObjectAttr"]]:
deep_by_attr = {}
for dp in real_deep:
if dp["ObjectAttr"] not in deep_by_attr:
deep_by_attr[dp["ObjectAttr"]] = []
deep_by_attr[dp["ObjectAttr"]].append(dp["SubAttr"]) deep_by_attr[dp["ObjectAttr"]].append(dp["SubAttr"])
if deep_by_attr:
info(f" Processing {len(deep_by_attr)} deep path attribute(s)...")
for attr_name, sub_attr_names in deep_by_attr.items(): for attr_name, sub_attr_names in deep_by_attr.items():
# Find the attribute's type to determine target catalog attr_info = next((a for a in src_attrs if a["Name"] == attr_name), None)
attr_info = None
for a in src_attrs:
if a["Name"] == attr_name:
attr_info = a
break
if not attr_info: if not attr_info:
continue continue
# Extract catalog name from type: cfg:CatalogRef.XXX
cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', attr_info["TypeXml"]) cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', attr_info["TypeXml"])
if not cat_match: if not cat_match:
continue continue
borrow_deep_target_attrs(cat_match.group(1), cat_match.group(2), sub_attr_names)
target_type_name = cat_match.group(1) # Tabular-section deep paths: Объект.<ТЧ>.<Колонка>.<Sub> — borrow the column's catalog with the sub-attribute
target_obj_name = cat_match.group(2) ts_deep_by_col = {}
for dp in deep_paths:
# Ensure target is borrowed if dp["ObjectAttr"] not in ts_names:
if not test_object_borrowed(target_type_name, target_obj_name): continue
t_src = read_source_object(target_type_name, target_obj_name) if not dp.get("SubSubAttr"):
t_borrowed_xml = build_borrowed_object_xml(target_type_name, target_obj_name, t_src["Uuid"], t_src["Properties"]) continue
t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name]) if dp["SubSubAttr"] in STANDARD_FIELDS:
os.makedirs(t_target_dir, exist_ok=True) continue
t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml") k = (dp["ObjectAttr"], dp["SubAttr"])
save_text_bom(t_target_file, t_borrowed_xml) ts_deep_by_col.setdefault(k, [])
add_to_child_objects(target_type_name, target_obj_name) if dp["SubSubAttr"] not in ts_deep_by_col[k]:
borrowed_files.append(t_target_file) ts_deep_by_col[k].append(dp["SubSubAttr"])
info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}") if ts_deep_by_col:
info(f" Processing {len(ts_deep_by_col)} tabular-section deep path(s)...")
# Resolve sub-attributes in target catalog for (ts_name, col_name), sub_attr_names in ts_deep_by_col.items():
sub_names = {sn: True for sn in sub_attr_names} ts_info = next((t for t in src_ts if t["Name"] == ts_name), None)
sub_resolved = resolve_source_attributes(target_type_name, target_obj_name, sub_names) if not ts_info:
continue
if sub_resolved["Attributes"]: col_info = next((c for c in ts_info["Attributes"] if c["Name"] == col_name), None)
merge_attributes_into_object(target_type_name, target_obj_name, sub_resolved["Attributes"]) if not col_info:
continue
# Collect and borrow ref types from deep attributes cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', col_info["TypeXml"])
sub_type_xmls = [sa["TypeXml"] for sa in sub_resolved["Attributes"]] if not cat_match:
sub_ref_types = collect_reference_types(sub_type_xmls) continue
for srt in sub_ref_types: borrow_deep_target_attrs(cat_match.group(1), cat_match.group(2), sub_attr_names)
if srt["TypeName"] not in CHILD_TYPE_DIR_MAP:
continue
if test_object_borrowed(srt["TypeName"], srt["ObjName"]):
continue
s_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]], f"{srt['ObjName']}.xml")
if not os.path.isfile(s_src_file):
continue
s_src = read_source_object(srt["TypeName"], srt["ObjName"])
s_borrowed_xml = build_borrowed_object_xml(srt["TypeName"], srt["ObjName"], s_src["Uuid"], s_src["Properties"])
s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]])
os.makedirs(s_target_dir, exist_ok=True)
s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml")
save_text_bom(s_target_file, s_borrowed_xml)
add_to_child_objects(srt["TypeName"], srt["ObjName"])
borrowed_files.append(s_target_file)
info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}")
info(" Main attribute borrowing complete") info(" Main attribute borrowing complete")
def borrow_deep_target_attrs(target_type_name, target_obj_name, sub_attr_names):
# Borrow a deep-path target catalog together with the referenced sub-attributes, for both
# Объект.<Ref>.<Sub> and Объект.<ТЧ>.<Колонка>.<Sub>. Mirrors Designer: the referenced catalog
# is adopted WITH the sub-attributes the form shows, else the platform rejects the deep DataPath.
if not test_object_borrowed(target_type_name, target_obj_name):
t_src = read_source_object(target_type_name, target_obj_name)
t_borrowed_xml = build_borrowed_object_xml(target_type_name, target_obj_name, t_src["Uuid"], t_src["Properties"])
t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name])
os.makedirs(t_target_dir, exist_ok=True)
t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml")
save_text_bom(t_target_file, t_borrowed_xml)
add_to_child_objects(target_type_name, target_obj_name)
borrowed_files.append(t_target_file)
info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}")
sub_names = {sn: True for sn in sub_attr_names}
sub_resolved = resolve_source_attributes(target_type_name, target_obj_name, sub_names)
if sub_resolved["Attributes"]:
merge_attributes_into_object(target_type_name, target_obj_name, sub_resolved["Attributes"])
sub_type_xmls = [sa["TypeXml"] for sa in sub_resolved["Attributes"]]
sub_ref_types = collect_reference_types(sub_type_xmls)
for srt in sub_ref_types:
if srt["TypeName"] not in CHILD_TYPE_DIR_MAP:
continue
if test_object_borrowed(srt["TypeName"], srt["ObjName"]):
continue
s_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]], f"{srt['ObjName']}.xml")
if not os.path.isfile(s_src_file):
continue
s_src = read_source_object(srt["TypeName"], srt["ObjName"])
s_borrowed_xml = build_borrowed_object_xml(srt["TypeName"], srt["ObjName"], s_src["Uuid"], s_src["Properties"])
s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]])
os.makedirs(s_target_dir, exist_ok=True)
s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml")
save_text_bom(s_target_file, s_borrowed_xml)
add_to_child_objects(srt["TypeName"], srt["ObjName"])
borrowed_files.append(s_target_file)
info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}")
def borrow_form(type_name, obj_name, form_name, borrow_main_attr=False): def borrow_form(type_name, obj_name, form_name, borrow_main_attr=False):
dir_name = CHILD_TYPE_DIR_MAP[type_name] dir_name = CHILD_TYPE_DIR_MAP[type_name]
@@ -1100,8 +1180,22 @@ def main():
with open(src_form_xml_path, "r", encoding="utf-8-sig") as fh: with open(src_form_xml_path, "r", encoding="utf-8-sig") as fh:
src_form_content = fh.read() src_form_content = fh.read()
# 3. Generate form metadata XML # 3. Generate form metadata XML.
new_form_uuid = new_guid() # If the wrapper was already borrowed, reuse its uuid so re-borrow is idempotent
# (regenerating it would churn the form's identity on every rerun).
existing_wrapper = os.path.join(ext_dir, dir_name, obj_name, "Forms", f"{form_name}.xml")
new_form_uuid = ""
if os.path.isfile(existing_wrapper):
try:
existing_root = etree.parse(existing_wrapper).getroot()
for c in existing_root:
if isinstance(c.tag, str) and localname(c) == "Form":
new_form_uuid = c.get("uuid", "") or ""
break
except Exception:
new_form_uuid = ""
if not new_form_uuid:
new_form_uuid = new_guid()
form_meta_lines = [ form_meta_lines = [
'<?xml version="1.0" encoding="UTF-8"?>', '<?xml version="1.0" encoding="UTF-8"?>',
f'<MetaDataObject {XMLNS_DECL} version="{format_version}">', f'<MetaDataObject {XMLNS_DECL} version="{format_version}">',
@@ -1131,7 +1225,10 @@ def main():
src_form_tree = etree.parse(src_form_xml_path, src_form_parser) src_form_tree = etree.parse(src_form_xml_path, src_form_parser)
src_form_el = src_form_tree.getroot() src_form_el = src_form_tree.getroot()
form_version = src_form_el.get("version", format_version) # Borrowed form uses the extension's format version (not the source form's) — keeps the
# extension uniform; otherwise the platform rejects the import on a version mismatch
# (e.g. a 2.13 form inside a 2.17 extension). The platform upgrades the form to the root version.
form_version = format_version
src_auto_cmd = None src_auto_cmd = None
form_props = [] form_props = []
@@ -1149,25 +1246,21 @@ def main():
continue continue
if not reached_visual: if not reached_visual:
# Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.) # Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.)
form_props.append(etree.tostring(fc, encoding="unicode")) form_props.append(decode_numeric_entities(etree.tostring(fc, encoding="unicode")))
ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"') ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
# AutoCommandBar: keep ChildItems (buttons with CommandName->0), Autofill->false # AutoCommandBar: keep ChildItems (buttons with CommandName->0), Autofill->false
auto_cmd_xml = "" auto_cmd_xml = ""
if src_auto_cmd is not None: if src_auto_cmd is not None:
auto_cmd_xml = etree.tostring(src_auto_cmd, encoding="unicode") auto_cmd_xml = decode_numeric_entities(etree.tostring(src_auto_cmd, encoding="unicode"))
auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml) auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml)
auto_cmd_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', auto_cmd_xml) auto_cmd_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', auto_cmd_xml)
auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>') auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>')
# Strip ExcludedCommand (references to standard commands invalid in extension) # Strip ExcludedCommand (references to standard commands invalid in extension)
auto_cmd_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', auto_cmd_xml) auto_cmd_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', auto_cmd_xml)
# Strip DataPath in AutoCommandBar buttons # Strip data-binding tags whose root attribute isn't borrowed
if borrow_main_attr: auto_cmd_xml = strip_form_bindings(auto_cmd_xml, borrow_main_attr)
# Keep only Объект.* DataPaths
auto_cmd_xml = re.sub(r'\s*<DataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</DataPath>', '', auto_cmd_xml)
else:
auto_cmd_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', auto_cmd_xml)
# ChildItems: copy full tree, clean up base-config references # ChildItems: copy full tree, clean up base-config references
child_items_xml = "" child_items_xml = ""
@@ -1178,20 +1271,12 @@ def main():
break break
if src_child_items is not None: if src_child_items is not None:
child_items_xml = etree.tostring(src_child_items, encoding="unicode") child_items_xml = decode_numeric_entities(etree.tostring(src_child_items, encoding="unicode"))
child_items_xml = ns_strip_pattern.sub("", child_items_xml) child_items_xml = ns_strip_pattern.sub("", child_items_xml)
# Replace all CommandName values with 0 # Replace all CommandName values with 0
child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml) child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml)
# Strip DataPath / TitleDataPath / RowPictureDataPath # Strip data-binding tags whose root attribute isn't borrowed
if borrow_main_attr: child_items_xml = strip_form_bindings(child_items_xml, borrow_main_attr)
# Keep only Объект.* DataPaths — strip form-attribute DataPaths (not borrowed)
child_items_xml = re.sub(r'\s*<DataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</DataPath>', '', child_items_xml)
child_items_xml = re.sub(r'\s*<TitleDataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</TitleDataPath>', '', child_items_xml)
child_items_xml = re.sub(r'\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '', child_items_xml)
else:
child_items_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', child_items_xml)
child_items_xml = re.sub(r'\s*<TitleDataPath>[^<]*</TitleDataPath>', '', child_items_xml)
child_items_xml = re.sub(r'\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '', child_items_xml)
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension) # Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
child_items_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', child_items_xml) child_items_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', child_items_xml)
# Strip TypeLink blocks with human-readable DataPath (Items.XXX) # Strip TypeLink blocks with human-readable DataPath (Items.XXX)
@@ -1428,12 +1513,16 @@ def main():
save_text_bom(form_xml_file, "".join(parts)) save_text_bom(form_xml_file, "".join(parts))
info(f" Created: {form_xml_file}") info(f" Created: {form_xml_file}")
# 6. Create empty Module.bsl # 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
# not clobber user code added to the form module).
module_dir = os.path.join(form_xml_dir, "Form") module_dir = os.path.join(form_xml_dir, "Form")
os.makedirs(module_dir, exist_ok=True) os.makedirs(module_dir, exist_ok=True)
module_bsl_file = os.path.join(module_dir, "Module.bsl") module_bsl_file = os.path.join(module_dir, "Module.bsl")
save_text_bom(module_bsl_file, "") if os.path.isfile(module_bsl_file):
info(f" Created: {module_bsl_file}") info(" Preserved existing Module.bsl")
else:
save_text_bom(module_bsl_file, "")
info(f" Created: {module_bsl_file}")
# 7. Register form in parent object ChildObjects # 7. Register form in parent object ChildObjects
register_form_in_object(type_name, obj_name, form_name) register_form_in_object(type_name, obj_name, form_name)
@@ -1,57 +1,57 @@
--- ---
name: cfe-diff name: cfe-diff
description: Анализ расширения конфигурации 1С (CFE) — состав, заимствованные объекты, перехватчики, проверка переноса. Используй когда нужно понять что содержит расширение или проверить перенесены ли вставки в конфигурацию description: Анализ расширения конфигурации 1С (CFE) — состав, заимствованные объекты, перехватчики, проверка переноса. Используй когда нужно понять что содержит расширение или проверить перенесены ли вставки в конфигурацию
argument-hint: -ExtensionPath <path> -ConfigPath <path> [-Mode A|B] argument-hint: -ExtensionPath <path> -ConfigPath <path> [-Mode A|B]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cfe-diff — Анализ расширения конфигурации # /cfe-diff — Анализ расширения конфигурации
Анализирует расширение в двух режимах: обзор изменений (Mode A) или проверка переноса (Mode B). Анализирует расширение в двух режимах: обзор изменений (Mode A) или проверка переноса (Mode B).
## Параметры ## Параметры
| Параметр | Описание | По умолчанию | | Параметр | Описание | По умолчанию |
|----------|----------|--------------| |----------|----------|--------------|
| `ExtensionPath` | Путь к расширению (обязат.) | — | | `ExtensionPath` | Путь к расширению (обязат.) | — |
| `ConfigPath` | Путь к конфигурации (обязат.) | — | | `ConfigPath` | Путь к конфигурации (обязат.) | — |
| `Mode` | `A` (обзор) / `B` (проверка переноса) | `A` | | `Mode` | `A` (обзор) / `B` (проверка переноса) | `A` |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A powershell.exe -NoProfile -File ".opencode/skills/cfe-diff/scripts/cfe-diff.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
``` ```
## Mode A — обзор расширения ## Mode A — обзор расширения
Для каждого объекта показывает: Для каждого объекта показывает:
- `[BORROWED]` — заимствованный: перехватчики (`&Перед`, `&После`, `&ИзменениеИКонтроль`, `&Вместо`), собственные реквизиты/ТЧ/формы - `[BORROWED]` — заимствованный: перехватчики (`&Перед`, `&После`, `&ИзменениеИКонтроль`, `&Вместо`), собственные реквизиты/ТЧ/формы
- `[OWN]` — собственный: количество реквизитов, ТЧ, форм - `[OWN]` — собственный: количество реквизитов, ТЧ, форм
Для каждой формы заимствованного объекта показывается: Для каждой формы заимствованного объекта показывается:
- `(borrowed)` / `(own)` — заимствованная или собственная форма - `(borrowed)` / `(own)` — заимствованная или собственная форма
- callType-события формы и элементов - callType-события формы и элементов
- callType на командах - callType на командах
## Mode B — проверка переноса ## Mode B — проверка переноса
Для каждого `&ИзменениеИКонтроль` извлекает блоки `#Вставка`/`#КонецВставки` из расширения и ищет их в соответствующем модуле конфигурации. Для каждого `&ИзменениеИКонтроль` извлекает блоки `#Вставка`/`#КонецВставки` из расширения и ищет их в соответствующем модуле конфигурации.
Статусы: Статусы:
- `[TRANSFERRED]` — код найден в конфигурации - `[TRANSFERRED]` — код найден в конфигурации
- `[NOT_TRANSFERRED]` — код не найден - `[NOT_TRANSFERRED]` — код не найден
- `[NEEDS_REVIEW]` — нет блоков `#Вставка` или модуль конфигурации не найден - `[NEEDS_REVIEW]` — нет блоков `#Вставка` или модуль конфигурации не найден
## Примеры ## Примеры
```powershell ```powershell
# Обзор — что изменено в расширении # Обзор — что изменено в расширении
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A ... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
# Проверка переноса — все ли #Вставка перенесены # Проверка переноса — все ли #Вставка перенесены
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode B ... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode B
``` ```
@@ -1,471 +1,471 @@
# cfe-diff v1.0 — Analyze and compare 1C configuration extension (CFE) # cfe-diff v1.0 — Analyze and compare 1C configuration extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$ExtensionPath, [string]$ExtensionPath,
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$ConfigPath, [string]$ConfigPath,
[ValidateSet("A","B")] [ValidateSet("A","B")]
[string]$Mode = "A" [string]$Mode = "A"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve paths --- # --- Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) { if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath $ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
} }
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) { if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath $ConfigPath = Join-Path (Get-Location).Path $ConfigPath
} }
if (Test-Path $ExtensionPath -PathType Leaf) { $ExtensionPath = Split-Path $ExtensionPath -Parent } if (Test-Path $ExtensionPath -PathType Leaf) { $ExtensionPath = Split-Path $ExtensionPath -Parent }
if (Test-Path $ConfigPath -PathType Leaf) { $ConfigPath = Split-Path $ConfigPath -Parent } if (Test-Path $ConfigPath -PathType Leaf) { $ConfigPath = Split-Path $ConfigPath -Parent }
$extCfg = Join-Path $ExtensionPath "Configuration.xml" $extCfg = Join-Path $ExtensionPath "Configuration.xml"
$srcCfg = Join-Path $ConfigPath "Configuration.xml" $srcCfg = Join-Path $ConfigPath "Configuration.xml"
if (-not (Test-Path $extCfg)) { Write-Error "Extension Configuration.xml not found: $extCfg"; exit 1 } if (-not (Test-Path $extCfg)) { Write-Error "Extension Configuration.xml not found: $extCfg"; exit 1 }
if (-not (Test-Path $srcCfg)) { Write-Error "Config Configuration.xml not found: $srcCfg"; exit 1 } if (-not (Test-Path $srcCfg)) { Write-Error "Config Configuration.xml not found: $srcCfg"; exit 1 }
# --- Type -> directory mapping --- # --- Type -> directory mapping ---
$childTypeDirMap = @{ $childTypeDirMap = @{
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums" "Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
"CommonModule"="CommonModules"; "CommonPicture"="CommonPictures" "CommonModule"="CommonModules"; "CommonPicture"="CommonPictures"
"CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates" "CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates"
"ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors" "ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors"
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters" "InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes" "ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters" "ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters" "ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks" "BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants" "Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants"
"FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes" "FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes"
"FunctionalOptionsParameter"="FunctionalOptionsParameters" "FunctionalOptionsParameter"="FunctionalOptionsParameters"
"CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals" "CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals"
"SessionParameter"="SessionParameters"; "StyleItem"="StyleItems" "SessionParameter"="SessionParameters"; "StyleItem"="StyleItems"
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs" "EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria" "SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators" "CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices" "Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
"CommonAttribute"="CommonAttributes" "CommonAttribute"="CommonAttributes"
} }
# --- Parse extension Configuration.xml --- # --- Parse extension Configuration.xml ---
$extDoc = New-Object System.Xml.XmlDocument $extDoc = New-Object System.Xml.XmlDocument
$extDoc.PreserveWhitespace = $false $extDoc.PreserveWhitespace = $false
$extDoc.Load($extCfg) $extDoc.Load($extCfg)
$ns = New-Object System.Xml.XmlNamespaceManager($extDoc.NameTable) $ns = New-Object System.Xml.XmlNamespaceManager($extDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") $ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable") $ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$extProps = $extDoc.SelectSingleNode("//md:Configuration/md:Properties", $ns) $extProps = $extDoc.SelectSingleNode("//md:Configuration/md:Properties", $ns)
$extNameNode = $extProps.SelectSingleNode("md:Name", $ns) $extNameNode = $extProps.SelectSingleNode("md:Name", $ns)
$extName = if ($extNameNode) { $extNameNode.InnerText } else { "?" } $extName = if ($extNameNode) { $extNameNode.InnerText } else { "?" }
$prefixNode = $extProps.SelectSingleNode("md:NamePrefix", $ns) $prefixNode = $extProps.SelectSingleNode("md:NamePrefix", $ns)
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "" } $namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "" }
$purposeNode = $extProps.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns) $purposeNode = $extProps.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns)
$purpose = if ($purposeNode) { $purposeNode.InnerText } else { "?" } $purpose = if ($purposeNode) { $purposeNode.InnerText } else { "?" }
Write-Host "=== cfe-diff Mode ${Mode}: $extName (${purpose}) ===" Write-Host "=== cfe-diff Mode ${Mode}: $extName (${purpose}) ==="
Write-Host " NamePrefix: $namePrefix" Write-Host " NamePrefix: $namePrefix"
Write-Host "" Write-Host ""
# --- Collect ChildObjects --- # --- Collect ChildObjects ---
$childObjNode = $extDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns) $childObjNode = $extDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns)
if (-not $childObjNode) { if (-not $childObjNode) {
Write-Host "[WARN] No ChildObjects in extension" Write-Host "[WARN] No ChildObjects in extension"
exit 0 exit 0
} }
$objects = @() $objects = @()
foreach ($child in $childObjNode.ChildNodes) { foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue } if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq "Language") { continue } if ($child.LocalName -eq "Language") { continue }
$objects += @{ Type = $child.LocalName; Name = $child.InnerText } $objects += @{ Type = $child.LocalName; Name = $child.InnerText }
} }
if ($objects.Count -eq 0) { if ($objects.Count -eq 0) {
Write-Host "No objects (besides Language) in extension." Write-Host "No objects (besides Language) in extension."
exit 0 exit 0
} }
# --- Helper: check if object is borrowed --- # --- Helper: check if object is borrowed ---
function Get-ObjectInfo { function Get-ObjectInfo {
param([string]$objType, [string]$objName) param([string]$objType, [string]$objName)
if (-not $childTypeDirMap.ContainsKey($objType)) { return $null } if (-not $childTypeDirMap.ContainsKey($objType)) { return $null }
$dirName = $childTypeDirMap[$objType] $dirName = $childTypeDirMap[$objType]
$objFile = Join-Path (Join-Path $ExtensionPath $dirName) "${objName}.xml" $objFile = Join-Path (Join-Path $ExtensionPath $dirName) "${objName}.xml"
if (-not (Test-Path $objFile)) { return @{ Borrowed = $false; File = $objFile; Exists = $false } } if (-not (Test-Path $objFile)) { return @{ Borrowed = $false; File = $objFile; Exists = $false } }
$doc = New-Object System.Xml.XmlDocument $doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $false $doc.PreserveWhitespace = $false
$doc.Load($objFile) $doc.Load($objFile)
$objNs = New-Object System.Xml.XmlNamespaceManager($doc.NameTable) $objNs = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") $objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$objEl = $null $objEl = $null
foreach ($c in $doc.DocumentElement.ChildNodes) { foreach ($c in $doc.DocumentElement.ChildNodes) {
if ($c.NodeType -eq 'Element') { $objEl = $c; break } if ($c.NodeType -eq 'Element') { $objEl = $c; break }
} }
if (-not $objEl) { return @{ Borrowed = $false; File = $objFile; Exists = $true } } if (-not $objEl) { return @{ Borrowed = $false; File = $objFile; Exists = $true } }
$propsEl = $objEl.SelectSingleNode("md:Properties", $objNs) $propsEl = $objEl.SelectSingleNode("md:Properties", $objNs)
$obNode = if ($propsEl) { $propsEl.SelectSingleNode("md:ObjectBelonging", $objNs) } else { $null } $obNode = if ($propsEl) { $propsEl.SelectSingleNode("md:ObjectBelonging", $objNs) } else { $null }
$info = @{ $info = @{
Borrowed = ($obNode -and $obNode.InnerText -eq "Adopted") Borrowed = ($obNode -and $obNode.InnerText -eq "Adopted")
File = $objFile File = $objFile
Exists = $true Exists = $true
Type = $objType Type = $objType
Name = $objName Name = $objName
DirName = $dirName DirName = $dirName
ObjElement = $objEl ObjElement = $objEl
ObjNs = $objNs ObjNs = $objNs
} }
return $info return $info
} }
# --- Helper: find .bsl files for object --- # --- Helper: find .bsl files for object ---
function Get-BslFiles { function Get-BslFiles {
param([string]$objType, [string]$objName) param([string]$objType, [string]$objName)
if (-not $childTypeDirMap.ContainsKey($objType)) { return @() } if (-not $childTypeDirMap.ContainsKey($objType)) { return @() }
$dirName = $childTypeDirMap[$objType] $dirName = $childTypeDirMap[$objType]
$objDir = Join-Path (Join-Path $ExtensionPath $dirName) $objName $objDir = Join-Path (Join-Path $ExtensionPath $dirName) $objName
if (-not (Test-Path $objDir -PathType Container)) { return @() } if (-not (Test-Path $objDir -PathType Container)) { return @() }
$bslFiles = @() $bslFiles = @()
$extDir = Join-Path $objDir "Ext" $extDir = Join-Path $objDir "Ext"
if (Test-Path $extDir) { if (Test-Path $extDir) {
$items = Get-ChildItem -Path $extDir -Filter "*.bsl" -ErrorAction SilentlyContinue $items = Get-ChildItem -Path $extDir -Filter "*.bsl" -ErrorAction SilentlyContinue
foreach ($item in $items) { $bslFiles += $item.FullName } foreach ($item in $items) { $bslFiles += $item.FullName }
} }
# Forms # Forms
$formsDir = Join-Path $objDir "Forms" $formsDir = Join-Path $objDir "Forms"
if (Test-Path $formsDir) { if (Test-Path $formsDir) {
$formModules = Get-ChildItem -Path $formsDir -Recurse -Filter "Module.bsl" -ErrorAction SilentlyContinue $formModules = Get-ChildItem -Path $formsDir -Recurse -Filter "Module.bsl" -ErrorAction SilentlyContinue
foreach ($fm in $formModules) { $bslFiles += $fm.FullName } foreach ($fm in $formModules) { $bslFiles += $fm.FullName }
} }
return $bslFiles return $bslFiles
} }
# --- Helper: parse interceptors from .bsl --- # --- Helper: parse interceptors from .bsl ---
function Get-Interceptors { function Get-Interceptors {
param([string]$bslPath) param([string]$bslPath)
if (-not (Test-Path $bslPath)) { return @() } if (-not (Test-Path $bslPath)) { return @() }
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8) $lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
$interceptors = @() $interceptors = @()
$i = 0 $i = 0
while ($i -lt $lines.Count) { while ($i -lt $lines.Count) {
$line = $lines[$i].Trim() $line = $lines[$i].Trim()
if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') { if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
$type = $Matches[1] $type = $Matches[1]
$method = $Matches[2] $method = $Matches[2]
$interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath } $interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath }
} }
$i++ $i++
} }
return $interceptors return $interceptors
} }
# --- Helper: extract #Вставка blocks from .bsl --- # --- Helper: extract #Вставка blocks from .bsl ---
function Get-InsertionBlocks { function Get-InsertionBlocks {
param([string]$bslPath) param([string]$bslPath)
if (-not (Test-Path $bslPath)) { return @() } if (-not (Test-Path $bslPath)) { return @() }
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8) $lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
$blocks = @() $blocks = @()
$inBlock = $false $inBlock = $false
$blockLines = @() $blockLines = @()
$startLine = 0 $startLine = 0
for ($i = 0; $i -lt $lines.Count; $i++) { for ($i = 0; $i -lt $lines.Count; $i++) {
$line = $lines[$i].Trim() $line = $lines[$i].Trim()
if ($line -eq "#Вставка") { if ($line -eq "#Вставка") {
$inBlock = $true $inBlock = $true
$blockLines = @() $blockLines = @()
$startLine = $i + 1 $startLine = $i + 1
} elseif ($line -eq "#КонецВставки" -and $inBlock) { } elseif ($line -eq "#КонецВставки" -and $inBlock) {
$inBlock = $false $inBlock = $false
$blocks += @{ $blocks += @{
StartLine = $startLine StartLine = $startLine
EndLine = $i + 1 EndLine = $i + 1
Code = ($blockLines -join "`n").Trim() Code = ($blockLines -join "`n").Trim()
File = $bslPath File = $bslPath
} }
} elseif ($inBlock) { } elseif ($inBlock) {
$blockLines += $lines[$i] $blockLines += $lines[$i]
} }
} }
return $blocks return $blocks
} }
# --- Helper: analyze form for callType events and commands --- # --- Helper: analyze form for callType events and commands ---
function Get-FormInterceptors { function Get-FormInterceptors {
param([string]$formXmlPath) param([string]$formXmlPath)
if (-not (Test-Path $formXmlPath)) { return $null } if (-not (Test-Path $formXmlPath)) { return $null }
$formDoc = New-Object System.Xml.XmlDocument $formDoc = New-Object System.Xml.XmlDocument
$formDoc.PreserveWhitespace = $false $formDoc.PreserveWhitespace = $false
try { $formDoc.Load($formXmlPath) } catch { return $null } try { $formDoc.Load($formXmlPath) } catch { return $null }
$fNs = New-Object System.Xml.XmlNamespaceManager($formDoc.NameTable) $fNs = New-Object System.Xml.XmlNamespaceManager($formDoc.NameTable)
$fNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform") $fNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
$fRoot = $formDoc.DocumentElement $fRoot = $formDoc.DocumentElement
$baseForm = $fRoot.SelectSingleNode("f:BaseForm", $fNs) $baseForm = $fRoot.SelectSingleNode("f:BaseForm", $fNs)
$isBorrowed = ($baseForm -ne $null) $isBorrowed = ($baseForm -ne $null)
$interceptors = @() $interceptors = @()
# Form-level events with callType # Form-level events with callType
$eventsNode = $fRoot.SelectSingleNode("f:Events", $fNs) $eventsNode = $fRoot.SelectSingleNode("f:Events", $fNs)
if ($eventsNode) { if ($eventsNode) {
foreach ($evt in $eventsNode.SelectNodes("f:Event", $fNs)) { foreach ($evt in $eventsNode.SelectNodes("f:Event", $fNs)) {
$ct = $evt.GetAttribute("callType") $ct = $evt.GetAttribute("callType")
if ($ct) { if ($ct) {
$interceptors += "Event:$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)" $interceptors += "Event:$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
} }
} }
} }
# Element-level events with callType (scan all elements recursively) # Element-level events with callType (scan all elements recursively)
$childItems = $fRoot.SelectSingleNode("f:ChildItems", $fNs) $childItems = $fRoot.SelectSingleNode("f:ChildItems", $fNs)
if ($childItems) { if ($childItems) {
foreach ($evtNode in $childItems.SelectNodes(".//*[f:Events/f:Event[@callType]]", $fNs)) { foreach ($evtNode in $childItems.SelectNodes(".//*[f:Events/f:Event[@callType]]", $fNs)) {
$elName = $evtNode.GetAttribute("name") $elName = $evtNode.GetAttribute("name")
foreach ($evt in $evtNode.SelectNodes("f:Events/f:Event[@callType]", $fNs)) { foreach ($evt in $evtNode.SelectNodes("f:Events/f:Event[@callType]", $fNs)) {
$ct = $evt.GetAttribute("callType") $ct = $evt.GetAttribute("callType")
$interceptors += "Element:${elName}.$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)" $interceptors += "Element:${elName}.$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
} }
} }
} }
# Commands with callType on Action # Commands with callType on Action
foreach ($cmd in $fRoot.SelectNodes("f:Commands/f:Command", $fNs)) { foreach ($cmd in $fRoot.SelectNodes("f:Commands/f:Command", $fNs)) {
$cmdName = $cmd.GetAttribute("name") $cmdName = $cmd.GetAttribute("name")
foreach ($action in $cmd.SelectNodes("f:Action[@callType]", $fNs)) { foreach ($action in $cmd.SelectNodes("f:Action[@callType]", $fNs)) {
$ct = $action.GetAttribute("callType") $ct = $action.GetAttribute("callType")
$interceptors += "Command:$cmdName [$ct] -> $($action.InnerText)" $interceptors += "Command:$cmdName [$ct] -> $($action.InnerText)"
} }
} }
return @{ return @{
IsBorrowed = $isBorrowed IsBorrowed = $isBorrowed
Interceptors = $interceptors Interceptors = $interceptors
} }
} }
# ============================================================ # ============================================================
# MODE A: Extension overview # MODE A: Extension overview
# ============================================================ # ============================================================
if ($Mode -eq "A") { if ($Mode -eq "A") {
$borrowedList = @() $borrowedList = @()
$ownList = @() $ownList = @()
foreach ($obj in $objects) { foreach ($obj in $objects) {
$info = Get-ObjectInfo $obj.Type $obj.Name $info = Get-ObjectInfo $obj.Type $obj.Name
if (-not $info) { if (-not $info) {
Write-Host " [?] $($obj.Type).$($obj.Name) — unknown type" Write-Host " [?] $($obj.Type).$($obj.Name) — unknown type"
continue continue
} }
if (-not $info.Exists) { if (-not $info.Exists) {
Write-Host " [?] $($obj.Type).$($obj.Name) — file not found" Write-Host " [?] $($obj.Type).$($obj.Name) — file not found"
continue continue
} }
if ($info.Borrowed) { if ($info.Borrowed) {
$borrowedList += $obj $borrowedList += $obj
Write-Host " [BORROWED] $($obj.Type).$($obj.Name)" Write-Host " [BORROWED] $($obj.Type).$($obj.Name)"
# Find .bsl files and interceptors # Find .bsl files and interceptors
$bslFiles = Get-BslFiles $obj.Type $obj.Name $bslFiles = Get-BslFiles $obj.Type $obj.Name
foreach ($bsl in $bslFiles) { foreach ($bsl in $bslFiles) {
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/") $relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
$interceptors = Get-Interceptors $bsl $interceptors = Get-Interceptors $bsl
if ($interceptors.Count -gt 0) { if ($interceptors.Count -gt 0) {
foreach ($ic in $interceptors) { foreach ($ic in $interceptors) {
Write-Host " &$($ic.Type)(`"$($ic.Method)`") — line $($ic.Line) in $relPath" Write-Host " &$($ic.Type)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
} }
} else { } else {
Write-Host " $relPath (no interceptors)" Write-Host " $relPath (no interceptors)"
} }
} }
# Check for own attributes/forms in ChildObjects # Check for own attributes/forms in ChildObjects
if ($info.ObjElement) { if ($info.ObjElement) {
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs) $childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
if ($childObj) { if ($childObj) {
$ownAttrs = 0 $ownAttrs = 0
$ownForms = 0 $ownForms = 0
$ownTS = 0 $ownTS = 0
$borrowedItems = 0 $borrowedItems = 0
$formNames = @() $formNames = @()
foreach ($c in $childObj.ChildNodes) { foreach ($c in $childObj.ChildNodes) {
if ($c.NodeType -ne 'Element') { continue } if ($c.NodeType -ne 'Element') { continue }
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs) $cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
if ($cProps) { if ($cProps) {
$cOb = $cProps.SelectSingleNode("md:ObjectBelonging", $info.ObjNs) $cOb = $cProps.SelectSingleNode("md:ObjectBelonging", $info.ObjNs)
if ($cOb -and $cOb.InnerText -eq "Adopted") { if ($cOb -and $cOb.InnerText -eq "Adopted") {
$borrowedItems++ $borrowedItems++
continue continue
} }
} }
switch ($c.LocalName) { switch ($c.LocalName) {
"Attribute" { $ownAttrs++ } "Attribute" { $ownAttrs++ }
"TabularSection" { $ownTS++ } "TabularSection" { $ownTS++ }
"Form" { $formNames += $c.InnerText; $ownForms++ } "Form" { $formNames += $c.InnerText; $ownForms++ }
} }
} }
$parts = @() $parts = @()
if ($ownAttrs -gt 0) { $parts += "$ownAttrs own attrs" } if ($ownAttrs -gt 0) { $parts += "$ownAttrs own attrs" }
if ($ownTS -gt 0) { $parts += "$ownTS own TS" } if ($ownTS -gt 0) { $parts += "$ownTS own TS" }
if ($ownForms -gt 0) { $parts += "$ownForms own forms" } if ($ownForms -gt 0) { $parts += "$ownForms own forms" }
if ($borrowedItems -gt 0) { $parts += "$borrowedItems borrowed items" } if ($borrowedItems -gt 0) { $parts += "$borrowedItems borrowed items" }
if ($parts.Count -gt 0) { if ($parts.Count -gt 0) {
Write-Host " ChildObjects: $($parts -join ', ')" Write-Host " ChildObjects: $($parts -join ', ')"
} }
# Analyze forms # Analyze forms
$borrowedFormCount = 0 $borrowedFormCount = 0
$ownFormCount = 0 $ownFormCount = 0
foreach ($fn in $formNames) { foreach ($fn in $formNames) {
$formXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $info.DirName) $info.Name) "Forms") $fn) "Ext/Form.xml" $formXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $info.DirName) $info.Name) "Forms") $fn) "Ext/Form.xml"
$fi = Get-FormInterceptors $formXmlPath $fi = Get-FormInterceptors $formXmlPath
if (-not $fi) { if (-not $fi) {
Write-Host " Form.$fn (?)" Write-Host " Form.$fn (?)"
continue continue
} }
$formTag = if ($fi.IsBorrowed) { "borrowed"; $borrowedFormCount++ } else { "own"; $ownFormCount++ } $formTag = if ($fi.IsBorrowed) { "borrowed"; $borrowedFormCount++ } else { "own"; $ownFormCount++ }
if ($fi.Interceptors.Count -gt 0) { if ($fi.Interceptors.Count -gt 0) {
Write-Host " Form.$fn ($formTag):" Write-Host " Form.$fn ($formTag):"
foreach ($ic in $fi.Interceptors) { foreach ($ic in $fi.Interceptors) {
Write-Host " $ic" Write-Host " $ic"
} }
} else { } else {
Write-Host " Form.$fn ($formTag)" Write-Host " Form.$fn ($formTag)"
} }
} }
} }
} }
} else { } else {
$ownList += $obj $ownList += $obj
Write-Host " [OWN] $($obj.Type).$($obj.Name)" Write-Host " [OWN] $($obj.Type).$($obj.Name)"
# Brief info for own objects # Brief info for own objects
if ($info.ObjElement) { if ($info.ObjElement) {
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs) $childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
if ($childObj) { if ($childObj) {
$attrs = 0; $forms = 0; $ts = 0 $attrs = 0; $forms = 0; $ts = 0
foreach ($c in $childObj.ChildNodes) { foreach ($c in $childObj.ChildNodes) {
if ($c.NodeType -ne 'Element') { continue } if ($c.NodeType -ne 'Element') { continue }
switch ($c.LocalName) { switch ($c.LocalName) {
"Attribute" { $attrs++ } "Attribute" { $attrs++ }
"TabularSection" { $ts++ } "TabularSection" { $ts++ }
"Form" { $forms++ } "Form" { $forms++ }
} }
} }
$parts = @() $parts = @()
if ($attrs -gt 0) { $parts += "$attrs attrs" } if ($attrs -gt 0) { $parts += "$attrs attrs" }
if ($ts -gt 0) { $parts += "$ts TS" } if ($ts -gt 0) { $parts += "$ts TS" }
if ($forms -gt 0) { $parts += "$forms forms" } if ($forms -gt 0) { $parts += "$forms forms" }
if ($parts.Count -gt 0) { if ($parts.Count -gt 0) {
Write-Host " $($parts -join ', ')" Write-Host " $($parts -join ', ')"
} }
} }
} }
} }
} }
Write-Host "" Write-Host ""
Write-Host "=== Summary: $($borrowedList.Count) borrowed, $($ownList.Count) own objects ===" Write-Host "=== Summary: $($borrowedList.Count) borrowed, $($ownList.Count) own objects ==="
} }
# ============================================================ # ============================================================
# MODE B: Transfer check # MODE B: Transfer check
# ============================================================ # ============================================================
if ($Mode -eq "B") { if ($Mode -eq "B") {
$transferred = 0 $transferred = 0
$notTransferred = 0 $notTransferred = 0
$needsReview = 0 $needsReview = 0
foreach ($obj in $objects) { foreach ($obj in $objects) {
$info = Get-ObjectInfo $obj.Type $obj.Name $info = Get-ObjectInfo $obj.Type $obj.Name
if (-not $info -or -not $info.Exists -or -not $info.Borrowed) { continue } if (-not $info -or -not $info.Exists -or -not $info.Borrowed) { continue }
# Find .bsl files with &ИзменениеИКонтроль # Find .bsl files with &ИзменениеИКонтроль
$bslFiles = Get-BslFiles $obj.Type $obj.Name $bslFiles = Get-BslFiles $obj.Type $obj.Name
foreach ($bsl in $bslFiles) { foreach ($bsl in $bslFiles) {
$interceptors = Get-Interceptors $bsl $interceptors = Get-Interceptors $bsl
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" }) $macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
if ($macInterceptors.Count -eq 0) { continue } if ($macInterceptors.Count -eq 0) { continue }
foreach ($ic in $macInterceptors) { foreach ($ic in $macInterceptors) {
$methodName = $ic.Method $methodName = $ic.Method
$relBsl = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/") $relBsl = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
# Find #Вставка blocks in this file # Find #Вставка blocks in this file
$insertBlocks = Get-InsertionBlocks $bsl $insertBlocks = Get-InsertionBlocks $bsl
if ($insertBlocks.Count -eq 0) { if ($insertBlocks.Count -eq 0) {
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — no #Вставка blocks" Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — no #Вставка blocks"
$needsReview++ $needsReview++
continue continue
} }
# Find corresponding module in config # Find corresponding module in config
if (-not $childTypeDirMap.ContainsKey($obj.Type)) { continue } if (-not $childTypeDirMap.ContainsKey($obj.Type)) { continue }
$dirName = $childTypeDirMap[$obj.Type] $dirName = $childTypeDirMap[$obj.Type]
$configBsl = $bsl.Replace($ExtensionPath, $ConfigPath) $configBsl = $bsl.Replace($ExtensionPath, $ConfigPath)
if (-not (Test-Path $configBsl)) { if (-not (Test-Path $configBsl)) {
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — config module not found" Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — config module not found"
$needsReview++ $needsReview++
continue continue
} }
$configContent = [System.IO.File]::ReadAllText($configBsl, [System.Text.Encoding]::UTF8) $configContent = [System.IO.File]::ReadAllText($configBsl, [System.Text.Encoding]::UTF8)
$allTransferred = $true $allTransferred = $true
foreach ($block in $insertBlocks) { foreach ($block in $insertBlocks) {
$code = $block.Code $code = $block.Code
if (-not $code) { continue } if (-not $code) { continue }
# Normalize whitespace for comparison # Normalize whitespace for comparison
$codeNorm = $code -replace '\s+', ' ' $codeNorm = $code -replace '\s+', ' '
$configNorm = $configContent -replace '\s+', ' ' $configNorm = $configContent -replace '\s+', ' '
if ($configNorm.Contains($codeNorm)) { if ($configNorm.Contains($codeNorm)) {
# Found in config # Found in config
} else { } else {
$allTransferred = $false $allTransferred = $false
} }
} }
if ($allTransferred) { if ($allTransferred) {
Write-Host " [TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — $($insertBlocks.Count) block(s)" Write-Host " [TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — $($insertBlocks.Count) block(s)"
$transferred++ $transferred++
} else { } else {
Write-Host " [NOT_TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — some blocks not found in config" Write-Host " [NOT_TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — some blocks not found in config"
$notTransferred++ $notTransferred++
} }
} }
} }
} }
Write-Host "" Write-Host ""
Write-Host "=== Transfer check: $transferred transferred, $notTransferred not transferred, $needsReview needs review ===" Write-Host "=== Transfer check: $transferred transferred, $notTransferred not transferred, $needsReview needs review ==="
} }
@@ -1,71 +1,71 @@
--- ---
name: cfe-init name: cfe-init
description: Создать расширение конфигурации 1С (CFE) — scaffold XML-исходников. Используй когда нужно создать новое расширение для исправления, доработки или дополнения конфигурации description: Создать расширение конфигурации 1С (CFE) — scaffold XML-исходников. Используй когда нужно создать новое расширение для исправления, доработки или дополнения конфигурации
argument-hint: <Name> [-ConfigPath <path>] [-Purpose Patch|Customization|AddOn] [-CompatibilityMode Version8_3_24] argument-hint: <Name> [-ConfigPath <path>] [-Purpose Patch|Customization|AddOn] [-CompatibilityMode Version8_3_24]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cfe-init — Создание расширения конфигурации 1С # /cfe-init — Создание расширения конфигурации 1С
Создаёт scaffold расширения: `Configuration.xml`, `Languages/Русский.xml`, опционально `Roles/`. Создаёт scaffold расширения: `Configuration.xml`, `Languages/Русский.xml`, опционально `Roles/`.
## Подготовка ## Подготовка
Если есть выгрузка базовой конфигурации, передай `-ConfigPath` — скрипт автоматически определит `CompatibilityMode` и UUID языка из базовой конфигурации. Если есть выгрузка базовой конфигурации, передай `-ConfigPath` — скрипт автоматически определит `CompatibilityMode` и UUID языка из базовой конфигурации.
### Авто-определение ConfigPath ### Авто-определение ConfigPath
Если пользователь не указал `-ConfigPath` — попробуй определить автоматически: Если пользователь не указал `-ConfigPath` — попробуй определить автоматически:
1. Прочитай `.v8-project.json` из корня проекта 1. Прочитай `.v8-project.json` из корня проекта
2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`) 2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`)
3. Если у базы есть поле `configSrc` — используй как `-ConfigPath` 3. Если у базы есть поле `configSrc` — используй как `-ConfigPath`
4. Если `configSrc` нет — спроси у пользователя 4. Если `configSrc` нет — спроси у пользователя
Если `.v8-project.json` не найден и `-ConfigPath` не задан — расширение создастся с предупреждением (UUID языка = нули, CompatibilityMode по умолчанию). Если `.v8-project.json` не найден и `-ConfigPath` не задан — расширение создастся с предупреждением (UUID языка = нули, CompatibilityMode по умолчанию).
## Параметры ## Параметры
| Параметр | Описание | По умолчанию | | Параметр | Описание | По умолчанию |
|----------|----------|--------------| |----------|----------|--------------|
| `Name` | Имя расширения (обязат.) | — | | `Name` | Имя расширения (обязат.) | — |
| `Synonym` | Синоним | = Name | | `Synonym` | Синоним | = Name |
| `NamePrefix` | Префикс собственных объектов | = Name + "_" | | `NamePrefix` | Префикс собственных объектов | = Name + "_" |
| `OutputDir` | Каталог для создания | `src` | | `OutputDir` | Каталог для создания | `src` |
| `Purpose` | `Patch` (исправление) / `Customization` (доработка) / `AddOn` (дополнение) | `Customization` | | `Purpose` | `Patch` (исправление) / `Customization` (доработка) / `AddOn` (дополнение) | `Customization` |
| `Version` | Версия расширения | — | | `Version` | Версия расширения | — |
| `Vendor` | Поставщик | — | | `Vendor` | Поставщик | — |
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` | | `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
| `ConfigPath` | Путь к выгрузке базовой конфигурации (авто-определяет CompatibilityMode и Language UUID) | — | | `ConfigPath` | Путь к выгрузке базовой конфигурации (авто-определяет CompatibilityMode и Language UUID) | — |
| `NoRole` | Без основной роли | false | | `NoRole` | Без основной роли | false |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-init.ps1" -Name "МоёРасширение" powershell.exe -NoProfile -File ".opencode/skills/cfe-init/scripts/cfe-init.ps1" -Name "МоёРасширение"
``` ```
## Примеры ## Примеры
```powershell ```powershell
# Расширение для ERP с авто-определением совместимости из базовой конфигурации # Расширение для ERP с авто-определением совместимости из базовой конфигурации
... -Name Расш1 -ConfigPath C:\WS\tasks\cfsrc\erp_8.3.24 -OutputDir src ... -Name Расш1 -ConfigPath C:\WS\tasks\cfsrc\erp_8.3.24 -OutputDir src
# Расширение-исправление с явным режимом совместимости # Расширение-исправление с явным режимом совместимости
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src ... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src
# Расширение-доработка с версией # Расширение-доработка с версией
... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src ... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src
# Без роли, с явным префиксом # Без роли, с явным префиксом
... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src ... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src
``` ```
## Верификация ## Верификация
``` ```
/cfe-validate <OutputDir> /cfe-validate <OutputDir>
``` ```
@@ -1,270 +1,279 @@
# cfe-init v1.1 — Create 1C configuration extension scaffold (CFE) # cfe-init v1.2 — Create 1C configuration extension scaffold (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$Name, [string]$Name,
[string]$Synonym = $Name, [string]$Synonym = $Name,
[string]$NamePrefix, [string]$NamePrefix,
[string]$OutputDir = "src", [string]$OutputDir = "src",
[ValidateSet("Patch","Customization","AddOn")] [ValidateSet("Patch","Customization","AddOn")]
[string]$Purpose = "Customization", [string]$Purpose = "Customization",
[string]$Version, [string]$Version,
[string]$Vendor, [string]$Vendor,
[string]$CompatibilityMode = "Version8_3_24", [string]$CompatibilityMode = "Version8_3_24",
[string]$ConfigPath, [string]$ConfigPath,
[switch]$NoRole [switch]$NoRole
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Default NamePrefix --- # --- Default NamePrefix ---
if (-not $NamePrefix) { if (-not $NamePrefix) {
$NamePrefix = "${Name}_" $NamePrefix = "${Name}_"
} }
# --- Resolve output dir --- # --- Resolve output dir ---
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) { if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
$OutputDir = Join-Path (Get-Location).Path $OutputDir $OutputDir = Join-Path (Get-Location).Path $OutputDir
} }
# --- Check existing --- # --- Check existing ---
$cfgFile = Join-Path $OutputDir "Configuration.xml" $cfgFile = Join-Path $OutputDir "Configuration.xml"
if (Test-Path $cfgFile) { if (Test-Path $cfgFile) {
Write-Error "Configuration.xml already exists: $cfgFile" Write-Error "Configuration.xml already exists: $cfgFile"
exit 1 exit 1
} }
# --- Resolve ConfigPath --- # MDClasses format version — inherited from the base config so the extension stays uniform
$baseLangUuid = "00000000-0000-0000-0000-000000000000" # with it (a 2.13 base must yield a 2.13 extension, else platform import rejects the mismatch).
if ($ConfigPath) { $formatVersion = "2.17"
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath # --- Resolve ConfigPath ---
} $baseLangUuid = "00000000-0000-0000-0000-000000000000"
if (Test-Path $ConfigPath -PathType Container) { if ($ConfigPath) {
$candidate = Join-Path $ConfigPath "Configuration.xml" if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
if (Test-Path $candidate) { $ConfigPath = $candidate } $ConfigPath = Join-Path (Get-Location).Path $ConfigPath
else { Write-Error "No Configuration.xml in config directory: $ConfigPath"; exit 1 } }
} if (Test-Path $ConfigPath -PathType Container) {
if (-not (Test-Path $ConfigPath)) { Write-Error "Config file not found: $ConfigPath"; exit 1 } $candidate = Join-Path $ConfigPath "Configuration.xml"
$cfgDir = Split-Path (Resolve-Path $ConfigPath).Path -Parent if (Test-Path $candidate) { $ConfigPath = $candidate }
else { Write-Error "No Configuration.xml in config directory: $ConfigPath"; exit 1 }
# 3a. Read Language UUID from base config }
$baseLangFile = Join-Path (Join-Path $cfgDir "Languages") "Русский.xml" if (-not (Test-Path $ConfigPath)) { Write-Error "Config file not found: $ConfigPath"; exit 1 }
if (Test-Path $baseLangFile) { $cfgDir = Split-Path (Resolve-Path $ConfigPath).Path -Parent
$baseLangDoc = New-Object System.Xml.XmlDocument
$baseLangDoc.PreserveWhitespace = $false # 3a. Read Language UUID from base config
$baseLangDoc.Load($baseLangFile) $baseLangFile = Join-Path (Join-Path $cfgDir "Languages") "Русский.xml"
$langEl = $null if (Test-Path $baseLangFile) {
foreach ($c in $baseLangDoc.DocumentElement.ChildNodes) { $baseLangDoc = New-Object System.Xml.XmlDocument
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq 'Language') { $langEl = $c; break } $baseLangDoc.PreserveWhitespace = $false
} $baseLangDoc.Load($baseLangFile)
if ($langEl) { $langEl = $null
$baseLangUuid = $langEl.GetAttribute("uuid") foreach ($c in $baseLangDoc.DocumentElement.ChildNodes) {
Write-Host "[INFO] Base config Language UUID: $baseLangUuid" if ($c.NodeType -eq 'Element' -and $c.LocalName -eq 'Language') { $langEl = $c; break }
} else { }
Write-Host "[WARN] No <Language> element in $baseLangFile" if ($langEl) {
} $baseLangUuid = $langEl.GetAttribute("uuid")
} else { Write-Host "[INFO] Base config Language UUID: $baseLangUuid"
Write-Host "[WARN] Base config language not found: $baseLangFile" } else {
} Write-Host "[WARN] No <Language> element in $baseLangFile"
}
# 3b. Read CompatibilityMode and InterfaceCompatibilityMode from base config } else {
$baseCfgDoc = New-Object System.Xml.XmlDocument Write-Host "[WARN] Base config language not found: $baseLangFile"
$baseCfgDoc.PreserveWhitespace = $false }
$baseCfgDoc.Load((Resolve-Path $ConfigPath).Path)
$baseCfgNs = New-Object System.Xml.XmlNamespaceManager($baseCfgDoc.NameTable) # 3b. Read CompatibilityMode and InterfaceCompatibilityMode from base config
$baseCfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") $baseCfgDoc = New-Object System.Xml.XmlDocument
$compatNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:CompatibilityMode", $baseCfgNs) $baseCfgDoc.PreserveWhitespace = $false
if ($compatNode -and $compatNode.InnerText) { $baseCfgDoc.Load((Resolve-Path $ConfigPath).Path)
$CompatibilityMode = $compatNode.InnerText.Trim() $baseCfgNs = New-Object System.Xml.XmlNamespaceManager($baseCfgDoc.NameTable)
Write-Host "[INFO] Base config CompatibilityMode: $CompatibilityMode" $baseCfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
} else { $fmtVer = $baseCfgDoc.DocumentElement.GetAttribute("version")
Write-Host "[WARN] CompatibilityMode not found in base config, using default: $CompatibilityMode" if ($fmtVer) {
} $formatVersion = $fmtVer
$ifcNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:InterfaceCompatibilityMode", $baseCfgNs) Write-Host "[INFO] Base config format version: $formatVersion"
if ($ifcNode -and $ifcNode.InnerText) { }
$InterfaceCompatibilityMode = $ifcNode.InnerText.Trim() $compatNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:CompatibilityMode", $baseCfgNs)
Write-Host "[INFO] Base config InterfaceCompatibilityMode: $InterfaceCompatibilityMode" if ($compatNode -and $compatNode.InnerText) {
} else { $CompatibilityMode = $compatNode.InnerText.Trim()
$InterfaceCompatibilityMode = "TaxiEnableVersion8_2" Write-Host "[INFO] Base config CompatibilityMode: $CompatibilityMode"
Write-Host "[WARN] InterfaceCompatibilityMode not found in base config, using default: $InterfaceCompatibilityMode" } else {
} Write-Host "[WARN] CompatibilityMode not found in base config, using default: $CompatibilityMode"
} else { }
$InterfaceCompatibilityMode = "TaxiEnableVersion8_2" $ifcNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:InterfaceCompatibilityMode", $baseCfgNs)
Write-Host "[WARN] Language ExtendedConfigurationObject set to zeros. Use -ConfigPath to auto-resolve from base config, or fix manually before loading." if ($ifcNode -and $ifcNode.InnerText) {
} $InterfaceCompatibilityMode = $ifcNode.InnerText.Trim()
Write-Host "[INFO] Base config InterfaceCompatibilityMode: $InterfaceCompatibilityMode"
# --- Generate UUIDs --- } else {
$uuidCfg = [guid]::NewGuid().ToString() $InterfaceCompatibilityMode = "TaxiEnableVersion8_2"
$uuidLang = [guid]::NewGuid().ToString() Write-Host "[WARN] InterfaceCompatibilityMode not found in base config, using default: $InterfaceCompatibilityMode"
$uuidRole = [guid]::NewGuid().ToString() }
} else {
# 7 ContainedObject ObjectIds $InterfaceCompatibilityMode = "TaxiEnableVersion8_2"
$co1 = [guid]::NewGuid().ToString() Write-Host "[WARN] Language ExtendedConfigurationObject set to zeros. Use -ConfigPath to auto-resolve from base config, or fix manually before loading."
$co2 = [guid]::NewGuid().ToString() }
$co3 = [guid]::NewGuid().ToString()
$co4 = [guid]::NewGuid().ToString() # --- Generate UUIDs ---
$co5 = [guid]::NewGuid().ToString() $uuidCfg = [guid]::NewGuid().ToString()
$co6 = [guid]::NewGuid().ToString() $uuidLang = [guid]::NewGuid().ToString()
$co7 = [guid]::NewGuid().ToString() $uuidRole = [guid]::NewGuid().ToString()
# --- Synonym XML --- # 7 ContainedObject ObjectIds
$synonymXml = "" $co1 = [guid]::NewGuid().ToString()
if ($Synonym) { $co2 = [guid]::NewGuid().ToString()
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t" $co3 = [guid]::NewGuid().ToString()
} $co4 = [guid]::NewGuid().ToString()
$co5 = [guid]::NewGuid().ToString()
# --- Optional properties --- $co6 = [guid]::NewGuid().ToString()
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" } $co7 = [guid]::NewGuid().ToString()
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
# --- Synonym XML ---
# --- Role name --- $synonymXml = ""
$roleName = "${NamePrefix}ОсновнаяРоль" if ($Synonym) {
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
# --- DefaultRoles XML --- }
$defaultRolesXml = ""
if (-not $NoRole) { # --- Optional properties ---
$defaultRolesXml = "`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t" $vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
} $versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
# --- ChildObjects --- # --- Role name ---
$childObjectsXml = "`r`n`t`t`t<Language>Русский</Language>" $roleName = "${NamePrefix}ОсновнаяРоль"
if (-not $NoRole) {
$childObjectsXml += "`r`n`t`t`t<Role>$roleName</Role>" # --- DefaultRoles XML ---
} $defaultRolesXml = ""
$childObjectsXml += "`r`n`t`t" if (-not $NoRole) {
$defaultRolesXml = "`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t"
# --- Configuration.xml --- }
$cfgXml = @"
<?xml version="1.0" encoding="UTF-8"?> # --- ChildObjects ---
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> $childObjectsXml = "`r`n`t`t`t<Language>Русский</Language>"
<Configuration uuid="$uuidCfg"> if (-not $NoRole) {
<InternalInfo> $childObjectsXml += "`r`n`t`t`t<Role>$roleName</Role>"
<xr:ContainedObject> }
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId> $childObjectsXml += "`r`n`t`t"
<xr:ObjectId>$co1</xr:ObjectId>
</xr:ContainedObject> # --- Configuration.xml ---
<xr:ContainedObject> $cfgXml = @"
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId> <?xml version="1.0" encoding="UTF-8"?>
<xr:ObjectId>$co2</xr:ObjectId> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
</xr:ContainedObject> <Configuration uuid="$uuidCfg">
<xr:ContainedObject> <InternalInfo>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId> <xr:ContainedObject>
<xr:ObjectId>$co3</xr:ObjectId> <xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
</xr:ContainedObject> <xr:ObjectId>$co1</xr:ObjectId>
<xr:ContainedObject> </xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId> <xr:ContainedObject>
<xr:ObjectId>$co4</xr:ObjectId> <xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
</xr:ContainedObject> <xr:ObjectId>$co2</xr:ObjectId>
<xr:ContainedObject> </xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId> <xr:ContainedObject>
<xr:ObjectId>$co5</xr:ObjectId> <xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
</xr:ContainedObject> <xr:ObjectId>$co3</xr:ObjectId>
<xr:ContainedObject> </xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId> <xr:ContainedObject>
<xr:ObjectId>$co6</xr:ObjectId> <xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
</xr:ContainedObject> <xr:ObjectId>$co4</xr:ObjectId>
<xr:ContainedObject> </xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId> <xr:ContainedObject>
<xr:ObjectId>$co7</xr:ObjectId> <xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
</xr:ContainedObject> <xr:ObjectId>$co5</xr:ObjectId>
</InternalInfo> </xr:ContainedObject>
<Properties> <xr:ContainedObject>
<ObjectBelonging>Adopted</ObjectBelonging> <xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name> <xr:ObjectId>$co6</xr:ObjectId>
<Synonym>$synonymXml</Synonym> </xr:ContainedObject>
<Comment/> <xr:ContainedObject>
<ConfigurationExtensionPurpose>$Purpose</ConfigurationExtensionPurpose> <xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs> <xr:ObjectId>$co7</xr:ObjectId>
<NamePrefix>$([System.Security.SecurityElement]::Escape($NamePrefix))</NamePrefix> </xr:ContainedObject>
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode> </InternalInfo>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <Properties>
<UsePurposes> <ObjectBelonging>Adopted</ObjectBelonging>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
</UsePurposes> <Synonym>$synonymXml</Synonym>
<ScriptVariant>Russian</ScriptVariant> <Comment/>
<DefaultRoles>$defaultRolesXml</DefaultRoles> <ConfigurationExtensionPurpose>$Purpose</ConfigurationExtensionPurpose>
<Vendor>$vendorXml</Vendor> <KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
<Version>$versionXml</Version> <NamePrefix>$([System.Security.SecurityElement]::Escape($NamePrefix))</NamePrefix>
<DefaultLanguage>Language.Русский</DefaultLanguage> <ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
<BriefInformation/> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<DetailedInformation/> <UsePurposes>
<Copyright/> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
<VendorInformationAddress/> </UsePurposes>
<ConfigurationInformationAddress/> <ScriptVariant>Russian</ScriptVariant>
<InterfaceCompatibilityMode>$InterfaceCompatibilityMode</InterfaceCompatibilityMode> <DefaultRoles>$defaultRolesXml</DefaultRoles>
</Properties> <Vendor>$vendorXml</Vendor>
<ChildObjects>$childObjectsXml</ChildObjects> <Version>$versionXml</Version>
</Configuration> <DefaultLanguage>Language.Русский</DefaultLanguage>
</MetaDataObject> <BriefInformation/>
"@ <DetailedInformation/>
<Copyright/>
# --- Languages/Русский.xml (adopted format) --- <VendorInformationAddress/>
$langXml = @" <ConfigurationInformationAddress/>
<?xml version="1.0" encoding="UTF-8"?> <InterfaceCompatibilityMode>$InterfaceCompatibilityMode</InterfaceCompatibilityMode>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> </Properties>
<Language uuid="$uuidLang"> <ChildObjects>$childObjectsXml</ChildObjects>
<InternalInfo/> </Configuration>
<Properties> </MetaDataObject>
<ObjectBelonging>Adopted</ObjectBelonging> "@
<Name>Русский</Name>
<Comment/> # --- Languages/Русский.xml (adopted format) ---
<ExtendedConfigurationObject>$baseLangUuid</ExtendedConfigurationObject> $langXml = @"
<LanguageCode>ru</LanguageCode> <?xml version="1.0" encoding="UTF-8"?>
</Properties> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
</Language> <Language uuid="$uuidLang">
</MetaDataObject> <InternalInfo/>
"@ <Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
# --- Role XML --- <Name>Русский</Name>
$roleXml = @" <Comment/>
<?xml version="1.0" encoding="UTF-8"?> <ExtendedConfigurationObject>$baseLangUuid</ExtendedConfigurationObject>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <LanguageCode>ru</LanguageCode>
<Role uuid="$uuidRole"> </Properties>
<Properties> </Language>
<Name>$([System.Security.SecurityElement]::Escape($roleName))</Name> </MetaDataObject>
<Synonym/> "@
<Comment/>
</Properties> # --- Role XML ---
</Role> $roleXml = @"
</MetaDataObject> <?xml version="1.0" encoding="UTF-8"?>
"@ <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
<Role uuid="$uuidRole">
# --- Create directories --- <Properties>
if (-not (Test-Path $OutputDir)) { <Name>$([System.Security.SecurityElement]::Escape($roleName))</Name>
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null <Synonym/>
} <Comment/>
$langDir = Join-Path $OutputDir "Languages" </Properties>
if (-not (Test-Path $langDir)) { </Role>
New-Item -ItemType Directory -Path $langDir -Force | Out-Null </MetaDataObject>
} "@
# --- Write files with UTF-8 BOM --- # --- Create directories ---
$enc = New-Object System.Text.UTF8Encoding($true) if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc) }
$langFile = Join-Path $langDir "Русский.xml" $langDir = Join-Path $OutputDir "Languages"
[System.IO.File]::WriteAllText($langFile, $langXml, $enc) if (-not (Test-Path $langDir)) {
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
# --- Role --- }
if (-not $NoRole) {
$roleDir = Join-Path $OutputDir "Roles" # --- Write files with UTF-8 BOM ---
if (-not (Test-Path $roleDir)) { $enc = New-Object System.Text.UTF8Encoding($true)
New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
} [System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
$roleFile = Join-Path $roleDir "$roleName.xml" $langFile = Join-Path $langDir "Русский.xml"
[System.IO.File]::WriteAllText($roleFile, $roleXml, $enc) [System.IO.File]::WriteAllText($langFile, $langXml, $enc)
}
# --- Role ---
# --- Output --- if (-not $NoRole) {
Write-Host "[OK] Создано расширение: $Name" $roleDir = Join-Path $OutputDir "Roles"
Write-Host " Каталог: $OutputDir" if (-not (Test-Path $roleDir)) {
Write-Host " Назначение: $Purpose" New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
Write-Host " Префикс: $NamePrefix" }
Write-Host " Совместимость: $CompatibilityMode" $roleFile = Join-Path $roleDir "$roleName.xml"
Write-Host " Configuration.xml: $cfgFile" [System.IO.File]::WriteAllText($roleFile, $roleXml, $enc)
Write-Host " Languages: $langFile" }
if (-not $NoRole) {
Write-Host " Role: $roleFile" # --- Output ---
} Write-Host "[OK] Создано расширение: $Name"
Write-Host " Каталог: $OutputDir"
Write-Host " Назначение: $Purpose"
Write-Host " Префикс: $NamePrefix"
Write-Host " Совместимость: $CompatibilityMode"
Write-Host " Configuration.xml: $cfgFile"
Write-Host " Languages: $langFile"
if (-not $NoRole) {
Write-Host " Role: $roleFile"
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cfe-init v1.1 — Create 1C configuration extension scaffold (CFE) # cfe-init v1.2 — Create 1C configuration extension scaffold (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Generates minimal XML source files for a 1C configuration extension.""" """Generates minimal XML source files for a 1C configuration extension."""
import sys, os, argparse, uuid import sys, os, argparse, uuid
@@ -50,6 +50,10 @@ def main():
print(f"Configuration.xml already exists: {cfg_file}", file=sys.stderr) print(f"Configuration.xml already exists: {cfg_file}", file=sys.stderr)
sys.exit(1) sys.exit(1)
# MDClasses format version — inherited from the base config so the extension stays uniform
# with it (a 2.13 base must yield a 2.13 extension, else platform import rejects the mismatch).
format_version = "2.17"
# --- Resolve ConfigPath --- # --- Resolve ConfigPath ---
base_lang_uuid = "00000000-0000-0000-0000-000000000000" base_lang_uuid = "00000000-0000-0000-0000-000000000000"
if args.ConfigPath: if args.ConfigPath:
@@ -88,6 +92,10 @@ def main():
try: try:
base_cfg_tree = ET.parse(os.path.abspath(config_path)) base_cfg_tree = ET.parse(os.path.abspath(config_path))
base_cfg_root = base_cfg_tree.getroot() base_cfg_root = base_cfg_tree.getroot()
fmt_ver = base_cfg_root.get("version")
if fmt_ver:
format_version = fmt_ver
print(f"[INFO] Base config format version: {format_version}")
ns = {'md': 'http://v8.1c.ru/8.3/MDClasses'} ns = {'md': 'http://v8.1c.ru/8.3/MDClasses'}
compat_node = base_cfg_root.find('.//md:Configuration/md:Properties/md:CompatibilityMode', ns) compat_node = base_cfg_root.find('.//md:Configuration/md:Properties/md:CompatibilityMode', ns)
if compat_node is not None and compat_node.text: if compat_node is not None and compat_node.text:
@@ -155,7 +163,7 @@ def main():
\t\t\t</xr:ContainedObject>\n""" \t\t\t</xr:ContainedObject>\n"""
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?> cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
\t<Configuration uuid="{uuid_cfg}"> \t<Configuration uuid="{uuid_cfg}">
\t\t<InternalInfo> \t\t<InternalInfo>
{contained_objects}\t\t</InternalInfo> {contained_objects}\t\t</InternalInfo>
@@ -190,7 +198,7 @@ def main():
# --- Languages/Русский.xml (adopted format) --- # --- Languages/Русский.xml (adopted format) ---
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?> lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
\t<Language uuid="{uuid_lang}"> \t<Language uuid="{uuid_lang}">
\t\t<InternalInfo/> \t\t<InternalInfo/>
\t\t<Properties> \t\t<Properties>
@@ -205,7 +213,7 @@ def main():
# --- Role XML --- # --- Role XML ---
role_xml = f'''<?xml version="1.0" encoding="UTF-8"?> role_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
\t<Role uuid="{uuid_role}"> \t<Role uuid="{uuid_role}">
\t\t<Properties> \t\t<Properties>
\t\t\t<Name>{esc_xml(role_name)}</Name> \t\t\t<Name>{esc_xml(role_name)}</Name>
@@ -1,78 +1,78 @@
--- ---
name: cfe-patch-method name: cfe-patch-method
description: Генерация перехватчика метода в расширении 1С (CFE). Используй когда нужно перехватить метод заимствованного объекта — вставить код до, после или вместо оригинального description: Генерация перехватчика метода в расширении 1С (CFE). Используй когда нужно перехватить метод заимствованного объекта — вставить код до, после или вместо оригинального
argument-hint: -ExtensionPath <path> -ModulePath "Catalog.X.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before argument-hint: -ExtensionPath <path> -ModulePath "Catalog.X.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cfe-patch-method — Генерация перехватчика метода # /cfe-patch-method — Генерация перехватчика метода
Генерирует `.bsl` файл с декоратором перехвата для заимствованного объекта расширения. Создаёт файл или дописывает в существующий. Генерирует `.bsl` файл с декоратором перехвата для заимствованного объекта расширения. Создаёт файл или дописывает в существующий.
## Предусловие ## Предусловие
Объект должен быть заимствован в расширение (`/cfe-borrow`). Скрипт читает `NamePrefix` из `Configuration.xml` расширения для формирования имени процедуры. Объект должен быть заимствован в расширение (`/cfe-borrow`). Скрипт читает `NamePrefix` из `Configuration.xml` расширения для формирования имени процедуры.
## Параметры ## Параметры
| Параметр | Описание | По умолчанию | | Параметр | Описание | По умолчанию |
|----------|----------|--------------| |----------|----------|--------------|
| `ExtensionPath` | Путь к расширению (обязат.) | — | | `ExtensionPath` | Путь к расширению (обязат.) | — |
| `ModulePath` | Путь к модулю (обязат.) | — | | `ModulePath` | Путь к модулю (обязат.) | — |
| `MethodName` | Имя перехватываемого метода (обязат.) | — | | `MethodName` | Имя перехватываемого метода (обязат.) | — |
| `InterceptorType` | `Before` / `After` / `ModificationAndControl` (обязат.) | — | | `InterceptorType` | `Before` / `After` / `ModificationAndControl` (обязат.) | — |
| `Context` | Директива контекста | `НаСервере` | | `Context` | Директива контекста | `НаСервере` |
| `IsFunction` | Метод — функция (добавит `Возврат`) | false | | `IsFunction` | Метод — функция (добавит `Возврат`) | false |
## Формат ModulePath ## Формат ModulePath
| ModulePath | Файл | | ModulePath | Файл |
|------------|------| |------------|------|
| `Catalog.X.ObjectModule` | `Catalogs/X/Ext/ObjectModule.bsl` | | `Catalog.X.ObjectModule` | `Catalogs/X/Ext/ObjectModule.bsl` |
| `Catalog.X.ManagerModule` | `Catalogs/X/Ext/ManagerModule.bsl` | | `Catalog.X.ManagerModule` | `Catalogs/X/Ext/ManagerModule.bsl` |
| `Catalog.X.Form.Y` | `Catalogs/X/Forms/Y/Ext/Form/Module.bsl` | | `Catalog.X.Form.Y` | `Catalogs/X/Forms/Y/Ext/Form/Module.bsl` |
| `CommonModule.X` | `CommonModules/X/Ext/Module.bsl` | | `CommonModule.X` | `CommonModules/X/Ext/Module.bsl` |
| `Document.X.ObjectModule` | `Documents/X/Ext/ObjectModule.bsl` | | `Document.X.ObjectModule` | `Documents/X/Ext/ObjectModule.bsl` |
| `Document.X.Form.Y` | `Documents/X/Forms/Y/Ext/Form/Module.bsl` | | `Document.X.Form.Y` | `Documents/X/Forms/Y/Ext/Form/Module.bsl` |
Аналогично для Report, DataProcessor, InformationRegister и других типов. Аналогично для Report, DataProcessor, InformationRegister и других типов.
## Типы перехвата ## Типы перехвата
| InterceptorType | Декоратор | Назначение | | InterceptorType | Декоратор | Назначение |
|-----------------|-----------|------------| |-----------------|-----------|------------|
| `Before` | `&Перед` | Код до вызова оригинального метода | | `Before` | `&Перед` | Код до вызова оригинального метода |
| `After` | `&После` | Код после вызова оригинального метода | | `After` | `&После` | Код после вызова оригинального метода |
| `ModificationAndControl` | `&ИзменениеИКонтроль` | Копия тела метода с маркерами `#Вставка`/`#Удаление` | | `ModificationAndControl` | `&ИзменениеИКонтроль` | Копия тела метода с маркерами `#Вставка`/`#Удаление` |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.ps1" -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before powershell.exe -NoProfile -File ".opencode/skills/cfe-patch-method/scripts/cfe-patch-method.ps1" -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
``` ```
## Примеры ## Примеры
```powershell ```powershell
# Перехват &Перед на сервере # Перехват &Перед на сервере
... -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before ... -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
# Перехват &После на клиенте # Перехват &После на клиенте
... -ExtensionPath src -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After -Context "НаКлиенте" ... -ExtensionPath src -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After -Context "НаКлиенте"
# ИзменениеИКонтроль для функции # ИзменениеИКонтроль для функции
... -ExtensionPath src -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType ModificationAndControl -IsFunction ... -ExtensionPath src -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType ModificationAndControl -IsFunction
``` ```
## Генерируемый код (Before) ## Генерируемый код (Before)
```bsl ```bsl
&НаСервере &НаСервере
&Перед("ПриЗаписи") &Перед("ПриЗаписи")
Процедура Расш1_ПриЗаписи() Процедура Расш1_ПриЗаписи()
// TODO: код перед вызовом оригинального метода // TODO: код перед вызовом оригинального метода
КонецПроцедуры КонецПроцедуры
``` ```
@@ -1,29 +1,29 @@
--- ---
name: cfe-validate name: cfe-validate
description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности
argument-hint: <ExtensionPath> [-Detailed] [-MaxErrors 30] argument-hint: <ExtensionPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /cfe-validate — валидация расширения конфигурации (CFE) # /cfe-validate — валидация расширения конфигурации (CFE)
Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты. Аналог `/cf-validate`, но для расширений. Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты. Аналог `/cf-validate`, но для расширений.
## Параметры ## Параметры
| Параметр | Обяз. | Умолч. | Описание | | Параметр | Обяз. | Умолч. | Описание |
|---------------|:-----:|---------|-------------------------------------------------| |---------------|:-----:|---------|-------------------------------------------------|
| ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения | | ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения |
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) | | Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл | | OutFile | нет | — | Записать результат в файл |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src" powershell.exe -NoProfile -File ".opencode/skills/cfe-validate/scripts/cfe-validate.ps1" -ExtensionPath "src"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src/Configuration.xml" powershell.exe -NoProfile -File ".opencode/skills/cfe-validate/scripts/cfe-validate.ps1" -ExtensionPath "src/Configuration.xml"
``` ```
@@ -1,70 +1,70 @@
--- ---
name: db-create name: db-create
description: Создание информационной базы 1С. Используй когда нужно создать базу, новую ИБ, пустую базу description: Создание информационной базы 1С. Используй когда нужно создать базу, новую ИБ, пустую базу
argument-hint: <path|name> argument-hint: <path|name>
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Write - Write
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-create — Создание информационной базы # /db-create — Создание информационной базы
Создаёт новую информационную базу 1С (файловую или серверную) и предлагает зарегистрировать в `.v8-project.json`. Создаёт новую информационную базу 1С (файловую или серверную) и предлагает зарегистрировать в `.v8-project.json`.
## Usage ## Usage
``` ```
/db-create <path> — файловая база по указанному пути /db-create <path> — файловая база по указанному пути
/db-create <server>/<name> — серверная база /db-create <server>/<name> — серверная база
/db-create — интерактивно /db-create — интерактивно
``` ```
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта для `v8path` (путь к платформе). Прочитай `.v8-project.json` из корня проекта для `v8path` (путь к платформе).
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
После создания базы предложи зарегистрировать через `/db-list add`. После создания базы предложи зарегистрировать через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Путь к файловой базе | | `-InfoBasePath <путь>` | * | Путь к файловой базе |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UseTemplate <файл>` | нет | Создать из шаблона (.cf или .dt) | | `-UseTemplate <файл>` | нет | Создать из шаблона (.cf или .dt) |
| `-AddToList` | нет | Добавить в список баз 1С | | `-AddToList` | нет | Добавить в список баз 1С |
| `-ListName <имя>` | нет | Имя базы в списке | | `-ListName <имя>` | нет | Имя базы в списке |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
## После создания ## После создания
Предложи зарегистрировать базу в `.v8-project.json` (через `/db-list add`) Предложи зарегистрировать базу в `.v8-project.json` (через `/db-list add`)
3. Если указан шаблон `/UseTemplate` — предупреди что конфигурация будет загружена из шаблона 3. Если указан шаблон `/UseTemplate` — предупреди что конфигурация будет загружена из шаблона
## Примеры ## Примеры
```powershell ```powershell
# Создать файловую базу # Создать файловую базу
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB"
# Создать серверную базу # Создать серверную базу
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
# Создать из шаблона CF # Создать из шаблона CF
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf" powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf"
# Создать и добавить в список баз # Создать и добавить в список баз
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база" powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база"
``` ```
@@ -1,188 +1,220 @@
# db-create v1.1 — Create 1C information base # db-create v1.4 — Create 1C information base
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Создание информационной базы 1С Создание информационной базы 1С
.DESCRIPTION .DESCRIPTION
Создаёт новую информационную базу 1С (файловую или серверную). Создаёт новую информационную базу 1С (файловую или серверную).
Поддерживает создание из шаблона и добавление в список баз. Поддерживает создание из шаблона и добавление в список баз.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UseTemplate .PARAMETER UseTemplate
Путь к файлу шаблона (.cf или .dt) Путь к файлу шаблона (.cf или .dt)
.PARAMETER AddToList .PARAMETER AddToList
Добавить в список баз 1С Добавить в список баз 1С
.PARAMETER ListName .PARAMETER ListName
Имя базы в списке Имя базы в списке
.EXAMPLE .EXAMPLE
.\db-create.ps1 -InfoBasePath "C:\Bases\NewDB" .\db-create.ps1 -InfoBasePath "C:\Bases\NewDB"
.EXAMPLE .EXAMPLE
.\db-create.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" .\db-create.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
.EXAMPLE .EXAMPLE
.\db-create.ps1 -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf" -AddToList -ListName "Новая база" .\db-create.ps1 -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf" -AddToList -ListName "Новая база"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UseTemplate, [string]$UseTemplate,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$AddToList, [switch]$AddToList,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$ListName [string]$ListName
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Validate connection --- # --- Detect engine (ibcmd vs 1cv8) by exe name ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 # --- Validate connection ---
} if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
# --- Validate template --- Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
if ($UseTemplate -and -not (Test-Path $UseTemplate)) { exit 1
Write-Host "Error: template file not found: $UseTemplate" -ForegroundColor Red }
exit 1 } elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
} Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1
# --- Temp dir --- }
$tempDir = Join-Path $env:TEMP "db_create_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null # --- Validate template ---
if ($UseTemplate -and -not (Test-Path $UseTemplate)) {
try { Write-Host "Error: template file not found: $UseTemplate" -ForegroundColor Red
# --- Build arguments --- exit 1
$arguments = @("CREATEINFOBASE") }
if ($InfoBaseServer -and $InfoBaseRef) { # --- Temp dir ---
$arguments += "Srvr=`"$InfoBaseServer`";Ref=`"$InfoBaseRef`"" $tempDir = Join-Path $env:TEMP "db_create_$(Get-Random)"
} else { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
$arguments += "File=`"$InfoBasePath`""
} try {
if ($engine -eq "ibcmd") {
# --- Template --- # --- ibcmd branch (file infobase only) ---
if ($UseTemplate) { $arguments = @("infobase", "create", "--db-path=$InfoBasePath", "--create-database")
$arguments += "/UseTemplate", "`"$UseTemplate`"" if ($UseTemplate) {
} if ([System.IO.Path]::GetExtension($UseTemplate) -ieq ".dt") {
$arguments += "--restore=$UseTemplate"
# --- Add to list --- } else {
if ($AddToList) { $arguments += "--load=$UseTemplate", "--apply"
if ($ListName) { }
$arguments += "/AddToList", "`"$ListName`"" }
} else { $arguments += "--data=$tempDir"
$arguments += "/AddToList" Write-Host "Running: ibcmd $($arguments -join ' ')"
} $output = & $V8Path @arguments 2>&1
} $exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
# --- Output --- Write-Host "Information base created successfully: $InfoBasePath" -ForegroundColor Green
$outFile = Join-Path $tempDir "create_log.txt" } else {
$arguments += "/Out", "`"$outFile`"" Write-Host "Error creating information base (code: $exitCode)" -ForegroundColor Red
$arguments += "/DisableStartupDialogs" }
if ($output) { Write-Host ($output | Out-String) }
# --- Execute --- exit $exitCode
Write-Host "Running: 1cv8.exe $($arguments -join ' ')" }
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode # --- 1cv8 branch ---
# --- Build arguments ---
# --- Result --- $arguments = @("CREATEINFOBASE")
if ($exitCode -eq 0) {
if ($InfoBaseServer -and $InfoBaseRef) { if ($InfoBaseServer -and $InfoBaseRef) {
Write-Host "Information base created successfully: $InfoBaseServer/$InfoBaseRef" -ForegroundColor Green $arguments += "Srvr=`"$InfoBaseServer`";Ref=`"$InfoBaseRef`""
} else { } else {
Write-Host "Information base created successfully: $InfoBasePath" -ForegroundColor Green $arguments += "File=`"$InfoBasePath`""
} }
} else {
Write-Host "Error creating information base (code: $exitCode)" -ForegroundColor Red # --- Template ---
} if ($UseTemplate) {
$arguments += "/UseTemplate", "`"$UseTemplate`""
if (Test-Path $outFile) { }
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) { # --- Add to list ---
Write-Host "--- Log ---" if ($AddToList) {
Write-Host $logContent if ($ListName) {
Write-Host "--- End ---" $arguments += "/AddToList", "`"$ListName`""
} } else {
} $arguments += "/AddToList"
}
exit $exitCode }
} finally { # --- Output ---
if (Test-Path $tempDir) { $outFile = Join-Path $tempDir "create_log.txt"
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue $arguments += "/Out", "`"$outFile`""
} $arguments += "/DisableStartupDialogs"
}
# --- Execute ---
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
# --- Result ---
if ($exitCode -eq 0) {
if ($InfoBaseServer -and $InfoBaseRef) {
Write-Host "Information base created successfully: $InfoBaseServer/$InfoBaseRef" -ForegroundColor Green
} else {
Write-Host "Information base created successfully: $InfoBasePath" -ForegroundColor Green
}
} else {
Write-Host "Error creating information base (code: $exitCode)" -ForegroundColor Red
}
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-create v1.1 — Create 1C information base # db-create v1.4 — Create 1C information base
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -82,9 +83,14 @@ def main():
args = parser.parse_args() args = parser.parse_args()
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate connection --- # --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -93,6 +99,29 @@ def main():
print(f"Error: template file not found: {args.UseTemplate}", file=sys.stderr) print(f"Error: template file not found: {args.UseTemplate}", file=sys.stderr)
sys.exit(1) sys.exit(1)
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
arguments = ["infobase", "create", f"--db-path={args.InfoBasePath}", "--create-database"]
if args.UseTemplate:
if os.path.splitext(args.UseTemplate)[1].lower() == ".dt":
arguments.append(f"--restore={args.UseTemplate}")
else:
arguments.extend([f"--load={args.UseTemplate}", "--apply"])
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print(f"Information base created successfully: {args.InfoBasePath}")
else:
print(f"Error creating information base (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Temp dir --- # --- Temp dir ---
temp_dir = os.path.join(tempfile.gettempdir(), f"db_create_{random.randint(0, 999999)}") temp_dir = os.path.join(tempfile.gettempdir(), f"db_create_{random.randint(0, 999999)}")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
@@ -102,9 +131,11 @@ def main():
arguments = ["CREATEINFOBASE"] arguments = ["CREATEINFOBASE"]
if args.InfoBaseServer and args.InfoBaseRef: if args.InfoBaseServer and args.InfoBaseRef:
arguments.append(f'Srvr="{args.InfoBaseServer}";Ref="{args.InfoBaseRef}"') # No embedded quotes: subprocess quotes the whole token; 1C's argv parser
# strips outer quotes. Inner quotes get escaped by list2cmdline and break parsing.
arguments.append(f'Srvr={args.InfoBaseServer};Ref={args.InfoBaseRef}')
else: else:
arguments.append(f'File="{args.InfoBasePath}"') arguments.append(f'File={args.InfoBasePath}')
# --- Template --- # --- Template ---
if args.UseTemplate: if args.UseTemplate:
@@ -1,68 +1,68 @@
--- ---
name: db-dump-cf name: db-dump-cf
description: Выгрузка конфигурации 1С в CF-файл. Используй когда нужно выгрузить конфигурацию в CF, сохранить конфигурацию, сделать бэкап CF description: Выгрузка конфигурации 1С в CF-файл. Используй когда нужно выгрузить конфигурацию в CF, сохранить конфигурацию, сделать бэкап CF
argument-hint: "[database] [output.cf]" argument-hint: "[database] [output.cf]"
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-dump-cf — Выгрузка конфигурации в CF-файл # /db-dump-cf — Выгрузка конфигурации в CF-файл
Выгружает конфигурацию информационной базы в бинарный CF-файл. Выгружает конфигурацию информационной базы в бинарный CF-файл.
## Usage ## Usage
``` ```
/db-dump-cf [database] [output.cf] /db-dump-cf [database] [output.cf]
/db-dump-cf dev config.cf /db-dump-cf dev config.cf
/db-dump-cf — база по умолчанию, файл config.cf /db-dump-cf — база по умолчанию, файл config.cf
``` ```
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-dump-cf/scripts/db-dump-cf.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-OutputFile <путь>` | да | Путь к выходному CF-файлу | | `-OutputFile <путь>` | да | Путь к выходному CF-файлу |
| `-Extension <имя>` | нет | Выгрузить расширение | | `-Extension <имя>` | нет | Выгрузить расширение |
| `-AllExtensions` | нет | Выгрузить все расширения | | `-AllExtensions` | нет | Выгрузить все расширения |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
## Примеры ## Примеры
```powershell ```powershell
# Выгрузка конфигурации (файловая база) # Выгрузка конфигурации (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf" powershell.exe -NoProfile -File ".opencode/skills/db-dump-cf/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf" powershell.exe -NoProfile -File ".opencode/skills/db-dump-cf/scripts/db-dump-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf"
# Выгрузка расширения # Выгрузка расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение" powershell.exe -NoProfile -File ".opencode/skills/db-dump-cf/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение"
``` ```
@@ -1,191 +1,224 @@
# db-dump-cf v1.1 — Dump 1C configuration to CF file # db-dump-cf v1.4 — Dump 1C configuration to CF file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Выгрузка конфигурации 1С в CF-файл Выгрузка конфигурации 1С в CF-файл
.DESCRIPTION .DESCRIPTION
Выгружает конфигурацию информационной базы в бинарный CF-файл. Выгружает конфигурацию информационной базы в бинарный CF-файл.
Поддерживает выгрузку расширений. Поддерживает выгрузку расширений.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER OutputFile .PARAMETER OutputFile
Путь к выходному CF-файлу Путь к выходному CF-файлу
.PARAMETER Extension .PARAMETER Extension
Имя расширения для выгрузки Имя расширения для выгрузки
.PARAMETER AllExtensions .PARAMETER AllExtensions
Выгрузить все расширения Выгрузить все расширения
.EXAMPLE .EXAMPLE
.\db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "config.cf" .\db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "config.cf"
.EXAMPLE .EXAMPLE
.\db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "ext.cfe" -Extension "МоёРасширение" .\db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "ext.cfe" -Extension "МоёРасширение"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$OutputFile, [string]$OutputFile,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Extension, [string]$Extension,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$AllExtensions [switch]$AllExtensions
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Validate connection --- # --- Detect engine (ibcmd vs 1cv8) by exe name ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 # --- Validate connection ---
} if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
# --- Ensure output directory exists --- Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
$outDir = Split-Path $OutputFile -Parent exit 1
if ($outDir -and -not (Test-Path $outDir)) { }
New-Item -ItemType Directory -Path $outDir -Force | Out-Null } elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
} Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1
# --- Temp dir --- }
$tempDir = Join-Path $env:TEMP "db_dump_cf_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null # --- Ensure output directory exists ---
$outDir = Split-Path $OutputFile -Parent
try { if ($outDir -and -not (Test-Path $outDir)) {
# --- Build arguments --- New-Item -ItemType Directory -Path $outDir -Force | Out-Null
$arguments = @("DESIGNER") }
if ($InfoBaseServer -and $InfoBaseRef) { # --- Temp dir ---
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`"" $tempDir = Join-Path $env:TEMP "db_dump_cf_$(Get-Random)"
} else { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
$arguments += "/F", "`"$InfoBasePath`""
} try {
if ($engine -eq "ibcmd") {
if ($UserName) { $arguments += "/N`"$UserName`"" } # --- ibcmd branch (file infobase only) ---
if ($Password) { $arguments += "/P`"$Password`"" } if ($AllExtensions) {
Write-Host "Error: ibcmd config save does not support -AllExtensions (use -Extension)" -ForegroundColor Red
$arguments += "/DumpCfg", "`"$OutputFile`"" exit 1
}
# --- Extensions --- $arguments = @("infobase", "config", "save", "--db-path=$InfoBasePath")
if ($Extension) { if ($Extension) { $arguments += "--extension=$Extension" }
$arguments += "-Extension", "`"$Extension`"" $arguments += "$OutputFile"
} elseif ($AllExtensions) { if ($UserName) { $arguments += "--user=$UserName" }
$arguments += "-AllExtensions" if ($Password) { $arguments += "--password=$Password" }
} $arguments += "--data=$tempDir"
Write-Host "Running: ibcmd $($arguments -join ' ')"
# --- Output --- $output = & $V8Path @arguments 2>&1
$outFile = Join-Path $tempDir "dump_cf_log.txt" $exitCode = $LASTEXITCODE
$arguments += "/Out", "`"$outFile`"" if ($exitCode -eq 0) {
$arguments += "/DisableStartupDialogs" Write-Host "Configuration dumped successfully to: $OutputFile" -ForegroundColor Green
} else {
# --- Execute --- Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
Write-Host "Running: 1cv8.exe $($arguments -join ' ')" }
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru if ($output) { Write-Host ($output | Out-String) }
$exitCode = $process.ExitCode exit $exitCode
}
# --- Result ---
if ($exitCode -eq 0) { # --- 1cv8 branch ---
Write-Host "Configuration dumped successfully to: $OutputFile" -ForegroundColor Green # --- Build arguments ---
} else { $arguments = @("DESIGNER")
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
} if ($InfoBaseServer -and $InfoBaseRef) {
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
if (Test-Path $outFile) { } else {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue $arguments += "/F", "`"$InfoBasePath`""
if ($logContent) { }
Write-Host "--- Log ---"
Write-Host $logContent if ($UserName) { $arguments += "/N`"$UserName`"" }
Write-Host "--- End ---" if ($Password) { $arguments += "/P`"$Password`"" }
}
} $arguments += "/DumpCfg", "`"$OutputFile`""
exit $exitCode # --- Extensions ---
if ($Extension) {
} finally { $arguments += "-Extension", "`"$Extension`""
if (Test-Path $tempDir) { } elseif ($AllExtensions) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue $arguments += "-AllExtensions"
} }
}
# --- Output ---
$outFile = Join-Path $tempDir "dump_cf_log.txt"
$arguments += "/Out", "`"$outFile`""
$arguments += "/DisableStartupDialogs"
# --- Execute ---
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
# --- Result ---
if ($exitCode -eq 0) {
Write-Host "Configuration dumped successfully to: $OutputFile" -ForegroundColor Green
} else {
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
}
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-dump-cf v1.1 — Dump 1C configuration to CF file # db-dump-cf v1.4 — Dump 1C configuration to CF file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -84,9 +85,14 @@ def main():
args = parser.parse_args() args = parser.parse_args()
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate connection --- # --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -95,6 +101,34 @@ def main():
if out_dir and not os.path.isdir(out_dir): if out_dir and not os.path.isdir(out_dir):
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
if args.AllExtensions:
print("Error: ibcmd config save does not support -AllExtensions (use -Extension)", file=sys.stderr)
sys.exit(1)
arguments = ["infobase", "config", "save", f"--db-path={args.InfoBasePath}"]
if args.Extension:
arguments.append(f"--extension={args.Extension}")
arguments.append(args.OutputFile)
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print(f"Configuration dumped successfully to: {args.OutputFile}")
else:
print(f"Error dumping configuration (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Temp dir --- # --- Temp dir ---
temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_cf_{random.randint(0, 999999)}") temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_cf_{random.randint(0, 999999)}")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
@@ -1,72 +1,72 @@
--- ---
name: db-dump-dt name: db-dump-dt
description: Выгрузка информационной базы 1С в DT-файл (вся база — конфигурация + данные). Используй когда нужно выгрузить информационную базу, выгрузить архив базы, сделать бэкап, выгрузить dt description: Выгрузка информационной базы 1С в DT-файл (вся база — конфигурация + данные). Используй когда нужно выгрузить информационную базу, выгрузить архив базы, сделать бэкап, выгрузить dt
argument-hint: "[database] [output.dt]" argument-hint: "[database] [output.dt]"
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-dump-dt — Выгрузка информационной базы в DT-файл # /db-dump-dt — Выгрузка информационной базы в DT-файл
Выгружает информационную базу целиком (конфигурация **+ данные**) в DT-файл — полный снимок ИБ. Выгружает информационную базу целиком (конфигурация **+ данные**) в DT-файл — полный снимок ИБ.
> В отличие от `/db-dump-cf` (только конфигурация), `.dt` содержит **всю базу**: данные, > В отличие от `/db-dump-cf` (только конфигурация), `.dt` содержит **всю базу**: данные,
> настройки, пользователей. Это бэкап/точка отката, а не выгрузка метаданных. > настройки, пользователей. Это бэкап/точка отката, а не выгрузка метаданных.
## Usage ## Usage
``` ```
/db-dump-dt [database] [output.dt] /db-dump-dt [database] [output.dt]
/db-dump-dt dev backup.dt /db-dump-dt dev backup.dt
/db-dump-dt — база по умолчанию, имя файла по базе и дате /db-dump-dt — база по умолчанию, имя файла по базе и дате
``` ```
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-dump-dt/scripts/db-dump-dt.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-OutputFile <путь>` | да | Путь к выходному DT-файлу | | `-OutputFile <путь>` | да | Путь к выходному DT-файлу |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
## Примеры ## Примеры
```powershell ```powershell
# Выгрузка ИБ (файловая база) # Выгрузка ИБ (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\base.dt" powershell.exe -NoProfile -File ".opencode/skills/db-dump-dt/scripts/db-dump-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\base.dt"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "base.dt" powershell.exe -NoProfile -File ".opencode/skills/db-dump-dt/scripts/db-dump-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "base.dt"
``` ```
## Связанные навыки ## Связанные навыки
- `/db-load-dt` — загрузка ИБ из DT (обратная операция) - `/db-load-dt` — загрузка ИБ из DT (обратная операция)
- `/db-dump-cf` — выгрузка только конфигурации (без данных) - `/db-dump-cf` — выгрузка только конфигурации (без данных)
- `/db-create` — создать новую базу (в т.ч. из DT-шаблона) - `/db-create` — создать новую базу (в т.ч. из DT-шаблона)
@@ -1,4 +1,4 @@
# db-dump-dt v1.1 — Dump 1C information base to DT file # db-dump-dt v1.3 — Dump 1C information base to DT file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
@@ -102,8 +102,16 @@ if (-not (Test-Path $V8Path)) {
exit 1 exit 1
} }
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
# --- Validate connection --- # --- Validate connection ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
exit 1
}
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 exit 1
} }
@@ -119,6 +127,27 @@ $tempDir = Join-Path $env:TEMP "db_dump_dt_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
try { try {
if ($engine -eq "ibcmd") {
# --- ibcmd branch (file infobase only) ---
$arguments = @("infobase", "dump", "--db-path=$InfoBasePath")
if ($UserName) { $arguments += "--user=$UserName" }
if ($Password) { $arguments += "--password=$Password" }
$arguments += "$OutputFile"
$arguments += "--data=$tempDir"
Write-Host "Running: ibcmd $($arguments -join ' ')"
$output = & $V8Path @arguments 2>&1
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
Write-Host "Information base dumped successfully to: $OutputFile" -ForegroundColor Green
} else {
Write-Host "Error dumping information base (code: $exitCode)" -ForegroundColor Red
}
if ($output) { Write-Host ($output | Out-String) }
exit $exitCode
}
# --- 1cv8 branch ---
# --- Build arguments --- # --- Build arguments ---
$arguments = @("DESIGNER") $arguments = @("DESIGNER")
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-dump-dt v1.1 — Dump 1C information base to DT file # db-dump-dt v1.3 — Dump 1C information base to DT file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -82,9 +83,14 @@ def main():
args = parser.parse_args() args = parser.parse_args()
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate connection --- # --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -93,6 +99,29 @@ def main():
if out_dir and not os.path.isdir(out_dir): if out_dir and not os.path.isdir(out_dir):
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
arguments = ["infobase", "dump", f"--db-path={args.InfoBasePath}"]
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(args.OutputFile)
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print(f"Information base dumped successfully to: {args.OutputFile}")
else:
print(f"Error dumping information base (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Temp dir --- # --- Temp dir ---
temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_dt_{random.randint(0, 999999)}") temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_dt_{random.randint(0, 999999)}")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
@@ -1,90 +1,90 @@
--- ---
name: db-dump-xml name: db-dump-xml
description: Выгрузка конфигурации 1С в XML-файлы. Используй когда нужно выгрузить конфигурацию в файлы, XML, исходники, DumpConfigToFiles description: Выгрузка конфигурации 1С в XML-файлы. Используй когда нужно выгрузить конфигурацию в файлы, XML, исходники, DumpConfigToFiles
argument-hint: "[database] [outputDir]" argument-hint: "[database] [outputDir]"
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-dump-xml — Выгрузка конфигурации в XML # /db-dump-xml — Выгрузка конфигурации в XML
Выгружает конфигурацию информационной базы в XML-файлы (исходники). Поддерживает полную, инкрементальную, частичную выгрузку и обновление ConfigDumpInfo. Выгружает конфигурацию информационной базы в XML-файлы (исходники). Поддерживает полную, инкрементальную, частичную выгрузку и обновление ConfigDumpInfo.
## Usage ## Usage
``` ```
/db-dump-xml [database] [outputDir] /db-dump-xml [database] [outputDir]
/db-dump-xml dev src/config /db-dump-xml dev src/config
/db-dump-xml dev src/config -Mode Full /db-dump-xml dev src/config -Mode Full
/db-dump-xml dev src/config -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ" /db-dump-xml dev src/config -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
``` ```
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
Если в записи базы указан `configSrc` — используй как каталог выгрузки по умолчанию. Если в записи базы указан `configSrc` — используй как каталог выгрузки по умолчанию.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-ConfigDir <путь>` | да | Каталог для выгрузки | | `-ConfigDir <путь>` | да | Каталог для выгрузки |
| `-Mode <режим>` | нет | `Full` / `Changes` (по умолч.) / `Partial` / `UpdateInfo` | | `-Mode <режим>` | нет | `Full` / `Changes` (по умолч.) / `Partial` / `UpdateInfo` |
| `-Objects <список>` | для Partial | Имена объектов через запятую | | `-Objects <список>` | для Partial | Имена объектов через запятую |
| `-Extension <имя>` | нет | Выгрузить расширение | | `-Extension <имя>` | нет | Выгрузить расширение |
| `-AllExtensions` | нет | Выгрузить все расширения | | `-AllExtensions` | нет | Выгрузить все расширения |
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` | | `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
### Режимы выгрузки ### Режимы выгрузки
| Режим | Описание | | Режим | Описание |
|-------|----------| |-------|----------|
| `Full` | Полная выгрузка — все объекты конфигурации | | `Full` | Полная выгрузка — все объекты конфигурации |
| `Changes` | Инкрементальная — только изменённые с последней выгрузки (использует ConfigDumpInfo.xml) | | `Changes` | Инкрементальная — только изменённые с последней выгрузки (использует ConfigDumpInfo.xml) |
| `Partial` | Частичная — выбранные объекты из параметра `-Objects` | | `Partial` | Частичная — выбранные объекты из параметра `-Objects` |
| `UpdateInfo` | Обновить только ConfigDumpInfo.xml без выгрузки файлов | | `UpdateInfo` | Обновить только ConfigDumpInfo.xml без выгрузки файлов |
> Если пользователь просит выгрузить конкретные объекты — используй `-Mode Partial` с `-Objects`. > Если пользователь просит выгрузить конкретные объекты — используй `-Mode Partial` с `-Objects`.
## Примеры ## Примеры
```powershell ```powershell
# Полная выгрузка (файловая база) # Полная выгрузка (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
# Инкрементальная выгрузка # Инкрементальная выгрузка
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes
# Частичная выгрузка # Частичная выгрузка
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ" powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full
# Выгрузка расширения # Выгрузка расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение" powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
``` ```
@@ -1,249 +1,294 @@
# db-dump-xml v1.1 — Dump 1C configuration to XML files # db-dump-xml v1.6 — Dump 1C configuration to XML files
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Выгрузка конфигурации 1С в XML-файлы Выгрузка конфигурации 1С в XML-файлы
.DESCRIPTION .DESCRIPTION
Выполняет выгрузку конфигурации 1С в файлы в четырёх режимах: Выполняет выгрузку конфигурации 1С в файлы в четырёх режимах:
- Full: полная выгрузка всей конфигурации - Full: полная выгрузка всей конфигурации
- Changes: инкрементальная выгрузка изменённых объектов - Changes: инкрементальная выгрузка изменённых объектов
- Partial: выгрузка конкретных объектов из списка - Partial: выгрузка конкретных объектов из списка
- UpdateInfo: обновление только ConfigDumpInfo.xml - UpdateInfo: обновление только ConfigDumpInfo.xml
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER ConfigDir .PARAMETER ConfigDir
Каталог для выгрузки конфигурации Каталог для выгрузки конфигурации
.PARAMETER Mode .PARAMETER Mode
Режим выгрузки: Full, Changes, Partial, UpdateInfo (по умолчанию Changes) Режим выгрузки: Full, Changes, Partial, UpdateInfo (по умолчанию Changes)
.PARAMETER Objects .PARAMETER Objects
Имена объектов метаданных через запятую (для режима Partial) Имена объектов метаданных через запятую (для режима Partial)
.PARAMETER Extension .PARAMETER Extension
Имя расширения для выгрузки Имя расширения для выгрузки
.PARAMETER AllExtensions .PARAMETER AllExtensions
Выгрузить все расширения Выгрузить все расширения
.PARAMETER Format .PARAMETER Format
Формат выгрузки: Hierarchical или Plain (по умолчанию Hierarchical) Формат выгрузки: Hierarchical или Plain (по умолчанию Hierarchical)
.EXAMPLE .EXAMPLE
.\db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Full .\db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Full
.EXAMPLE .EXAMPLE
.\db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ" .\db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$ConfigDir, [string]$ConfigDir,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ValidateSet("Full", "Changes", "Partial", "UpdateInfo")] [ValidateSet("Full", "Changes", "Partial", "UpdateInfo")]
[string]$Mode = "Changes", [string]$Mode = "Changes",
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Objects, [string]$Objects,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Extension, [string]$Extension,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$AllExtensions, [switch]$AllExtensions,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ValidateSet("Hierarchical", "Plain")] [ValidateSet("Hierarchical", "Plain")]
[string]$Format = "Hierarchical" [string]$Format = "Hierarchical"
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Validate connection --- # --- Detect engine (ibcmd vs 1cv8) by exe name ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 # --- Validate connection ---
} if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
# --- Validate Partial mode --- Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
if ($Mode -eq "Partial" -and -not $Objects) { exit 1
Write-Host "Error: -Objects required for Partial mode" -ForegroundColor Red }
exit 1 } elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
} Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1
# --- Create output dir if needed --- }
if (-not (Test-Path $ConfigDir)) {
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null # --- Validate Partial mode ---
Write-Host "Created output directory: $ConfigDir" if ($Mode -eq "Partial" -and -not $Objects) {
} Write-Host "Error: -Objects required for Partial mode" -ForegroundColor Red
exit 1
# --- Temp dir --- }
$tempDir = Join-Path $env:TEMP "db_dump_xml_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null # --- Create output dir if needed ---
if (-not (Test-Path $ConfigDir)) {
try { New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
# --- Build arguments --- Write-Host "Created output directory: $ConfigDir"
$arguments = @("DESIGNER") }
if ($InfoBaseServer -and $InfoBaseRef) { # --- Temp dir ---
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`"" $tempDir = Join-Path $env:TEMP "db_dump_xml_$(Get-Random)"
} else { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
$arguments += "/F", "`"$InfoBasePath`""
} try {
if ($engine -eq "ibcmd") {
if ($UserName) { $arguments += "/N`"$UserName`"" } # --- ibcmd branch (file infobase only; hierarchical Full/Changes) ---
if ($Password) { $arguments += "/P`"$Password`"" } if ($Format -eq "Plain") {
Write-Host "Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
$arguments += "/DumpConfigToFiles", "`"$ConfigDir`"" exit 1
$arguments += "-Format", $Format }
if ($AllExtensions) {
switch ($Mode) { $arguments = @("infobase", "config", "export", "all-extensions", "$ConfigDir", "--db-path=$InfoBasePath")
"Full" { } elseif ($Mode -eq "UpdateInfo") {
Write-Host "Executing full configuration dump..." Write-Host "Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8" -ForegroundColor Red
} exit 1
"Changes" { } elseif ($Mode -eq "Partial") {
Write-Host "Executing incremental configuration dump..." $objList = @($Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$arguments += "-update" $arguments = @("infobase", "config", "export", "objects") + $objList
$arguments += "-force" $arguments += "--out=$ConfigDir", "--db-path=$InfoBasePath"
} if ($Extension) { $arguments += "--extension=$Extension" }
"Partial" { } else {
Write-Host "Executing partial configuration dump..." $arguments = @("infobase", "config", "export", "--db-path=$InfoBasePath")
$objectList = $Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ } if ($Extension) { $arguments += "--extension=$Extension" }
$arguments += "$ConfigDir"
$listFile = Join-Path $tempDir "dump_list.txt" }
$utf8Bom = New-Object System.Text.UTF8Encoding($true) if ($UserName) { $arguments += "--user=$UserName" }
[System.IO.File]::WriteAllLines($listFile, $objectList, $utf8Bom) if ($Password) { $arguments += "--password=$Password" }
$arguments += "--data=$tempDir"
$arguments += "-listFile", "`"$listFile`"" Write-Host "Running: ibcmd $($arguments -join ' ')"
Write-Host "Objects to dump: $($objectList.Count)" $output = & $V8Path @arguments 2>&1
foreach ($obj in $objectList) { Write-Host " $obj" } $exitCode = $LASTEXITCODE
} if ($exitCode -eq 0) {
"UpdateInfo" { Write-Host "Configuration exported successfully to: $ConfigDir" -ForegroundColor Green
Write-Host "Updating ConfigDumpInfo.xml..." } else {
$arguments += "-configDumpInfoOnly" Write-Host "Error exporting configuration (code: $exitCode)" -ForegroundColor Red
} }
} if ($output) { Write-Host ($output | Out-String) }
exit $exitCode
# --- Extensions --- }
if ($Extension) {
$arguments += "-Extension", "`"$Extension`"" # --- 1cv8 branch ---
} elseif ($AllExtensions) { # --- Build arguments ---
$arguments += "-AllExtensions" $arguments = @("DESIGNER")
}
if ($InfoBaseServer -and $InfoBaseRef) {
# --- Output --- $arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
$outFile = Join-Path $tempDir "dump_log.txt" } else {
$arguments += "/Out", "`"$outFile`"" $arguments += "/F", "`"$InfoBasePath`""
$arguments += "/DisableStartupDialogs" }
# --- Execute --- if ($UserName) { $arguments += "/N`"$UserName`"" }
Write-Host "Running: 1cv8.exe $($arguments -join ' ')" if ($Password) { $arguments += "/P`"$Password`"" }
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode $arguments += "/DumpConfigToFiles", "`"$ConfigDir`""
$arguments += "-Format", $Format
# --- Result ---
if ($exitCode -eq 0) { switch ($Mode) {
Write-Host "Dump completed successfully" -ForegroundColor Green "Full" {
Write-Host "Configuration dumped to: $ConfigDir" Write-Host "Executing full configuration dump..."
} else { }
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red "Changes" {
} Write-Host "Executing incremental configuration dump..."
$arguments += "-update"
if (Test-Path $outFile) { $arguments += "-force"
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue }
if ($logContent) { "Partial" {
Write-Host "--- Log ---" Write-Host "Executing partial configuration dump..."
Write-Host $logContent $objectList = $Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
Write-Host "--- End ---"
} $listFile = Join-Path $tempDir "dump_list.txt"
} $utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllLines($listFile, $objectList, $utf8Bom)
exit $exitCode
$arguments += "-listFile", "`"$listFile`""
} finally { Write-Host "Objects to dump: $($objectList.Count)"
if (Test-Path $tempDir) { foreach ($obj in $objectList) { Write-Host " $obj" }
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue }
} "UpdateInfo" {
} Write-Host "Updating ConfigDumpInfo.xml..."
$arguments += "-configDumpInfoOnly"
}
}
# --- Extensions ---
if ($Extension) {
$arguments += "-Extension", "`"$Extension`""
} elseif ($AllExtensions) {
$arguments += "-AllExtensions"
}
# --- Output ---
$outFile = Join-Path $tempDir "dump_log.txt"
$arguments += "/Out", "`"$outFile`""
$arguments += "/DisableStartupDialogs"
# --- Execute ---
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
# --- Result ---
if ($exitCode -eq 0) {
Write-Host "Dump completed successfully" -ForegroundColor Green
Write-Host "Configuration dumped to: $ConfigDir"
} else {
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
}
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-dump-xml v1.1 — Dump 1C configuration to XML files # db-dump-xml v1.6 — Dump 1C configuration to XML files
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -98,9 +99,14 @@ def main():
# --- Resolve V8Path --- # --- Resolve V8Path ---
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate connection --- # --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -114,6 +120,46 @@ def main():
os.makedirs(args.ConfigDir, exist_ok=True) os.makedirs(args.ConfigDir, exist_ok=True)
print(f"Created output directory: {args.ConfigDir}") print(f"Created output directory: {args.ConfigDir}")
# --- ibcmd branch (file infobase only; hierarchical Full/Changes) ---
if engine == "ibcmd":
if args.Format == "Plain":
print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
sys.exit(1)
if args.AllExtensions:
arguments = ["infobase", "config", "export", "all-extensions", args.ConfigDir, f"--db-path={args.InfoBasePath}"]
elif args.Mode == "UpdateInfo":
print("Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8", file=sys.stderr)
sys.exit(1)
elif args.Mode == "Partial":
obj_list = [o.strip() for o in args.Objects.split(",") if o.strip()]
arguments = ["infobase", "config", "export", "objects"] + obj_list
arguments += [f"--out={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
if args.Extension:
arguments.append(f"--extension={args.Extension}")
else:
arguments = ["infobase", "config", "export", f"--db-path={args.InfoBasePath}"]
if args.Extension:
arguments.append(f"--extension={args.Extension}")
arguments.append(args.ConfigDir)
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print(f"Configuration exported successfully to: {args.ConfigDir}")
else:
print(f"Error exporting configuration (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Temp dir --- # --- Temp dir ---
temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_xml_{random.randint(0, 999999)}") temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_xml_{random.randint(0, 999999)}")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
@@ -1,158 +1,158 @@
--- ---
name: db-list name: db-list
description: Управление реестром баз данных 1С (.v8-project.json). Используй когда нужно работать с реестром баз — список баз, зарегистрировать базу в реестре, какие базы есть description: Управление реестром баз данных 1С (.v8-project.json). Используй когда нужно работать с реестром баз — список баз, зарегистрировать базу в реестре, какие базы есть
argument-hint: "[add|remove|show]" argument-hint: "[add|remove|show]"
allowed-tools: allowed-tools:
- Read - Read
- Write - Write
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-list — Управление реестром баз данных # /db-list — Управление реестром баз данных
Управляет файлом `.v8-project.json` — реестром информационных баз проекта. Файл хранит параметры подключения, алиасы, привязку к веткам Git. Управляет файлом `.v8-project.json` — реестром информационных баз проекта. Файл хранит параметры подключения, алиасы, привязку к веткам Git.
## Usage ## Usage
``` ```
/db-list — показать список баз /db-list — показать список баз
/db-list add — добавить базу (интерактивно) /db-list add — добавить базу (интерактивно)
/db-list remove <id> — удалить базу из реестра /db-list remove <id> — удалить базу из реестра
/db-list show <id|alias> — подробности по базе /db-list show <id|alias> — подробности по базе
``` ```
## Формат `.v8-project.json` ## Формат `.v8-project.json`
Файл размещается в корне проекта (рядом с `.git/`). Файл размещается в корне проекта (рядом с `.git/`).
```json ```json
{ {
"v8path": "C:\\Program Files\\1cv8\\8.3.25.1257\\bin", "v8path": "C:\\Program Files\\1cv8\\8.3.25.1257\\bin",
"databases": [ "databases": [
{ {
"id": "dev", "id": "dev",
"name": "Разработка", "name": "Разработка",
"type": "file", "type": "file",
"path": "C:\\Bases\\MyApp_Dev", "path": "C:\\Bases\\MyApp_Dev",
"user": "Admin", "user": "Admin",
"password": "", "password": "",
"aliases": ["dev", "разработка"], "aliases": ["dev", "разработка"],
"branches": ["dev", "develop", "feature/*"], "branches": ["dev", "develop", "feature/*"],
"configSrc": "C:\\WS\\myapp\\cfsrc" "configSrc": "C:\\WS\\myapp\\cfsrc"
}, },
{ {
"id": "test", "id": "test",
"name": "Тестовая", "name": "Тестовая",
"type": "server", "type": "server",
"server": "srv01", "server": "srv01",
"ref": "MyApp_Test", "ref": "MyApp_Test",
"user": "Admin", "user": "Admin",
"password": "123", "password": "123",
"aliases": ["test", "тест"] "aliases": ["test", "тест"]
} }
], ],
"default": "dev" "default": "dev"
} }
``` ```
### Поля корневого объекта ### Поля корневого объекта
| Поле | Тип | Описание | | Поле | Тип | Описание |
|------|-----|----------| |------|-----|----------|
| `v8path` | string | Каталог bin платформы 1С. Необязательный — если не задан, автоопределение | | `v8path` | string | Каталог bin платформы 1С. Необязательный — если не задан, автоопределение |
| `databases` | array | Массив баз данных | | `databases` | array | Массив баз данных |
| `default` | string | id базы по умолчанию | | `default` | string | id базы по умолчанию |
### Поля объекта базы данных ### Поля объекта базы данных
| Поле | Тип | Обязательное | Описание | | Поле | Тип | Обязательное | Описание |
|------|-----|:------------:|----------| |------|-----|:------------:|----------|
| `id` | string | да | Уникальный идентификатор (латиница, без пробелов) | | `id` | string | да | Уникальный идентификатор (латиница, без пробелов) |
| `name` | string | да | Человекочитаемое имя | | `name` | string | да | Человекочитаемое имя |
| `type` | `"file"` / `"server"` | да | Тип подключения | | `type` | `"file"` / `"server"` | да | Тип подключения |
| `path` | string | для file | Путь к каталогу файловой базы | | `path` | string | для file | Путь к каталогу файловой базы |
| `server` | string | для server | Адрес сервера 1С | | `server` | string | для server | Адрес сервера 1С |
| `ref` | string | для server | Имя базы на сервере | | `ref` | string | для server | Имя базы на сервере |
| `user` | string | нет | Имя пользователя 1С | | `user` | string | нет | Имя пользователя 1С |
| `password` | string | нет | Пароль | | `password` | string | нет | Пароль |
| `aliases` | string[] | нет | Альтернативные имена для быстрого доступа | | `aliases` | string[] | нет | Альтернативные имена для быстрого доступа |
| `branches` | string[] | нет | Git-ветки или glob-паттерны (`release/*`, `feature/*`), привязанные к этой базе | | `branches` | string[] | нет | Git-ветки или glob-паттерны (`release/*`, `feature/*`), привязанные к этой базе |
| `configSrc` | string | нет | Каталог XML-выгрузки конфигурации | | `configSrc` | string | нет | Каталог XML-выгрузки конфигурации |
## Алгоритм разрешения базы данных ## Алгоритм разрешения базы данных
Этот алгоритм используется ВСЕМИ навыками (`db-*`, `epf-build`, `epf-dump`, `erf-build`, `erf-dump`) для определения целевой базы. Этот алгоритм используется ВСЕМИ навыками (`db-*`, `epf-build`, `epf-dump`, `erf-build`, `erf-dump`) для определения целевой базы.
1. Если пользователь указал **параметры подключения** (путь, сервер) — используй напрямую 1. Если пользователь указал **параметры подключения** (путь, сервер) — используй напрямую
2. Если пользователь указал **базу по имени** — ищи совпадение в таком порядке: 2. Если пользователь указал **базу по имени** — ищи совпадение в таком порядке:
1. По `id` (точное совпадение) 1. По `id` (точное совпадение)
2. По `aliases` (совпадение в массиве с учётом морфологии: «тестовую» = «тестовая» = «тестовой») 2. По `aliases` (совпадение в массиве с учётом морфологии: «тестовую» = «тестовая» = «тестовой»)
3. По `name` (нечёткое совпадение с учётом морфологии и регистра) 3. По `name` (нечёткое совпадение с учётом морфологии и регистра)
3. Если пользователь **не указал** базу — сопоставь текущую ветку Git с `databases[].branches`: 3. Если пользователь **не указал** базу — сопоставь текущую ветку Git с `databases[].branches`:
- Точное совпадение: ветка `dev``"branches": ["dev"]` - Точное совпадение: ветка `dev``"branches": ["dev"]`
- Glob-паттерн: ветка `release/2.1``"branches": ["release/*"]` - Glob-паттерн: ветка `release/2.1``"branches": ["release/*"]`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
5. Если не найдено или неоднозначно — спроси пользователя 5. Если не найдено или неоднозначно — спроси пользователя
6. Если файл `.v8-project.json` не найден — спроси параметры подключения и предложи создать файл 6. Если файл `.v8-project.json` не найден — спроси параметры подключения и предложи создать файл
После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`. После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`.
### Автоопределение платформы ### Автоопределение платформы
Если `v8path` не задан в конфиге: Если `v8path` не задан в конфиге:
```powershell ```powershell
$v8 = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort-Object -Descending | Select-Object -First 1 $v8 = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort-Object -Descending | Select-Object -First 1
``` ```
## Операции ## Операции
### Показать список баз ### Показать список баз
Прочитай `.v8-project.json`, выведи таблицу: Прочитай `.v8-project.json`, выведи таблицу:
``` ```
ID Имя Тип Путь/Сервер По умолч. ID Имя Тип Путь/Сервер По умолч.
dev Разработка file C:\Bases\MyApp_Dev ✓ dev Разработка file C:\Bases\MyApp_Dev ✓
test Тестовая server srv01/MyApp_Test test Тестовая server srv01/MyApp_Test
``` ```
### Добавить базу ### Добавить базу
Спроси у пользователя через AskUserQuestion: Спроси у пользователя через AskUserQuestion:
- id, name, type (file/server) - id, name, type (file/server)
- path (для file) или server + ref (для server) - path (для file) или server + ref (для server)
- user, password (необязательно) - user, password (необязательно)
- aliases, branches (необязательно) - aliases, branches (необязательно)
Добавь в массив `databases`. Если это первая база — установи как `default`. Добавь в массив `databases`. Если это первая база — установи как `default`.
### Удалить базу ### Удалить базу
Удали из массива `databases` по id. Если удаляемая была `default` — спросить новый default. Удали из массива `databases` по id. Если удаляемая была `default` — спросить новый default.
### Подробности по базе ### Подробности по базе
Выведи все поля конкретной базы. Выведи все поля конкретной базы.
## Формирование строки подключения ## Формирование строки подключения
Для использования в шаблонах команд других навыков: Для использования в шаблонах команд других навыков:
**Файловая база:** **Файловая база:**
``` ```
/F "<path>" /F "<path>"
``` ```
**Серверная база:** **Серверная база:**
``` ```
/S "<server>/<ref>" /S "<server>/<ref>"
``` ```
**Аутентификация** (добавляется если user задан): **Аутентификация** (добавляется если user задан):
``` ```
/N"<user>" /P"<password>" /N"<user>" /P"<password>"
``` ```
> **Важно**: между `/N` и именем пробела нет. Между `/P` и паролем пробела нет. Если пароль пустой — опусти `/P` целиком. > **Важно**: между `/N` и именем пробела нет. Между `/P` и паролем пробела нет. Если пароль пустой — опусти `/P` целиком.
@@ -1,73 +1,73 @@
--- ---
name: db-load-cf name: db-load-cf
description: Загрузка конфигурации 1С из CF-файла. Используй когда нужно загрузить конфигурацию из CF, восстановить из бэкапа CF description: Загрузка конфигурации 1С из CF-файла. Используй когда нужно загрузить конфигурацию из CF, восстановить из бэкапа CF
argument-hint: <input.cf> [database] argument-hint: <input.cf> [database]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-load-cf — Загрузка конфигурации из CF-файла # /db-load-cf — Загрузка конфигурации из CF-файла
Загружает конфигурацию из бинарного CF-файла в информационную базу. Загружает конфигурацию из бинарного CF-файла в информационную базу.
## Usage ## Usage
``` ```
/db-load-cf <input.cf> [database] /db-load-cf <input.cf> [database]
/db-load-cf config.cf dev /db-load-cf config.cf dev
``` ```
> **Внимание**: загрузка CF **полностью заменяет** конфигурацию в базе. Перед выполнением запроси подтверждение у пользователя. > **Внимание**: загрузка CF **полностью заменяет** конфигурацию в базе. Перед выполнением запроси подтверждение у пользователя.
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-load-cf/scripts/db-load-cf.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-InputFile <путь>` | да | Путь к CF-файлу | | `-InputFile <путь>` | да | Путь к CF-файлу |
| `-Extension <имя>` | нет | Загрузить как расширение | | `-Extension <имя>` | нет | Загрузить как расширение |
| `-AllExtensions` | нет | Загрузить все расширения из архива | | `-AllExtensions` | нет | Загрузить все расширения из архива |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
## После выполнения ## После выполнения
**Предложи выполнить `/db-update`** — загрузка CF обновляет только «основную» конфигурацию конфигуратора, для применения к БД нужен `/UpdateDBCfg` **Предложи выполнить `/db-update`** — загрузка CF обновляет только «основную» конфигурацию конфигуратора, для применения к БД нужен `/UpdateDBCfg`
## Примеры ## Примеры
```powershell ```powershell
# Файловая база # Файловая база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf" powershell.exe -NoProfile -File ".opencode/skills/db-load-cf/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf" powershell.exe -NoProfile -File ".opencode/skills/db-load-cf/scripts/db-load-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf"
# Загрузка расширения # Загрузка расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение" powershell.exe -NoProfile -File ".opencode/skills/db-load-cf/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение"
``` ```
@@ -1,191 +1,224 @@
# db-load-cf v1.1 — Load 1C configuration from CF file # db-load-cf v1.4 — Load 1C configuration from CF file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Загрузка конфигурации 1С из CF-файла Загрузка конфигурации 1С из CF-файла
.DESCRIPTION .DESCRIPTION
Загружает конфигурацию из бинарного CF-файла в информационную базу. Загружает конфигурацию из бинарного CF-файла в информационную базу.
Поддерживает загрузку расширений. Поддерживает загрузку расширений.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER InputFile .PARAMETER InputFile
Путь к CF-файлу для загрузки Путь к CF-файлу для загрузки
.PARAMETER Extension .PARAMETER Extension
Загрузить как расширение Загрузить как расширение
.PARAMETER AllExtensions .PARAMETER AllExtensions
Загрузить все расширения из архива Загрузить все расширения из архива
.EXAMPLE .EXAMPLE
.\db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "config.cf" .\db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "config.cf"
.EXAMPLE .EXAMPLE
.\db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "ext.cfe" -Extension "МоёРасширение" .\db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "ext.cfe" -Extension "МоёРасширение"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$InputFile, [string]$InputFile,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Extension, [string]$Extension,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$AllExtensions [switch]$AllExtensions
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Validate connection --- # --- Detect engine (ibcmd vs 1cv8) by exe name ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 # --- Validate connection ---
} if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
# --- Validate input file --- Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
if (-not (Test-Path $InputFile)) { exit 1
Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red }
exit 1 } elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
} Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1
# --- Temp dir --- }
$tempDir = Join-Path $env:TEMP "db_load_cf_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null # --- Validate input file ---
if (-not (Test-Path $InputFile)) {
try { Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red
# --- Build arguments --- exit 1
$arguments = @("DESIGNER") }
if ($InfoBaseServer -and $InfoBaseRef) { # --- Temp dir ---
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`"" $tempDir = Join-Path $env:TEMP "db_load_cf_$(Get-Random)"
} else { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
$arguments += "/F", "`"$InfoBasePath`""
} try {
if ($engine -eq "ibcmd") {
if ($UserName) { $arguments += "/N`"$UserName`"" } # --- ibcmd branch (file infobase only) ---
if ($Password) { $arguments += "/P`"$Password`"" } if ($AllExtensions) {
Write-Host "Error: ibcmd config load does not support -AllExtensions (use -Extension)" -ForegroundColor Red
$arguments += "/LoadCfg", "`"$InputFile`"" exit 1
}
# --- Extensions --- $arguments = @("infobase", "config", "load", "--db-path=$InfoBasePath")
if ($Extension) { if ($Extension) { $arguments += "--extension=$Extension" }
$arguments += "-Extension", "`"$Extension`"" $arguments += "$InputFile"
} elseif ($AllExtensions) { if ($UserName) { $arguments += "--user=$UserName" }
$arguments += "-AllExtensions" if ($Password) { $arguments += "--password=$Password" }
} $arguments += "--data=$tempDir"
Write-Host "Running: ibcmd $($arguments -join ' ')"
# --- Output --- $output = & $V8Path @arguments 2>&1
$outFile = Join-Path $tempDir "load_cf_log.txt" $exitCode = $LASTEXITCODE
$arguments += "/Out", "`"$outFile`"" if ($exitCode -eq 0) {
$arguments += "/DisableStartupDialogs" Write-Host "Configuration loaded successfully from: $InputFile" -ForegroundColor Green
} else {
# --- Execute --- Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
Write-Host "Running: 1cv8.exe $($arguments -join ' ')" }
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru if ($output) { Write-Host ($output | Out-String) }
$exitCode = $process.ExitCode exit $exitCode
}
# --- Result ---
if ($exitCode -eq 0) { # --- 1cv8 branch ---
Write-Host "Configuration loaded successfully from: $InputFile" -ForegroundColor Green # --- Build arguments ---
} else { $arguments = @("DESIGNER")
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
} if ($InfoBaseServer -and $InfoBaseRef) {
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
if (Test-Path $outFile) { } else {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue $arguments += "/F", "`"$InfoBasePath`""
if ($logContent) { }
Write-Host "--- Log ---"
Write-Host $logContent if ($UserName) { $arguments += "/N`"$UserName`"" }
Write-Host "--- End ---" if ($Password) { $arguments += "/P`"$Password`"" }
}
} $arguments += "/LoadCfg", "`"$InputFile`""
exit $exitCode # --- Extensions ---
if ($Extension) {
} finally { $arguments += "-Extension", "`"$Extension`""
if (Test-Path $tempDir) { } elseif ($AllExtensions) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue $arguments += "-AllExtensions"
} }
}
# --- Output ---
$outFile = Join-Path $tempDir "load_cf_log.txt"
$arguments += "/Out", "`"$outFile`""
$arguments += "/DisableStartupDialogs"
# --- Execute ---
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
# --- Result ---
if ($exitCode -eq 0) {
Write-Host "Configuration loaded successfully from: $InputFile" -ForegroundColor Green
} else {
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
}
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-load-cf v1.1 — Load 1C configuration from CF file # db-load-cf v1.4 — Load 1C configuration from CF file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -84,9 +85,14 @@ def main():
args = parser.parse_args() args = parser.parse_args()
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate connection --- # --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -95,6 +101,34 @@ def main():
print(f"Error: input file not found: {args.InputFile}", file=sys.stderr) print(f"Error: input file not found: {args.InputFile}", file=sys.stderr)
sys.exit(1) sys.exit(1)
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
if args.AllExtensions:
print("Error: ibcmd config load does not support -AllExtensions (use -Extension)", file=sys.stderr)
sys.exit(1)
arguments = ["infobase", "config", "load", f"--db-path={args.InfoBasePath}"]
if args.Extension:
arguments.append(f"--extension={args.Extension}")
arguments.append(args.InputFile)
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print(f"Configuration loaded successfully from: {args.InputFile}")
else:
print(f"Error loading configuration (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Temp dir --- # --- Temp dir ---
temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_cf_{random.randint(0, 999999)}") temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_cf_{random.randint(0, 999999)}")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
@@ -1,92 +1,92 @@
--- ---
name: db-load-dt name: db-load-dt
description: Загрузка информационной базы 1С из DT-файла — полная перезапись базы (конфигурация + данные). Используй когда нужно загрузить архив информационной базы, восстановить базу, загрузить dt description: Загрузка информационной базы 1С из DT-файла — полная перезапись базы (конфигурация + данные). Используй когда нужно загрузить архив информационной базы, восстановить базу, загрузить dt
disable-model-invocation: true disable-model-invocation: true
argument-hint: <input.dt> [database] argument-hint: <input.dt> [database]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-load-dt — Загрузка информационной базы из DT-файла # /db-load-dt — Загрузка информационной базы из DT-файла
Восстанавливает информационную базу целиком (конфигурация **+ данные**) из DT-файла. Восстанавливает информационную базу целиком (конфигурация **+ данные**) из DT-файла.
> ⚠️ **Необратимая операция.** Загрузка `.dt` **полностью перезаписывает базу** — и > ⚠️ **Необратимая операция.** Загрузка `.dt` **полностью перезаписывает базу** — и
> конфигурацию, и все данные. Текущее содержимое базы будет потеряно. После загрузки > конфигурацию, и все данные. Текущее содержимое базы будет потеряно. После загрузки
> `/db-update` **не нужен** — конфигурация БД уже синхронна внутри снимка. > `/db-update` **не нужен** — конфигурация БД уже синхронна внутри снимка.
## Когда НЕ использовать ## Когда НЕ использовать
- Нужно создать **новую** базу из `.dt` → используй `/db-create` (из DT-шаблона), а не загрузку - Нужно создать **новую** базу из `.dt` → используй `/db-create` (из DT-шаблона), а не загрузку
в существующую. в существующую.
- Нужно обновить только конфигурацию (без данных) → `/db-load-cf` или `/db-load-xml`. - Нужно обновить только конфигурацию (без данных) → `/db-load-cf` или `/db-load-xml`.
## Usage ## Usage
``` ```
/db-load-dt <input.dt> [database] /db-load-dt <input.dt> [database]
/db-load-dt backup.dt dev /db-load-dt backup.dt dev
``` ```
## Порядок действий перед загрузкой ## Порядок действий перед загрузкой
1. Предложи пользователю сначала сделать `/db-dump-dt` текущего состояния базы — это точка 1. Предложи пользователю сначала сделать `/db-dump-dt` текущего состояния базы — это точка
отката (восстановиться будет нечем, если не сохранить). отката (восстановиться будет нечем, если не сохранить).
2. Запроси **явное подтверждение**: вся база (данные + конфигурация) будет перезаписана. 2. Запроси **явное подтверждение**: вся база (данные + конфигурация) будет перезаписана.
3. Только после подтверждения выполняй загрузку. 3. Только после подтверждения выполняй загрузку.
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-load-dt/scripts/db-load-dt.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-InputFile <путь>` | да | Путь к DT-файлу | | `-InputFile <путь>` | да | Путь к DT-файлу |
| `-JobsCount <N>` | нет | Число фоновых заданий загрузки (0 = по числу процессоров) | | `-JobsCount <N>` | нет | Число фоновых заданий загрузки (0 = по числу процессоров) |
| `-UnlockCode <код>` | нет | Код разблокировки (`/UC`), если заблокировано начало сеансов | | `-UnlockCode <код>` | нет | Код разблокировки (`/UC`), если заблокировано начало сеансов |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
## После выполнения ## После выполнения
Если база занята (активные сеансы), загрузка не выполнится — для серверной базы можно Если база занята (активные сеансы), загрузка не выполнится — для серверной базы можно
передать `-UnlockCode`; иначе освободи базу и повтори. передать `-UnlockCode`; иначе освободи базу и повтори.
## Примеры ## Примеры
```powershell ```powershell
# Файловая база # Файловая база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\base.dt" powershell.exe -NoProfile -File ".opencode/skills/db-load-dt/scripts/db-load-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\base.dt"
# Серверная база с ускорением загрузки # Серверная база с ускорением загрузки
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "base.dt" -JobsCount 4 powershell.exe -NoProfile -File ".opencode/skills/db-load-dt/scripts/db-load-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "base.dt" -JobsCount 4
``` ```
## Связанные навыки ## Связанные навыки
- `/db-dump-dt` — выгрузка ИБ в DT (обратная операция, точка отката перед загрузкой) - `/db-dump-dt` — выгрузка ИБ в DT (обратная операция, точка отката перед загрузкой)
- `/db-create` — создать новую базу (в т.ч. из DT-шаблона) - `/db-create` — создать новую базу (в т.ч. из DT-шаблона)
@@ -1,4 +1,4 @@
# db-load-dt v1.1 — Load 1C information base from DT file # db-load-dt v1.3 — Load 1C information base from DT file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
@@ -115,8 +115,16 @@ if (-not (Test-Path $V8Path)) {
exit 1 exit 1
} }
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
# --- Validate connection --- # --- Validate connection ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
exit 1
}
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 exit 1
} }
@@ -132,6 +140,28 @@ $tempDir = Join-Path $env:TEMP "db_load_dt_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
try { try {
if ($engine -eq "ibcmd") {
# --- ibcmd branch (file infobase only) ---
$arguments = @("infobase", "restore", "--db-path=$InfoBasePath")
if (-not (Test-Path (Join-Path $InfoBasePath "1Cv8.1CD"))) { $arguments += "--create-database" }
if ($UserName) { $arguments += "--user=$UserName" }
if ($Password) { $arguments += "--password=$Password" }
$arguments += "$InputFile"
$arguments += "--data=$tempDir"
Write-Host "Running: ibcmd $($arguments -join ' ')"
$output = & $V8Path @arguments 2>&1
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
Write-Host "Information base restored successfully from: $InputFile" -ForegroundColor Green
} else {
Write-Host "Error restoring information base (code: $exitCode)" -ForegroundColor Red
}
if ($output) { Write-Host ($output | Out-String) }
exit $exitCode
}
# --- 1cv8 branch ---
# --- Build arguments --- # --- Build arguments ---
$arguments = @("DESIGNER") $arguments = @("DESIGNER")
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-load-dt v1.1 — Load 1C information base from DT file # db-load-dt v1.3 — Load 1C information base from DT file
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -84,9 +85,14 @@ def main():
args = parser.parse_args() args = parser.parse_args()
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate connection --- # --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -95,6 +101,31 @@ def main():
print(f"Error: input file not found: {args.InputFile}", file=sys.stderr) print(f"Error: input file not found: {args.InputFile}", file=sys.stderr)
sys.exit(1) sys.exit(1)
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
arguments = ["infobase", "restore", f"--db-path={args.InfoBasePath}"]
if not os.path.isfile(os.path.join(args.InfoBasePath, "1Cv8.1CD")):
arguments.append("--create-database")
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(args.InputFile)
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print(f"Information base restored successfully from: {args.InputFile}")
else:
print(f"Error restoring information base (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Temp dir --- # --- Temp dir ---
temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_dt_{random.randint(0, 999999)}") temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_dt_{random.randint(0, 999999)}")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
@@ -1,77 +1,77 @@
--- ---
name: db-load-git name: db-load-git
description: Загрузка изменений из Git в базу 1С. Используй когда нужно загрузить изменения из гита, обновить базу из репозитория, partial load из коммита description: Загрузка изменений из Git в базу 1С. Используй когда нужно загрузить изменения из гита, обновить базу из репозитория, partial load из коммита
argument-hint: "[database] [source]" argument-hint: "[database] [source]"
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-load-git — Загрузка изменений из Git # /db-load-git — Загрузка изменений из Git
Определяет изменённые файлы конфигурации по данным Git и выполняет частичную загрузку в информационную базу. Определяет изменённые файлы конфигурации по данным Git и выполняет частичную загрузку в информационную базу.
## Usage ## Usage
``` ```
/db-load-git [database] /db-load-git [database]
/db-load-git dev — все незафиксированные изменения /db-load-git dev — все незафиксированные изменения
/db-load-git dev -Source Staged — только staged /db-load-git dev -Source Staged — только staged
/db-load-git dev -Source Commit -CommitRange "HEAD~3..HEAD" /db-load-git dev -Source Commit -CommitRange "HEAD~3..HEAD"
/db-load-git dev -DryRun — только показать что будет загружено /db-load-git dev -DryRun — только показать что будет загружено
``` ```
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
Если в записи базы указан `configSrc` — используй как каталог конфигурации. Если в записи базы указан `configSrc` — используй как каталог конфигурации.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-load-git/scripts/db-load-git.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-ConfigDir <путь>` | да | Каталог XML-выгрузки (git-репозиторий) | | `-ConfigDir <путь>` | да | Каталог XML-выгрузки (git-репозиторий) |
| `-Source <источник>` | нет | `All` (по умолч.) / `Staged` / `Unstaged` / `Commit` | | `-Source <источник>` | нет | `All` (по умолч.) / `Staged` / `Unstaged` / `Commit` |
| `-CommitRange <range>` | для Commit | Диапазон коммитов (напр. `HEAD~3..HEAD`) | | `-CommitRange <range>` | для Commit | Диапазон коммитов (напр. `HEAD~3..HEAD`) |
| `-Extension <имя>` | нет | Загрузить в расширение | | `-Extension <имя>` | нет | Загрузить в расширение |
| `-AllExtensions` | нет | Загрузить все расширения | | `-AllExtensions` | нет | Загрузить все расширения |
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` | | `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
| `-DryRun` | нет | Только показать что будет загружено (без загрузки) | | `-DryRun` | нет | Только показать что будет загружено (без загрузки) |
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) | | `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
## После выполнения ## После выполнения
Если `-UpdateDB` не был указан — **предложить `/db-update`** для применения изменений к БД Если `-UpdateDB` не был указан — **предложить `/db-update`** для применения изменений к БД
## Примеры ## Примеры
```powershell ```powershell
# Все незафиксированные изменения # Все незафиксированные изменения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB powershell.exe -NoProfile -File ".opencode/skills/db-load-git/scripts/db-load-git.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB
# Из диапазона коммитов # Из диапазона коммитов
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD" powershell.exe -NoProfile -File ".opencode/skills/db-load-git/scripts/db-load-git.ps1" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD"
``` ```
@@ -1,393 +1,445 @@
# db-load-git v1.5 — Load Git changes into 1C database # db-load-git v1.8 — Load Git changes into 1C database
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Загрузка изменений из Git в базу 1С Загрузка изменений из Git в базу 1С
.DESCRIPTION .DESCRIPTION
Определяет изменённые файлы конфигурации по данным Git и выполняет Определяет изменённые файлы конфигурации по данным Git и выполняет
частичную загрузку в информационную базу. частичную загрузку в информационную базу.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER ConfigDir .PARAMETER ConfigDir
Каталог XML-выгрузки конфигурации (git-репозиторий) Каталог XML-выгрузки конфигурации (git-репозиторий)
.PARAMETER Source .PARAMETER Source
Источник изменений: All, Staged, Unstaged, Commit (по умолчанию All) Источник изменений: All, Staged, Unstaged, Commit (по умолчанию All)
.PARAMETER CommitRange .PARAMETER CommitRange
Диапазон коммитов (для Source=Commit), напр. HEAD~3..HEAD Диапазон коммитов (для Source=Commit), напр. HEAD~3..HEAD
.PARAMETER Extension .PARAMETER Extension
Имя расширения для загрузки Имя расширения для загрузки
.PARAMETER AllExtensions .PARAMETER AllExtensions
Загрузить все расширения Загрузить все расширения
.PARAMETER Format .PARAMETER Format
Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical) Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical)
.PARAMETER DryRun .PARAMETER DryRun
Только показать что будет загружено (без загрузки) Только показать что будет загружено (без загрузки)
.EXAMPLE .EXAMPLE
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Source All .\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Source All
.EXAMPLE .EXAMPLE
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Source Commit -CommitRange "HEAD~3..HEAD" .\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Source Commit -CommitRange "HEAD~3..HEAD"
.EXAMPLE .EXAMPLE
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -DryRun .\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -DryRun
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$ConfigDir, [string]$ConfigDir,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ValidateSet("All", "Staged", "Unstaged", "Commit")] [ValidateSet("All", "Staged", "Unstaged", "Commit")]
[string]$Source = "All", [string]$Source = "All",
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$CommitRange, [string]$CommitRange,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Extension, [string]$Extension,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$AllExtensions, [switch]$AllExtensions,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ValidateSet("Hierarchical", "Plain")] [ValidateSet("Hierarchical", "Plain")]
[string]$Format = "Hierarchical", [string]$Format = "Hierarchical",
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$DryRun, [switch]$DryRun,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$UpdateDB [switch]$UpdateDB
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Helper: map sub-file path (BSL, HTML, etc.) to object XML --- # --- Helper: map sub-file path (BSL, HTML, etc.) to object XML ---
function Get-ObjectXmlFromSubFile { function Get-ObjectXmlFromSubFile {
param([string]$RelativePath) param([string]$RelativePath)
$parts = $RelativePath -split '[\\/]' $parts = $RelativePath -split '[\\/]'
if ($parts.Count -ge 2) { if ($parts.Count -ge 2) {
return "$($parts[0])/$($parts[1]).xml" return "$($parts[0])/$($parts[1]).xml"
} }
return $null return $null
} }
# --- Resolve V8Path (skip if DryRun) --- # --- Resolve V8Path (skip if DryRun) ---
if (-not $DryRun) { if (-not $DryRun) {
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
# --- Validate connection (skip if DryRun) --- # --- Detect engine + validate connection (skip if DryRun) ---
if (-not $DryRun) { $engine = "1cv8"
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { if (-not $DryRun) {
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
exit 1 if ($engine -eq "ibcmd") {
} if (-not $InfoBasePath) {
} Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
exit 1
# --- Validate config dir --- }
if (-not (Test-Path $ConfigDir)) { } elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
Write-Host "Error: config directory not found: $ConfigDir" -ForegroundColor Red Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 exit 1
} }
}
# --- Validate Commit mode ---
if ($Source -eq "Commit" -and -not $CommitRange) { # --- Validate config dir ---
Write-Host "Error: -CommitRange required for Source=Commit" -ForegroundColor Red if (-not (Test-Path $ConfigDir)) {
exit 1 Write-Host "Error: config directory not found: $ConfigDir" -ForegroundColor Red
} exit 1
}
# --- Check git ---
try { # --- Validate Commit mode ---
$null = git --version 2>&1 if ($Source -eq "Commit" -and -not $CommitRange) {
} catch { Write-Host "Error: -CommitRange required for Source=Commit" -ForegroundColor Red
Write-Host "Error: git not found in PATH" -ForegroundColor Red exit 1
exit 1 }
}
# --- Check git ---
# --- Get changed files from Git --- try {
$changedFiles = @() $null = git --version 2>&1
$ConfigDir = (Resolve-Path $ConfigDir).Path.TrimEnd('\') } catch {
$configDirNormalized = $ConfigDir.Replace('\', '/') Write-Host "Error: git not found in PATH" -ForegroundColor Red
exit 1
Push-Location $ConfigDir }
try {
switch ($Source) { # --- Get changed files from Git ---
"Staged" { $changedFiles = @()
Write-Host "Getting staged changes..." $ConfigDir = (Resolve-Path $ConfigDir).Path.TrimEnd('\')
$raw = git diff --cached --name-only --relative 2>&1 $configDirNormalized = $ConfigDir.Replace('\', '/')
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
} Push-Location $ConfigDir
"Unstaged" { try {
Write-Host "Getting unstaged changes..." switch ($Source) {
$raw = git diff --name-only --relative 2>&1 "Staged" {
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw } Write-Host "Getting staged changes..."
$raw = git ls-files --others --exclude-standard 2>&1 $raw = git diff --cached --name-only --relative 2>&1
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw } if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
} }
"Commit" { "Unstaged" {
Write-Host "Getting changes from $CommitRange..." Write-Host "Getting unstaged changes..."
$raw = git diff --name-only --relative $CommitRange 2>&1 $raw = git diff --name-only --relative 2>&1
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw } if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
} $raw = git ls-files --others --exclude-standard 2>&1
"All" { if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
Write-Host "Getting all uncommitted changes..." }
$raw = git diff --cached --name-only --relative 2>&1 "Commit" {
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw } Write-Host "Getting changes from $CommitRange..."
$raw = git diff --name-only --relative 2>&1 $raw = git diff --name-only --relative $CommitRange 2>&1
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw } if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
$raw = git ls-files --others --exclude-standard 2>&1 }
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw } "All" {
} Write-Host "Getting all uncommitted changes..."
} $raw = git diff --cached --name-only --relative 2>&1
} finally { if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
Pop-Location $raw = git diff --name-only --relative 2>&1
} if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
$raw = git ls-files --others --exclude-standard 2>&1
$changedFiles = $changedFiles | Where-Object { $_ -is [string] -and -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
}
if ($changedFiles.Count -eq 0) { }
Write-Host "No changes found" } finally {
exit 0 Pop-Location
} }
Write-Host "Git changes detected: $($changedFiles.Count) files" $changedFiles = $changedFiles | Where-Object { $_ -is [string] -and -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
# --- Filter and map to config files --- if ($changedFiles.Count -eq 0) {
$configFiles = @() Write-Host "No changes found"
$supportSkipped = @() exit 0
}
foreach ($file in $changedFiles) {
$file = $file.Trim().Replace('\', '/') Write-Host "Git changes detected: $($changedFiles.Count) files"
if ([string]::IsNullOrWhiteSpace($file)) { continue }
# --- Filter and map to config files ---
# Skip service files (not partially loadable). Support-state files are tracked $configFiles = @()
# to warn the user: support changes apply only via a full load. $supportSkipped = @()
if ($file -match 'ParentConfigurations\.bin$') { $supportSkipped += $file; continue }
if ($file -eq "ConfigDumpInfo.xml" -or $file -match '(^|/)ConfigDumpInfo\.xml$') { continue } foreach ($file in $changedFiles) {
$file = $file.Trim().Replace('\', '/')
$fullPath = Join-Path $ConfigDir $file if ([string]::IsNullOrWhiteSpace($file)) { continue }
if ($file -match '\.xml$') { # Skip service files (not partially loadable). Support-state files are tracked
# XML file — add directly if exists # to warn the user: support changes apply only via a full load.
if (Test-Path $fullPath) { if ($file -match 'ParentConfigurations\.bin$') { $supportSkipped += $file; continue }
if ($configFiles -notcontains $file) { if ($file -eq "ConfigDumpInfo.xml" -or $file -match '(^|/)ConfigDumpInfo\.xml$') { continue }
$configFiles += $file
} $fullPath = Join-Path $ConfigDir $file
}
} if ($file -match '\.xml$') {
else { # XML file — add directly if exists
# Non-XML (BSL, HTML, etc.) — map to parent object XML + include all Ext/ files if (Test-Path $fullPath) {
$objectXml = Get-ObjectXmlFromSubFile -RelativePath $file if ($configFiles -notcontains $file) {
if ($objectXml) { $configFiles += $file
$fullXmlPath = Join-Path $ConfigDir $objectXml }
if (Test-Path $fullXmlPath) { }
if ($configFiles -notcontains $objectXml) { }
$configFiles += $objectXml else {
} # Non-XML (BSL, HTML, etc.) — map to parent object XML + include all Ext/ files
if ((Test-Path $fullPath) -and $configFiles -notcontains $file) { $objectXml = Get-ObjectXmlFromSubFile -RelativePath $file
$configFiles += $file if ($objectXml) {
} $fullXmlPath = Join-Path $ConfigDir $objectXml
if (Test-Path $fullXmlPath) {
# Add all files from Ext/ directory of the object if ($configFiles -notcontains $objectXml) {
$parts = $file -split '[\\/]' $configFiles += $objectXml
if ($parts.Count -ge 2) { }
$extDir = Join-Path (Join-Path $ConfigDir $parts[0]) "$($parts[1])\Ext" if ((Test-Path $fullPath) -and $configFiles -notcontains $file) {
if (Test-Path $extDir) { $configFiles += $file
Get-ChildItem -Path $extDir -Recurse -File | ForEach-Object { }
$extRelPath = $_.FullName.Replace("$ConfigDir\", '').Replace('\', '/')
if ($configFiles -notcontains $extRelPath) { # Add all files from Ext/ directory of the object
$configFiles += $extRelPath $parts = $file -split '[\\/]'
} if ($parts.Count -ge 2) {
} $extDir = Join-Path (Join-Path $ConfigDir $parts[0]) "$($parts[1])\Ext"
} if (Test-Path $extDir) {
} Get-ChildItem -Path $extDir -Recurse -File | ForEach-Object {
} $extRelPath = $_.FullName.Replace("$ConfigDir\", '').Replace('\', '/')
} if ($configFiles -notcontains $extRelPath) {
} $configFiles += $extRelPath
} }
}
if ($supportSkipped.Count -gt 0) { }
Write-Host "[ВНИМАНИЕ] Состояние поддержки изменено в коммите, но частично не загружается (исключено):" -ForegroundColor Yellow }
foreach ($sf in $supportSkipped) { Write-Host " - $sf" -ForegroundColor Yellow } }
Write-Host " Смена состояния поддержки применяется только полной загрузкой (db-load-xml -Mode Full)." -ForegroundColor Yellow }
} }
}
if ($configFiles.Count -eq 0) {
Write-Host "No configuration files found in changes" if ($supportSkipped.Count -gt 0) {
exit 0 Write-Host "[ВНИМАНИЕ] Состояние поддержки изменено в коммите, но частично не загружается (исключено):" -ForegroundColor Yellow
} foreach ($sf in $supportSkipped) { Write-Host " - $sf" -ForegroundColor Yellow }
Write-Host " Смена состояния поддержки применяется только полной загрузкой (db-load-xml -Mode Full)." -ForegroundColor Yellow
Write-Host "Files for loading: $($configFiles.Count)" }
foreach ($f in $configFiles) { Write-Host " $f" }
if ($configFiles.Count -eq 0) {
# --- DryRun: stop here --- Write-Host "No configuration files found in changes"
if ($DryRun) { exit 0
Write-Host "" }
Write-Host "DryRun mode - no changes applied"
exit 0 Write-Host "Files for loading: $($configFiles.Count)"
} foreach ($f in $configFiles) { Write-Host " $f" }
# --- Temp dir --- # --- DryRun: stop here ---
$tempDir = Join-Path $env:TEMP "db_load_git_$(Get-Random)" if ($DryRun) {
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null Write-Host ""
Write-Host "DryRun mode - no changes applied"
try { exit 0
# --- Write list file (UTF-8 with BOM) --- }
$listFile = Join-Path $tempDir "load_list.txt"
$utf8Bom = New-Object System.Text.UTF8Encoding($true) # --- Temp dir ---
[System.IO.File]::WriteAllLines($listFile, $configFiles, $utf8Bom) $tempDir = Join-Path $env:TEMP "db_load_git_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
# --- Build arguments ---
$arguments = @("DESIGNER") try {
if ($engine -eq "ibcmd") {
if ($InfoBaseServer -and $InfoBaseRef) { # --- ibcmd branch (file infobase only; import specific files) ---
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`"" if ($Format -eq "Plain") {
} else { Write-Host "Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
$arguments += "/F", "`"$InfoBasePath`"" exit 1
} }
if ($AllExtensions) {
if ($UserName) { $arguments += "/N`"$UserName`"" } Write-Host "Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)" -ForegroundColor Red
if ($Password) { $arguments += "/P`"$Password`"" } exit 1
}
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`"" $arguments = @("infobase", "config", "import", "files") + $configFiles
$arguments += "-listFile", "`"$listFile`"" $arguments += "--base-dir=$ConfigDir", "--db-path=$InfoBasePath"
$arguments += "-Format", $Format if ($Extension) { $arguments += "--extension=$Extension" }
$arguments += "-partial" if ($UserName) { $arguments += "--user=$UserName" }
$arguments += "-updateConfigDumpInfo" if ($Password) { $arguments += "--password=$Password" }
$arguments += "--data=$tempDir"
# --- Extensions --- Write-Host "Running: ibcmd $($arguments -join ' ')"
if ($Extension) { $output = & $V8Path @arguments 2>&1
$arguments += "-Extension", "`"$Extension`"" $exitCode = $LASTEXITCODE
} elseif ($AllExtensions) { if ($exitCode -ne 0) {
$arguments += "-AllExtensions" Write-Host "Error loading changes (code: $exitCode)" -ForegroundColor Red
} if ($output) { Write-Host ($output | Out-String) }
exit $exitCode
# --- UpdateDB --- }
if ($UpdateDB) { Write-Host "Changes loaded successfully ($($configFiles.Count) files)" -ForegroundColor Green
$arguments += "/UpdateDBCfg" if ($output) { Write-Host ($output | Out-String) }
} if ($UpdateDB) {
$applyArgs = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force")
# --- Output --- if ($UserName) { $applyArgs += "--user=$UserName" }
$outFile = Join-Path $tempDir "load_log.txt" if ($Password) { $applyArgs += "--password=$Password" }
$arguments += "/Out", "`"$outFile`"" $applyArgs += "--data=$tempDir"
$arguments += "/DisableStartupDialogs" Write-Host "Running: ibcmd $($applyArgs -join ' ')"
$applyOut = & $V8Path @applyArgs 2>&1
# --- Execute --- $exitCode = $LASTEXITCODE
Write-Host "" if ($exitCode -eq 0) {
Write-Host "Executing partial configuration load..." Write-Host "Database configuration updated successfully" -ForegroundColor Green
Write-Host "Running: 1cv8.exe $($arguments -join ' ')" } else {
Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru }
$exitCode = $process.ExitCode if ($applyOut) { Write-Host ($applyOut | Out-String) }
}
# --- Result --- exit $exitCode
Write-Host "" }
if ($exitCode -eq 0) {
Write-Host "Load completed successfully" -ForegroundColor Green # --- 1cv8 branch ---
} else { # --- Write list file (UTF-8 with BOM) ---
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red $listFile = Join-Path $tempDir "load_list.txt"
} $utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllLines($listFile, $configFiles, $utf8Bom)
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue # --- Build arguments ---
if ($logContent) { $arguments = @("DESIGNER")
Write-Host "--- Log ---"
Write-Host $logContent if ($InfoBaseServer -and $InfoBaseRef) {
Write-Host "--- End ---" $arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
} } else {
} $arguments += "/F", "`"$InfoBasePath`""
}
exit $exitCode
if ($UserName) { $arguments += "/N`"$UserName`"" }
} finally { if ($Password) { $arguments += "/P`"$Password`"" }
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue $arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
} $arguments += "-listFile", "`"$listFile`""
} $arguments += "-Format", $Format
$arguments += "-partial"
$arguments += "-updateConfigDumpInfo"
# --- Extensions ---
if ($Extension) {
$arguments += "-Extension", "`"$Extension`""
} elseif ($AllExtensions) {
$arguments += "-AllExtensions"
}
# --- UpdateDB ---
if ($UpdateDB) {
$arguments += "/UpdateDBCfg"
}
# --- Output ---
$outFile = Join-Path $tempDir "load_log.txt"
$arguments += "/Out", "`"$outFile`""
$arguments += "/DisableStartupDialogs"
# --- Execute ---
Write-Host ""
Write-Host "Executing partial configuration load..."
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
# --- Result ---
Write-Host ""
if ($exitCode -eq 0) {
Write-Host "Load completed successfully" -ForegroundColor Green
} else {
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
}
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-load-git v1.5 — Load Git changes into 1C database # db-load-git v1.8 — Load Git changes into 1C database
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -125,9 +126,15 @@ def main():
if not args.DryRun: if not args.DryRun:
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
# --- Validate connection (skip if DryRun) --- # --- Detect engine + validate connection (skip if DryRun) ---
engine = "1cv8"
if not args.DryRun: if not args.DryRun:
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -248,6 +255,58 @@ def main():
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
try: try:
if engine == "ibcmd":
# --- ibcmd branch (file infobase only; import specific files) ---
if args.Format == "Plain":
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
sys.exit(1)
if args.AllExtensions:
print("Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)", file=sys.stderr)
sys.exit(1)
arguments = ["infobase", "config", "import", "files"] + config_files
arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
if args.Extension:
arguments.append(f"--extension={args.Extension}")
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode != 0:
print(f"Error loading changes (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
print(f"Changes loaded successfully ({len(config_files)} files)")
if result.stdout:
print(result.stdout)
exit_code = 0
if args.UpdateDB:
apply_args = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"]
if args.UserName:
apply_args.append(f"--user={args.UserName}")
if args.Password:
apply_args.append(f"--password={args.Password}")
apply_args.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(apply_args)}")
ar = subprocess.run([v8path] + apply_args, capture_output=True, encoding="utf-8", errors="replace")
exit_code = ar.returncode
if exit_code == 0:
print("Database configuration updated successfully")
else:
print(f"Error updating database configuration (code: {exit_code})", file=sys.stderr)
if ar.stdout:
print(ar.stdout)
if ar.stderr:
print(ar.stderr, file=sys.stderr)
sys.exit(exit_code)
# --- Write list file (UTF-8 with BOM) --- # --- Write list file (UTF-8 with BOM) ---
list_file = os.path.join(temp_dir, "load_list.txt") list_file = os.path.join(temp_dir, "load_list.txt")
with open(list_file, "w", encoding="utf-8-sig") as f: with open(list_file, "w", encoding="utf-8-sig") as f:
@@ -1,101 +1,101 @@
--- ---
name: db-load-xml name: db-load-xml
description: Загрузка конфигурации 1С из XML-файлов. Используй когда нужно загрузить конфигурацию из файлов, XML, исходников, LoadConfigFromFiles description: Загрузка конфигурации 1С из XML-файлов. Используй когда нужно загрузить конфигурацию из файлов, XML, исходников, LoadConfigFromFiles
argument-hint: <configDir> [database] argument-hint: <configDir> [database]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-load-xml — Загрузка конфигурации из XML # /db-load-xml — Загрузка конфигурации из XML
Загружает конфигурацию в информационную базу из XML-файлов (исходников). Поддерживает полную и частичную загрузку. Загружает конфигурацию в информационную базу из XML-файлов (исходников). Поддерживает полную и частичную загрузку.
## Usage ## Usage
``` ```
/db-load-xml <configDir> [database] /db-load-xml <configDir> [database]
/db-load-xml src/config dev /db-load-xml src/config dev
/db-load-xml src/config dev -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl" /db-load-xml src/config dev -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
``` ```
> **Внимание**: полная загрузка **заменяет всю конфигурацию** в базе. Перед выполнением запроси подтверждение у пользователя. > **Внимание**: полная загрузка **заменяет всю конфигурацию** в базе. Перед выполнением запроси подтверждение у пользователя.
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
Если в записи базы указан `configSrc` — используй как каталог загрузки по умолчанию. Если в записи базы указан `configSrc` — используй как каталог загрузки по умолчанию.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-ConfigDir <путь>` | да | Каталог XML-исходников | | `-ConfigDir <путь>` | да | Каталог XML-исходников |
| `-Mode <режим>` | нет | `Full` (по умолч.) / `Partial` | | `-Mode <режим>` | нет | `Full` (по умолч.) / `Partial` |
| `-Files <список>` | для Partial | Относительные пути файлов через запятую | | `-Files <список>` | для Partial | Относительные пути файлов через запятую |
| `-ListFile <путь>` | для Partial | Путь к файлу со списком (альтернатива `-Files`) | | `-ListFile <путь>` | для Partial | Путь к файлу со списком (альтернатива `-Files`) |
| `-Extension <имя>` | нет | Загрузить в расширение | | `-Extension <имя>` | нет | Загрузить в расширение |
| `-AllExtensions` | нет | Загрузить все расширения | | `-AllExtensions` | нет | Загрузить все расширения |
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` | | `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) | | `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
### Режимы загрузки ### Режимы загрузки
| Режим | Описание | | Режим | Описание |
|-------|----------| |-------|----------|
| `Full` | Полная загрузка — замена всей конфигурации из каталога XML | | `Full` | Полная загрузка — замена всей конфигурации из каталога XML |
| `Partial` | Частичная — загрузка выбранных файлов (с `-partial -updateConfigDumpInfo`) | | `Partial` | Частичная — загрузка выбранных файлов (с `-partial -updateConfigDumpInfo`) |
### Формат файла списка (listFile) ### Формат файла списка (listFile)
Файл содержит **относительные пути к файлам** в каталоге выгрузки (один на строку), кодировка **UTF-8 с BOM**: Файл содержит **относительные пути к файлам** в каталоге выгрузки (один на строку), кодировка **UTF-8 с BOM**:
``` ```
Catalogs/Номенклатура.xml Catalogs/Номенклатура.xml
Catalogs/Номенклатура/Ext/ObjectModule.bsl Catalogs/Номенклатура/Ext/ObjectModule.bsl
Documents/Заказ.xml Documents/Заказ.xml
Documents/Заказ/Forms/ФормаДокумента.xml Documents/Заказ/Forms/ФормаДокумента.xml
``` ```
## После выполнения ## После выполнения
Если `-UpdateDB` не был указан — **предложи выполнить `/db-update`** для применения изменений к БД Если `-UpdateDB` не был указан — **предложи выполнить `/db-update`** для применения изменений к БД
## Примеры ## Примеры
```powershell ```powershell
# Полная загрузка # Полная загрузка
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
# Частичная загрузка конкретных файлов # Частичная загрузка конкретных файлов
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl" powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
# Загрузка расширения # Загрузка расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение" powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
# Загрузка + обновление БД в одном запуске # Загрузка + обновление БД в одном запуске
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB
``` ```
@@ -1,315 +1,388 @@
# db-load-xml v1.5 — Load 1C configuration from XML files # db-load-xml v1.10 — Load 1C configuration from XML files
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Загрузка конфигурации 1С из XML-файлов Загрузка конфигурации 1С из XML-файлов
.DESCRIPTION .DESCRIPTION
Загружает конфигурацию в информационную базу из XML-файлов. Загружает конфигурацию в информационную базу из XML-файлов.
Поддерживает полную и частичную загрузку. Поддерживает полную и частичную загрузку.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER ConfigDir .PARAMETER ConfigDir
Каталог XML-исходников конфигурации Каталог XML-исходников конфигурации
.PARAMETER Mode .PARAMETER Mode
Режим загрузки: Full или Partial (по умолчанию Full) Режим загрузки: Full или Partial (по умолчанию Full)
.PARAMETER Files .PARAMETER Files
Относительные пути файлов через запятую (для режима Partial) Относительные пути файлов через запятую (для режима Partial)
.PARAMETER ListFile .PARAMETER ListFile
Путь к файлу со списком файлов (альтернатива -Files, для режима Partial) Путь к файлу со списком файлов (альтернатива -Files, для режима Partial)
.PARAMETER Extension .PARAMETER Extension
Имя расширения для загрузки Имя расширения для загрузки
.PARAMETER AllExtensions .PARAMETER AllExtensions
Загрузить все расширения Загрузить все расширения
.PARAMETER Format .PARAMETER Format
Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical) Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical)
.EXAMPLE .EXAMPLE
.\db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Full .\db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Full
.EXAMPLE .EXAMPLE
.\db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl" .\db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$ConfigDir, [string]$ConfigDir,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ValidateSet("Full", "Partial")] [ValidateSet("Full", "Partial")]
[string]$Mode = "Full", [string]$Mode = "Full",
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Files, [string]$Files,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$ListFile, [string]$ListFile,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Extension, [string]$Extension,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$AllExtensions, [switch]$AllExtensions,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ValidateSet("Hierarchical", "Plain")] [ValidateSet("Hierarchical", "Plain")]
[string]$Format = "Hierarchical", [string]$Format = "Hierarchical",
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$UpdateDB, [switch]$UpdateDB,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$StrictLog [switch]$StrictLog
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Validate connection --- # --- Detect engine (ibcmd vs 1cv8) by exe name ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 # --- Validate connection ---
} if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
# --- Validate config dir --- Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
if (-not (Test-Path $ConfigDir)) { exit 1
Write-Host "Error: config directory not found: $ConfigDir" -ForegroundColor Red }
exit 1 } elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
} Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1
# --- Validate Partial mode --- }
if ($Mode -eq "Partial" -and -not $Files -and -not $ListFile) {
Write-Host "Error: -Files or -ListFile required for Partial mode" -ForegroundColor Red # --- Validate config dir ---
exit 1 if (-not (Test-Path $ConfigDir)) {
} Write-Host "Error: config directory not found: $ConfigDir" -ForegroundColor Red
exit 1
# --- Temp dir --- }
$tempDir = Join-Path $env:TEMP "db_load_xml_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null # --- Validate Partial mode ---
if ($Mode -eq "Partial" -and -not $Files -and -not $ListFile) {
try { Write-Host "Error: -Files or -ListFile required for Partial mode" -ForegroundColor Red
# --- Build arguments --- exit 1
$arguments = @("DESIGNER") }
if ($InfoBaseServer -and $InfoBaseRef) { # --- Temp dir ---
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`"" $tempDir = Join-Path $env:TEMP "db_load_xml_$(Get-Random)"
} else { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
$arguments += "/F", "`"$InfoBasePath`""
} try {
if ($engine -eq "ibcmd") {
if ($UserName) { $arguments += "/N`"$UserName`"" } # --- ibcmd branch (file infobase only; hierarchical full-directory import) ---
if ($Password) { $arguments += "/P`"$Password`"" } if ($Format -eq "Plain") {
Write-Host "Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`"" exit 1
}
if ($Mode -eq "Full") { if ($AllExtensions) {
Write-Host "Executing full configuration load..." $arguments = @("infobase", "config", "import", "all-extensions", "$ConfigDir", "--db-path=$InfoBasePath")
} else { } elseif ($Mode -eq "Partial" -or $Files -or $ListFile) {
Write-Host "Executing partial configuration load..." # partial: import specific files (relative to ConfigDir)
$fileList = @()
# Build list file if ($ListFile) {
$rawList = @() if (-not (Test-Path $ListFile)) {
if ($ListFile) { Write-Host "Error: list file not found: $ListFile" -ForegroundColor Red
if (-not (Test-Path $ListFile)) { exit 1
Write-Host "Error: list file not found: $ListFile" -ForegroundColor Red }
exit 1 $fileList = @(Get-Content -Path $ListFile -Encoding UTF8 | ForEach-Object { $_.Trim() } | Where-Object { $_ })
} } elseif ($Files) {
$rawList = @(Get-Content -Path $ListFile -Encoding UTF8 | ForEach-Object { $_.Trim() } | Where-Object { $_ }) $fileList = @($Files -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
} else { }
$rawList = @($Files -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) if ($fileList.Count -eq 0) {
} Write-Host "Error: -Files or -ListFile required for partial import" -ForegroundColor Red
exit 1
# Support-state service files are NOT partially loadable — exclude with a hint. }
$supportRe = 'ParentConfigurations\.bin$|(^|[\\/])ConfigDumpInfo\.xml$' $arguments = @("infobase", "config", "import", "files") + $fileList
$supportFiles = @($rawList | Where-Object { $_ -match $supportRe }) $arguments += "--base-dir=$ConfigDir", "--db-path=$InfoBasePath"
$fileList = @($rawList | Where-Object { $_ -notmatch $supportRe }) if ($Extension) { $arguments += "--extension=$Extension" }
if ($supportFiles.Count -gt 0) { } else {
Write-Host "[ВНИМАНИЕ] Служебные файлы состояния поддержки исключены из частичной загрузки (частично не грузятся):" -ForegroundColor Yellow $arguments = @("infobase", "config", "import", "--db-path=$InfoBasePath")
foreach ($sf in $supportFiles) { Write-Host " - $sf" -ForegroundColor Yellow } if ($Extension) { $arguments += "--extension=$Extension" }
Write-Host " Смена состояния поддержки применяется только полной загрузкой: -Mode Full." -ForegroundColor Yellow $arguments += "$ConfigDir"
} }
if ($fileList.Count -eq 0) { if ($UserName) { $arguments += "--user=$UserName" }
Write-Host "Error: после исключения служебных файлов поддержки загружать нечего. Для смены поддержки используйте -Mode Full." -ForegroundColor Red if ($Password) { $arguments += "--password=$Password" }
exit 1 $arguments += "--data=$tempDir"
} Write-Host "Running: ibcmd $($arguments -join ' ')"
$generatedListFile = Join-Path $tempDir "load_list.txt" $output = & $V8Path @arguments 2>&1
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $exitCode = $LASTEXITCODE
[System.IO.File]::WriteAllLines($generatedListFile, $fileList, $utf8Bom) if ($exitCode -ne 0) {
Write-Host "Files to load: $($fileList.Count)" Write-Host "Error loading configuration from files (code: $exitCode)" -ForegroundColor Red
foreach ($f in $fileList) { Write-Host " $f" } if ($output) { Write-Host ($output | Out-String) }
exit $exitCode
$arguments += "-listFile", "`"$generatedListFile`"" }
$arguments += "-partial" Write-Host "Configuration loaded successfully from: $ConfigDir" -ForegroundColor Green
$arguments += "-updateConfigDumpInfo" if ($output) { Write-Host ($output | Out-String) }
}
if ($UpdateDB) {
$arguments += "-Format", $Format $applyArgs = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force")
if ($UserName) { $applyArgs += "--user=$UserName" }
# --- Extensions --- if ($Password) { $applyArgs += "--password=$Password" }
if ($Extension) { $applyArgs += "--data=$tempDir"
$arguments += "-Extension", "`"$Extension`"" Write-Host "Running: ibcmd $($applyArgs -join ' ')"
} elseif ($AllExtensions) { $applyOut = & $V8Path @applyArgs 2>&1
$arguments += "-AllExtensions" $exitCode = $LASTEXITCODE
} if ($exitCode -eq 0) {
Write-Host "Database configuration updated successfully" -ForegroundColor Green
# --- UpdateDB --- } else {
if ($UpdateDB) { Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
$arguments += "/UpdateDBCfg" }
} if ($applyOut) { Write-Host ($applyOut | Out-String) }
}
# --- Output --- exit $exitCode
$outFile = Join-Path $tempDir "load_log.txt" }
$arguments += "/Out", "`"$outFile`""
$arguments += "/DisableStartupDialogs" # --- 1cv8 branch ---
# --- Build arguments ---
# --- Execute --- $arguments = @("DESIGNER")
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru if ($InfoBaseServer -and $InfoBaseRef) {
$exitCode = $process.ExitCode $arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
} else {
# --- Read log --- $arguments += "/F", "`"$InfoBasePath`""
$logContent = $null }
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue if ($UserName) { $arguments += "/N`"$UserName`"" }
} if ($Password) { $arguments += "/P`"$Password`"" }
# --- Scan log for silent rejections --- $arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
# Platform often writes load-time rejections into /Out but exits with code 0.
# These patterns flag cases where metadata was dropped or rejected silently. if ($Mode -eq "Full") {
$fatalLogPatterns = @( Write-Host "Executing full configuration load..."
'Неверное свойство объекта метаданных', } else {
'не входит в состав объекта метаданных', Write-Host "Executing partial configuration load..."
'Неизвестное имя типа',
'Неизвестный объект метаданных', # Build list file
'Ни один из документов не является регистратором для регистра', $rawList = @()
'Неверное значение перечисления', if ($ListFile) {
'не может быть приведен к типу' if (-not (Test-Path $ListFile)) {
) Write-Host "Error: list file not found: $ListFile" -ForegroundColor Red
$silentFailures = @() exit 1
if ($logContent) { }
foreach ($line in ($logContent -split "`r?`n")) { $rawList = @(Get-Content -Path $ListFile -Encoding UTF8 | ForEach-Object { $_.Trim() } | Where-Object { $_ })
foreach ($pat in $fatalLogPatterns) { } else {
if ($line -match [regex]::Escape($pat)) { $rawList = @($Files -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$silentFailures += $line.Trim() }
break
} # Support-state service files are NOT partially loadable — exclude with a hint.
} $supportRe = 'ParentConfigurations\.bin$|(^|[\\/])ConfigDumpInfo\.xml$'
} $supportFiles = @($rawList | Where-Object { $_ -match $supportRe })
} $fileList = @($rawList | Where-Object { $_ -notmatch $supportRe })
if ($supportFiles.Count -gt 0) {
# --- Result --- Write-Host "[ВНИМАНИЕ] Служебные файлы состояния поддержки исключены из частичной загрузки (частично не грузятся):" -ForegroundColor Yellow
# Default: mirror platform's verdict via exit code. Log content (including any foreach ($sf in $supportFiles) { Write-Host " - $sf" -ForegroundColor Yellow }
# rejection warnings) is always printed to stdout for visibility. With -StrictLog, Write-Host " Смена состояния поддержки применяется только полной загрузкой: -Mode Full." -ForegroundColor Yellow
# elevate exit code to 1 when rejection patterns are found even if platform said 0. }
if ($exitCode -eq 0) { if ($fileList.Count -eq 0) {
Write-Host "Load completed successfully" -ForegroundColor Green Write-Host "Error: после исключения служебных файлов поддержки загружать нечего. Для смены поддержки используйте -Mode Full." -ForegroundColor Red
} else { exit 1
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red }
} $generatedListFile = Join-Path $tempDir "load_list.txt"
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
if ($logContent) { [System.IO.File]::WriteAllLines($generatedListFile, $fileList, $utf8Bom)
Write-Host "--- Log ---" Write-Host "Files to load: $($fileList.Count)"
Write-Host $logContent foreach ($f in $fileList) { Write-Host " $f" }
Write-Host "--- End ---"
} $arguments += "-listFile", "`"$generatedListFile`""
$arguments += "-partial"
if ($silentFailures.Count -gt 0) { $arguments += "-updateConfigDumpInfo"
$msg = "[warning] log contains $($silentFailures.Count) rejection(s) — platform loaded config but dropped properties/refs" }
if (-not $StrictLog) { $msg += " (pass -StrictLog to treat as error)" }
Write-Host $msg -ForegroundColor Yellow $arguments += "-Format", $Format
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 } # --- Extensions ---
} if ($Extension) {
$arguments += "-Extension", "`"$Extension`""
exit $exitCode } elseif ($AllExtensions) {
$arguments += "-AllExtensions"
} finally { }
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue # --- UpdateDB ---
} if ($UpdateDB) {
} $arguments += "/UpdateDBCfg"
}
# --- Output ---
$outFile = Join-Path $tempDir "load_log.txt"
$arguments += "/Out", "`"$outFile`""
$arguments += "/DisableStartupDialogs"
# --- Execute ---
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
# --- Read log ---
$logContent = $null
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
}
# --- Scan log for silent rejections ---
# Platform often writes load-time rejections into /Out but exits with code 0.
# These patterns flag cases where metadata was dropped or rejected silently.
$fatalLogPatterns = @(
'Неверное свойство объекта метаданных',
'не входит в состав объекта метаданных',
'Неизвестное имя типа',
'Неизвестный объект метаданных',
'Ни один из документов не является регистратором для регистра',
'Неверное значение перечисления',
'не может быть приведен к типу'
)
$silentFailures = @()
if ($logContent) {
foreach ($line in ($logContent -split "`r?`n")) {
foreach ($pat in $fatalLogPatterns) {
if ($line -match [regex]::Escape($pat)) {
$silentFailures += $line.Trim()
break
}
}
}
}
# --- Result ---
# Default: mirror platform's verdict via exit code. Log content (including any
# rejection warnings) is always printed to stdout for visibility. With -StrictLog,
# elevate exit code to 1 when rejection patterns are found even if platform said 0.
if ($exitCode -eq 0) {
Write-Host "Load completed successfully" -ForegroundColor Green
} else {
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
}
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
if ($silentFailures.Count -gt 0) {
$msg = "[warning] log contains $($silentFailures.Count) rejection(s) — platform loaded config but dropped properties/refs"
if (-not $StrictLog) { $msg += " (pass -StrictLog to treat as error)" }
Write-Host $msg -ForegroundColor Yellow
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-load-xml v1.5 — Load 1C configuration from XML files # db-load-xml v1.10 — Load 1C configuration from XML files
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -106,8 +107,14 @@ def main():
# --- Resolve V8Path --- # --- Resolve V8Path ---
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate connection --- # --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -121,6 +128,77 @@ def main():
print("Error: -Files or -ListFile required for Partial mode", file=sys.stderr) print("Error: -Files or -ListFile required for Partial mode", file=sys.stderr)
sys.exit(1) sys.exit(1)
# --- ibcmd branch (file infobase only; hierarchical full-directory import) ---
if engine == "ibcmd":
if args.Format == "Plain":
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
sys.exit(1)
if args.AllExtensions:
arguments = ["infobase", "config", "import", "all-extensions", args.ConfigDir, f"--db-path={args.InfoBasePath}"]
elif args.Mode == "Partial" or args.Files or args.ListFile:
# partial: import specific files (relative to ConfigDir)
if args.ListFile:
if not os.path.isfile(args.ListFile):
print(f"Error: list file not found: {args.ListFile}", file=sys.stderr)
sys.exit(1)
with open(args.ListFile, encoding="utf-8-sig") as f:
file_list = [ln.strip() for ln in f if ln.strip()]
elif args.Files:
file_list = [p.strip() for p in args.Files.split(",") if p.strip()]
else:
file_list = []
if not file_list:
print("Error: -Files or -ListFile required for partial import", file=sys.stderr)
sys.exit(1)
arguments = ["infobase", "config", "import", "files"] + file_list
arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
if args.Extension:
arguments.append(f"--extension={args.Extension}")
else:
arguments = ["infobase", "config", "import", f"--db-path={args.InfoBasePath}"]
if args.Extension:
arguments.append(f"--extension={args.Extension}")
arguments.append(args.ConfigDir)
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode != 0:
print(f"Error loading configuration from files (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
print(f"Configuration loaded successfully from: {args.ConfigDir}")
if result.stdout:
print(result.stdout)
exit_code = 0
if args.UpdateDB:
apply_args = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"]
if args.UserName:
apply_args.append(f"--user={args.UserName}")
if args.Password:
apply_args.append(f"--password={args.Password}")
apply_args.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(apply_args)}")
ar = subprocess.run([v8path] + apply_args, capture_output=True, encoding="utf-8", errors="replace")
exit_code = ar.returncode
if exit_code == 0:
print("Database configuration updated successfully")
else:
print(f"Error updating database configuration (code: {exit_code})", file=sys.stderr)
if ar.stdout:
print(ar.stdout)
if ar.stderr:
print(ar.stderr, file=sys.stderr)
sys.exit(exit_code)
# --- Temp dir --- # --- Temp dir ---
temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_xml_{random.randint(0, 999999)}") temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_xml_{random.randint(0, 999999)}")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
@@ -1,76 +1,76 @@
--- ---
name: db-run name: db-run
description: Запуск 1С:Предприятие. Используй когда нужно запустить 1С, открыть базу, запустить предприятие description: Запуск 1С:Предприятие. Используй когда нужно запустить 1С, открыть базу, запустить предприятие
argument-hint: "[database]" argument-hint: "[database]"
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-run — Запуск 1С:Предприятие # /db-run — Запуск 1С:Предприятие
Запускает информационную базу в режиме 1С:Предприятие (пользовательский режим). Запускает информационную базу в режиме 1С:Предприятие (пользовательский режим).
## Usage ## Usage
``` ```
/db-run [database] /db-run [database]
/db-run dev /db-run dev
/db-run dev /Execute process.epf /db-run dev /Execute process.epf
/db-run dev /C "параметр запуска" /db-run dev /C "параметр запуска"
``` ```
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-Execute <файл.epf>` | нет | Запуск внешней обработки сразу после старта | | `-Execute <файл.epf>` | нет | Запуск внешней обработки сразу после старта |
| `-CParam <строка>` | нет | Параметр запуска (/C) | | `-CParam <строка>` | нет | Параметр запуска (/C) |
| `-URL <ссылка>` | нет | Навигационная ссылка (формат `e1cib/...`) | | `-URL <ссылка>` | нет | Навигационная ссылка (формат `e1cib/...`) |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
## Важно ## Важно
Скрипт запускает 1С в фоне (`Start-Process` без `-Wait`) — управление возвращается сразу. Скрипт запускает 1С в фоне (`Start-Process` без `-Wait`) — управление возвращается сразу.
## Примеры ## Примеры
```powershell ```powershell
# Простой запуск # Простой запуск
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
# Запуск с обработкой # Запуск с обработкой
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf" powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf"
# Открыть по навигационной ссылке # Открыть по навигационной ссылке
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура" powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура"
# Серверная база с параметром запуска # Серверная база с параметром запуска
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление" powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление"
``` ```
@@ -1,170 +1,170 @@
# db-run v1.1 — Launch 1C:Enterprise # db-run v1.1 — Launch 1C:Enterprise
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Запуск 1С:Предприятие Запуск 1С:Предприятие
.DESCRIPTION .DESCRIPTION
Запускает информационную базу в режиме 1С:Предприятие (пользовательский режим). Запускает информационную базу в режиме 1С:Предприятие (пользовательский режим).
Запуск в фоне не ждёт завершения процесса. Запуск в фоне не ждёт завершения процесса.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER Execute .PARAMETER Execute
Путь к внешней обработке для запуска Путь к внешней обработке для запуска
.PARAMETER CParam .PARAMETER CParam
Параметр запуска (/C) Параметр запуска (/C)
.PARAMETER URL .PARAMETER URL
Навигационная ссылка (e1cib/...) Навигационная ссылка (e1cib/...)
.EXAMPLE .EXAMPLE
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" .\db-run.ps1 -InfoBasePath "C:\Bases\MyDB"
.EXAMPLE .EXAMPLE
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -Execute "C:\epf\МояОбработка.epf" .\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -Execute "C:\epf\МояОбработка.epf"
.EXAMPLE .EXAMPLE
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -CParam "ЗапуститьОбновление" .\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -CParam "ЗапуститьОбновление"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Execute, [string]$Execute,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$CParam, [string]$CParam,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$URL [string]$URL
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Validate connection --- # --- Validate connection ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 exit 1
} }
# --- Build arguments as single string --- # --- Build arguments as single string ---
# Note: Start-Process without -NoNewWindow uses ShellExecute. # Note: Start-Process without -NoNewWindow uses ShellExecute.
# Passing ArgumentList as array can corrupt Cyrillic when ShellExecute # Passing ArgumentList as array can corrupt Cyrillic when ShellExecute
# re-joins elements. Single string avoids this. # re-joins elements. Single string avoids this.
$argString = "ENTERPRISE" $argString = "ENTERPRISE"
if ($InfoBaseServer -and $InfoBaseRef) { if ($InfoBaseServer -and $InfoBaseRef) {
$argString += " /S `"$InfoBaseServer/$InfoBaseRef`"" $argString += " /S `"$InfoBaseServer/$InfoBaseRef`""
} else { } else {
$argString += " /F `"$InfoBasePath`"" $argString += " /F `"$InfoBasePath`""
} }
if ($UserName) { $argString += " /N`"$UserName`"" } if ($UserName) { $argString += " /N`"$UserName`"" }
if ($Password) { $argString += " /P`"$Password`"" } if ($Password) { $argString += " /P`"$Password`"" }
# --- Optional params --- # --- Optional params ---
if ($Execute) { if ($Execute) {
$ext = [System.IO.Path]::GetExtension($Execute).ToLower() $ext = [System.IO.Path]::GetExtension($Execute).ToLower()
if ($ext -eq ".erf") { if ($ext -eq ".erf") {
Write-Host "[WARN] /Execute не поддерживает ERF-файлы (внешние отчёты)." -ForegroundColor Yellow Write-Host "[WARN] /Execute не поддерживает ERF-файлы (внешние отчёты)." -ForegroundColor Yellow
Write-Host " Откройте отчёт через «Файл -> Открыть»: $Execute" -ForegroundColor Yellow Write-Host " Откройте отчёт через «Файл -> Открыть»: $Execute" -ForegroundColor Yellow
Write-Host " Запускаю базу без /Execute." -ForegroundColor Yellow Write-Host " Запускаю базу без /Execute." -ForegroundColor Yellow
$Execute = "" $Execute = ""
} }
} }
if ($Execute) { if ($Execute) {
$argString += " /Execute `"$Execute`"" $argString += " /Execute `"$Execute`""
} }
if ($CParam) { if ($CParam) {
$argString += " /C `"$CParam`"" $argString += " /C `"$CParam`""
} }
if ($URL) { if ($URL) {
$argString += " /URL `"$URL`"" $argString += " /URL `"$URL`""
} }
$argString += " /DisableStartupDialogs" $argString += " /DisableStartupDialogs"
# --- Execute (background, no wait) --- # --- Execute (background, no wait) ---
Write-Host "Running: 1cv8.exe $argString" Write-Host "Running: 1cv8.exe $argString"
Start-Process -FilePath $V8Path -ArgumentList $argString Start-Process -FilePath $V8Path -ArgumentList $argString
Write-Host "1C:Enterprise launched" -ForegroundColor Green Write-Host "1C:Enterprise launched" -ForegroundColor Green
@@ -1,86 +1,86 @@
--- ---
name: db-update name: db-update
description: Обновление конфигурации базы данных 1С. Используй когда нужно обновить БД, применить конфигурацию, UpdateDBCfg description: Обновление конфигурации базы данных 1С. Используй когда нужно обновить БД, применить конфигурацию, UpdateDBCfg
argument-hint: "[database]" argument-hint: "[database]"
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- AskUserQuestion - AskUserQuestion
--- ---
# /db-update — Обновление конфигурации БД # /db-update — Обновление конфигурации БД
Применяет изменения основной конфигурации к конфигурации базы данных (`/UpdateDBCfg`). Обязательный шаг после `/db-load-cf`, `/db-load-xml`, `/db-load-git`. Применяет изменения основной конфигурации к конфигурации базы данных (`/UpdateDBCfg`). Обязательный шаг после `/db-load-cf`, `/db-load-xml`, `/db-load-git`.
## Usage ## Usage
``` ```
/db-update [database] /db-update [database]
/db-update dev /db-update dev
/db-update dev -Dynamic+ /db-update dev -Dynamic+
``` ```
## Параметры подключения ## Параметры подключения
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу: Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
4. Если ветка не совпала — используй `default` 4. Если ветка не совпала — используй `default`
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если файла нет — предложи `/db-list add`. Если файла нет — предложи `/db-list add`.
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/db-update/scripts/db-update.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-Extension <имя>` | нет | Обновить расширение | | `-Extension <имя>` | нет | Обновить расширение |
| `-AllExtensions` | нет | Обновить все расширения | | `-AllExtensions` | нет | Обновить все расширения |
| `-Dynamic <+/->` | нет | `+` — динамическое обновление, `-` — отключить | | `-Dynamic <+/->` | нет | `+` — динамическое обновление, `-` — отключить |
| `-Server` | нет | Обновление на стороне сервера | | `-Server` | нет | Обновление на стороне сервера |
| `-WarningsAsErrors` | нет | Предупреждения считать ошибками | | `-WarningsAsErrors` | нет | Предупреждения считать ошибками |
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef` > `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
### Фоновое обновление (серверная база) ### Фоновое обновление (серверная база)
| Параметр | Описание | | Параметр | Описание |
|----------|----------| |----------|----------|
| `-BackgroundStart` | Начать фоновое обновление | | `-BackgroundStart` | Начать фоновое обновление |
| `-BackgroundFinish` | Дождаться окончания | | `-BackgroundFinish` | Дождаться окончания |
| `-BackgroundCancel` | Отменить | | `-BackgroundCancel` | Отменить |
| `-BackgroundSuspend` | Приостановить | | `-BackgroundSuspend` | Приостановить |
| `-BackgroundResume` | Возобновить | | `-BackgroundResume` | Возобновить |
## Предупреждения ## Предупреждения
- Если обновление **не динамическое** — потребуется **монопольный доступ** к базе (все пользователи должны выйти) - Если обновление **не динамическое** — потребуется **монопольный доступ** к базе (все пользователи должны выйти)
- Для серверных баз рекомендуется `-Dynamic+` для обновления без остановки - Для серверных баз рекомендуется `-Dynamic+` для обновления без остановки
- Если структура данных существенно изменилась (удаление реквизитов, изменение типов) — динамическое обновление может быть невозможно - Если структура данных существенно изменилась (удаление реквизитов, изменение типов) — динамическое обновление может быть невозможно
## Примеры ## Примеры
```powershell ```powershell
# Обычное обновление (файловая база) # Обычное обновление (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" powershell.exe -NoProfile -File ".opencode/skills/db-update/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
# Динамическое обновление (серверная база) # Динамическое обновление (серверная база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic "+" powershell.exe -NoProfile -File ".opencode/skills/db-update/scripts/db-update.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic "+"
# Обновление расширения # Обновление расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение" powershell.exe -NoProfile -File ".opencode/skills/db-update/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение"
``` ```
@@ -1,209 +1,243 @@
# db-update v1.1 — Update 1C database configuration # db-update v1.4 — Update 1C database configuration
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Обновление конфигурации базы данных 1С Обновление конфигурации базы данных 1С
.DESCRIPTION .DESCRIPTION
Применяет изменения основной конфигурации к конфигурации базы данных. Применяет изменения основной конфигурации к конфигурации базы данных.
Поддерживает динамическое обновление, обновление расширений. Поддерживает динамическое обновление, обновление расширений.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER Extension .PARAMETER Extension
Имя расширения для обновления Имя расширения для обновления
.PARAMETER AllExtensions .PARAMETER AllExtensions
Обновить все расширения Обновить все расширения
.PARAMETER Dynamic .PARAMETER Dynamic
Динамическое обновление: "+" включить, "-" отключить Динамическое обновление: "+" включить, "-" отключить
.PARAMETER Server .PARAMETER Server
Обновление на стороне сервера Обновление на стороне сервера
.PARAMETER WarningsAsErrors .PARAMETER WarningsAsErrors
Предупреждения считать ошибками Предупреждения считать ошибками
.EXAMPLE .EXAMPLE
.\db-update.ps1 -InfoBasePath "C:\Bases\MyDB" .\db-update.ps1 -InfoBasePath "C:\Bases\MyDB"
.EXAMPLE .EXAMPLE
.\db-update.ps1 -InfoBasePath "C:\Bases\MyDB" -Dynamic "+" -Extension "МоёРасширение" .\db-update.ps1 -InfoBasePath "C:\Bases\MyDB" -Dynamic "+" -Extension "МоёРасширение"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Extension, [string]$Extension,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$AllExtensions, [switch]$AllExtensions,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ValidateSet("+", "-")] [ValidateSet("+", "-")]
[string]$Dynamic, [string]$Dynamic,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$Server, [switch]$Server,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[switch]$WarningsAsErrors [switch]$WarningsAsErrors
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Validate connection --- # --- Detect engine (ibcmd vs 1cv8) by exe name ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
exit 1 # --- Validate connection ---
} if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
# --- Temp dir --- Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
$tempDir = Join-Path $env:TEMP "db_update_$(Get-Random)" exit 1
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null }
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
try { Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
# --- Build arguments --- exit 1
$arguments = @("DESIGNER") }
if ($InfoBaseServer -and $InfoBaseRef) { # --- Temp dir ---
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`"" $tempDir = Join-Path $env:TEMP "db_update_$(Get-Random)"
} else { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
$arguments += "/F", "`"$InfoBasePath`""
} try {
if ($engine -eq "ibcmd") {
if ($UserName) { $arguments += "/N`"$UserName`"" } # --- ibcmd branch (file infobase only) ---
if ($Password) { $arguments += "/P`"$Password`"" } if ($AllExtensions) {
Write-Host "Error: ibcmd config apply does not support -AllExtensions (use -Extension)" -ForegroundColor Red
$arguments += "/UpdateDBCfg" exit 1
}
# --- Options --- $arguments = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force")
if ($Dynamic) { if ($Dynamic -eq "+") { $arguments += "--dynamic=auto" }
$arguments += "-Dynamic$Dynamic" elseif ($Dynamic -eq "-") { $arguments += "--dynamic=disable" }
} if ($Extension) { $arguments += "--extension=$Extension" }
if ($Server) { if ($UserName) { $arguments += "--user=$UserName" }
$arguments += "-Server" if ($Password) { $arguments += "--password=$Password" }
} $arguments += "--data=$tempDir"
if ($WarningsAsErrors) { Write-Host "Running: ibcmd $($arguments -join ' ')"
$arguments += "-WarningsAsErrors" $output = & $V8Path @arguments 2>&1
} $exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
# --- Extensions --- Write-Host "Database configuration updated successfully" -ForegroundColor Green
if ($Extension) { } else {
$arguments += "-Extension", "`"$Extension`"" Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
} elseif ($AllExtensions) { }
$arguments += "-AllExtensions" if ($output) { Write-Host ($output | Out-String) }
} exit $exitCode
}
# --- Output ---
$outFile = Join-Path $tempDir "update_log.txt" # --- 1cv8 branch ---
$arguments += "/Out", "`"$outFile`"" # --- Build arguments ---
$arguments += "/DisableStartupDialogs" $arguments = @("DESIGNER")
# --- Execute --- if ($InfoBaseServer -and $InfoBaseRef) {
Write-Host "Running: 1cv8.exe $($arguments -join ' ')" $arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru } else {
$exitCode = $process.ExitCode $arguments += "/F", "`"$InfoBasePath`""
}
# --- Result ---
if ($exitCode -eq 0) { if ($UserName) { $arguments += "/N`"$UserName`"" }
Write-Host "Database configuration updated successfully" -ForegroundColor Green if ($Password) { $arguments += "/P`"$Password`"" }
} else {
Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red $arguments += "/UpdateDBCfg"
}
# --- Options ---
if (Test-Path $outFile) { if ($Dynamic) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue $arguments += "-Dynamic$Dynamic"
if ($logContent) { }
Write-Host "--- Log ---" if ($Server) {
Write-Host $logContent $arguments += "-Server"
Write-Host "--- End ---" }
} if ($WarningsAsErrors) {
} $arguments += "-WarningsAsErrors"
}
exit $exitCode
# --- Extensions ---
} finally { if ($Extension) {
if (Test-Path $tempDir) { $arguments += "-Extension", "`"$Extension`""
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } elseif ($AllExtensions) {
} $arguments += "-AllExtensions"
} }
# --- Output ---
$outFile = Join-Path $tempDir "update_log.txt"
$arguments += "/Out", "`"$outFile`""
$arguments += "/DisableStartupDialogs"
# --- Execute ---
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
# --- Result ---
if ($exitCode -eq 0) {
Write-Host "Database configuration updated successfully" -ForegroundColor Green
} else {
Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
}
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-update v1.1 — Update 1C database configuration # db-update v1.4 — Update 1C database configuration
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -87,11 +88,48 @@ def main():
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate connection --- # --- Validate connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr) print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
sys.exit(1) sys.exit(1)
# --- ibcmd branch (file infobase only) ---
if engine == "ibcmd":
if args.AllExtensions:
print("Error: ibcmd config apply does not support -AllExtensions (use -Extension)", file=sys.stderr)
sys.exit(1)
arguments = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"]
if args.Dynamic == "+":
arguments.append("--dynamic=auto")
elif args.Dynamic == "-":
arguments.append("--dynamic=disable")
if args.Extension:
arguments.append(f"--extension={args.Extension}")
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print("Database configuration updated successfully")
else:
print(f"Error updating database configuration (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Temp dir --- # --- Temp dir ---
temp_dir = os.path.join(tempfile.gettempdir(), f"db_update_{random.randint(0, 999999)}") temp_dir = os.path.join(tempfile.gettempdir(), f"db_update_{random.randint(0, 999999)}")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
@@ -1,196 +1,196 @@
--- ---
name: epf-bsp-add-command name: epf-bsp-add-command
description: Определить команду в БСП‑описании обработки (`СведенияОВнешнейОбработке`) — открытие формы, вызов клиентского/серверного метода, заполнение объекта и т.п. Используй когда нужно зарегистрировать команду в дополнительной обработке БСП description: Определить команду в БСП‑описании обработки (`СведенияОВнешнейОбработке`) — открытие формы, вызов клиентского/серверного метода, заполнение объекта и т.п. Используй когда нужно зарегистрировать команду в дополнительной обработке БСП
argument-hint: <ProcessorName> <Идентификатор> [ТипКоманды] [Представление] argument-hint: <ProcessorName> <Идентификатор> [ТипКоманды] [Представление]
allowed-tools: allowed-tools:
- Read - Read
- Edit - Edit
- Glob - Glob
- Grep - Grep
--- ---
# /epf-bsp-add-command — Добавление команды БСП # /epf-bsp-add-command — Добавление команды БСП
Добавляет команду в существующую функцию `СведенияОВнешнейОбработке()` и генерирует соответствующий обработчик. Добавляет команду в существующую функцию `СведенияОВнешнейОбработке()` и генерирует соответствующий обработчик.
Предварительно обработка должна быть инициализирована через `/epf-bsp-init`. Предварительно обработка должна быть инициализирована через `/epf-bsp-init`.
## Usage ## Usage
``` ```
/epf-bsp-add-command <ProcessorName> <Идентификатор> [ТипКоманды] [Представление] /epf-bsp-add-command <ProcessorName> <Идентификатор> [ТипКоманды] [Представление]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|---------------|:------------:|-----------------------|--------------------------------------------| |---------------|:------------:|-----------------------|--------------------------------------------|
| ProcessorName | да | — | Имя обработки | | ProcessorName | да | — | Имя обработки |
| Идентификатор | да | — | Внутреннее имя команды (латиница) | | Идентификатор | да | — | Внутреннее имя команды (латиница) |
| ТипКоманды | нет | из вида обработки | Тип запуска команды (см. маппинг ниже) | | ТипКоманды | нет | из вида обработки | Тип запуска команды (см. маппинг ниже) |
| Представление | нет | = Идентификатор | Отображаемое имя команды для пользователя | | Представление | нет | = Идентификатор | Отображаемое имя команды для пользователя |
| SrcDir | нет | `src` | Каталог исходников | | SrcDir | нет | `src` | Каталог исходников |
## Маппинг типов команд ## Маппинг типов команд
Пользователь может указать тип в свободной форме: Пользователь может указать тип в свободной форме:
| Пользователь пишет | ТипКоманды | | Пользователь пишет | ТипКоманды |
|---------------------------------------|-----------------------------------------------------| |---------------------------------------|-----------------------------------------------------|
| открыть форму, форма | `ТипКомандыОткрытиеФормы()` | | открыть форму, форма | `ТипКомандыОткрытиеФормы()` |
| клиентский метод, на клиенте | `ТипКомандыВызовКлиентскогоМетода()` | | клиентский метод, на клиенте | `ТипКомандыВызовКлиентскогоМетода()` |
| серверный метод, на сервере | `ТипКомандыВызовСерверногоМетода()` | | серверный метод, на сервере | `ТипКомандыВызовСерверногоМетода()` |
| заполнение формы, заполнить форму | `ТипКомандыЗаполнениеФормы()` | | заполнение формы, заполнить форму | `ТипКомандыЗаполнениеФормы()` |
| сценарий, безопасный режим | `ТипКомандыСценарийВБезопасномРежиме()` | | сценарий, безопасный режим | `ТипКомандыСценарийВБезопасномРежиме()` |
Если пользователь не указал тип — определи по виду обработки из существующего кода `СведенияОВнешнейОбработке()`: Если пользователь не указал тип — определи по виду обработки из существующего кода `СведенияОВнешнейОбработке()`:
| Вид обработки (из кода) | ТипКоманды по умолчанию | | Вид обработки (из кода) | ТипКоманды по умолчанию |
|----------------------------|-------------------------------------------| |----------------------------|-------------------------------------------|
| ДополнительнаяОбработка | `ТипКомандыОткрытиеФормы()` | | ДополнительнаяОбработка | `ТипКомандыОткрытиеФормы()` |
| ДополнительныйОтчет | `ТипКомандыОткрытиеФормы()` | | ДополнительныйОтчет | `ТипКомандыОткрытиеФормы()` |
| ЗаполнениеОбъекта | `ТипКомандыВызовСерверногоМетода()` | | ЗаполнениеОбъекта | `ТипКомандыВызовСерверногоМетода()` |
| Отчет | `ТипКомандыОткрытиеФормы()` | | Отчет | `ТипКомандыОткрытиеФормы()` |
| ПечатнаяФорма | `ТипКомандыВызовСерверногоМетода()` | | ПечатнаяФорма | `ТипКомандыВызовСерверногоМетода()` |
| СозданиеСвязанныхОбъектов | `ТипКомандыВызовСерверногоМетода()` | | СозданиеСвязанныхОбъектов | `ТипКомандыВызовСерверногоМетода()` |
## Шаблон добавления команды ## Шаблон добавления команды
Вставляется в `СведенияОВнешнейОбработке()` **перед** строкой `Возврат ПараметрыРегистрации`: Вставляется в `СведенияОВнешнейОбработке()` **перед** строкой `Возврат ПараметрыРегистрации`:
```bsl ```bsl
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить(); НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = НСтр("ru = '{{Представление}}'"); НоваяКоманда.Представление = НСтр("ru = '{{Представление}}'");
НоваяКоманда.Идентификатор = "{{Идентификатор}}"; НоваяКоманда.Идентификатор = "{{Идентификатор}}";
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ТипКоманды}}; НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ТипКоманды}};
НоваяКоманда.ПоказыватьОповещение = Ложь; НоваяКоманда.ПоказыватьОповещение = Ложь;
``` ```
Для печатных форм (ВидОбработкиПечатнаяФорма) добавь также: Для печатных форм (ВидОбработкиПечатнаяФорма) добавь также:
```bsl ```bsl
НоваяКоманда.Модификатор = "ПечатьMXL"; НоваяКоманда.Модификатор = "ПечатьMXL";
``` ```
Примечание: в отличие от первой команды (из `/epf-bsp-init`), дополнительные команды используют строковые литералы `НСтр("ru = '...'")` для представления и строку для идентификатора, а не `Метаданные()`. Примечание: в отличие от первой команды (из `/epf-bsp-init`), дополнительные команды используют строковые литералы `НСтр("ru = '...'")` для представления и строку для идентификатора, а не `Метаданные()`.
## Шаблоны обработчиков ## Шаблоны обработчиков
### ВызовСерверногоМетода — если обработчик уже есть ### ВызовСерверногоМетода — если обработчик уже есть
Если процедура `ВыполнитьКоманду` уже существует в модуле объекта, добавь ветку перед `КонецЕсли`: Если процедура `ВыполнитьКоманду` уже существует в модуле объекта, добавь ветку перед `КонецЕсли`:
```bsl ```bsl
ИначеЕсли ИдентификаторКоманды = "{{Идентификатор}}" Тогда ИначеЕсли ИдентификаторКоманды = "{{Идентификатор}}" Тогда
// TODO: Реализация {{Идентификатор}} // TODO: Реализация {{Идентификатор}}
``` ```
### ВызовСерверногоМетода — если обработчика нет ### ВызовСерверногоМетода — если обработчика нет
Для глобальных обработок (без `ОбъектыНазначения`): Для глобальных обработок (без `ОбъектыНазначения`):
```bsl ```bsl
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
// TODO: Реализация {{Идентификатор}} // TODO: Реализация {{Идентификатор}}
КонецЕсли; КонецЕсли;
КонецПроцедуры КонецПроцедуры
``` ```
Для назначаемых обработок (с `ОбъектыНазначения`): Для назначаемых обработок (с `ОбъектыНазначения`):
```bsl ```bsl
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
// TODO: Реализация {{Идентификатор}} // TODO: Реализация {{Идентификатор}}
КонецЕсли; КонецЕсли;
КонецПроцедуры КонецПроцедуры
``` ```
### ПечатнаяФорма — если процедура Печать уже есть ### ПечатнаяФорма — если процедура Печать уже есть
Добавь блок перед `КонецПроцедуры`: Добавь блок перед `КонецПроцедуры`:
```bsl ```bsl
ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "{{Идентификатор}}"); ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "{{Идентификатор}}");
Если ПечатнаяФорма <> Неопределено Тогда Если ПечатнаяФорма <> Неопределено Тогда
ПечатнаяФорма.ТабличныйДокумент = Сформировать{{Идентификатор}}(МассивОбъектов, ОбъектыПечати); ПечатнаяФорма.ТабличныйДокумент = Сформировать{{Идентификатор}}(МассивОбъектов, ОбъектыПечати);
ПечатнаяФорма.СинонимМакета = НСтр("ru = '{{Представление}}'"); ПечатнаяФорма.СинонимМакета = НСтр("ru = '{{Представление}}'");
КонецЕсли; КонецЕсли;
``` ```
### ПечатнаяФорма — если процедуры Печать нет ### ПечатнаяФорма — если процедуры Печать нет
```bsl ```bsl
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "{{Идентификатор}}"); ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "{{Идентификатор}}");
Если ПечатнаяФорма <> Неопределено Тогда Если ПечатнаяФорма <> Неопределено Тогда
ПечатнаяФорма.ТабличныйДокумент = Сформировать{{Идентификатор}}(МассивОбъектов, ОбъектыПечати); ПечатнаяФорма.ТабличныйДокумент = Сформировать{{Идентификатор}}(МассивОбъектов, ОбъектыПечати);
ПечатнаяФорма.СинонимМакета = НСтр("ru = '{{Представление}}'"); ПечатнаяФорма.СинонимМакета = НСтр("ru = '{{Представление}}'");
КонецЕсли; КонецЕсли;
КонецПроцедуры КонецПроцедуры
``` ```
### ВызовКлиентскогоМетода ### ВызовКлиентскогоМетода
Добавляется в **модуль формы** (`Forms/<FormName>/Ext/Form/Module.bsl`): Добавляется в **модуль формы** (`Forms/<FormName>/Ext/Form/Module.bsl`):
Для глобальных обработок: Для глобальных обработок:
```bsl ```bsl
&НаКлиенте &НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды) Экспорт Процедура ВыполнитьКоманду(ИдентификаторКоманды) Экспорт
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
// TODO: Реализация {{Идентификатор}} // TODO: Реализация {{Идентификатор}}
КонецЕсли; КонецЕсли;
КонецПроцедуры КонецПроцедуры
``` ```
Для назначаемых обработок: Для назначаемых обработок:
```bsl ```bsl
&НаКлиенте &НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначенияМассив) Экспорт Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначенияМассив) Экспорт
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
// TODO: Реализация {{Идентификатор}} // TODO: Реализация {{Идентификатор}}
КонецЕсли; КонецЕсли;
КонецПроцедуры КонецПроцедуры
``` ```
Если процедура уже есть — добавь ветку `ИначеЕсли`. Если процедура уже есть — добавь ветку `ИначеЕсли`.
## Инструкции ## Инструкции
1. Найди и прочитай `ObjectModule.bsl` через Glob: `src/{{ProcessorName}}/Ext/ObjectModule.bsl` 1. Найди и прочитай `ObjectModule.bsl` через Glob: `src/{{ProcessorName}}/Ext/ObjectModule.bsl`
2. Убедись что `СведенияОВнешнейОбработке()` существует. Если нет — предложи вызвать `/epf-bsp-init` 2. Убедись что `СведенияОВнешнейОбработке()` существует. Если нет — предложи вызвать `/epf-bsp-init`
3. Определи вид обработки из существующего кода (найди строку с `ВидОбработки...()`) 3. Определи вид обработки из существующего кода (найди строку с `ВидОбработки...()`)
4. Вставь блок команды **перед** `Возврат ПараметрыРегистрации` 4. Вставь блок команды **перед** `Возврат ПараметрыРегистрации`
5. Добавь обработчик: 5. Добавь обработчик:
- Для серверных обработчиков — в `ObjectModule.bsl`, область `ПрограммныйИнтерфейс` - Для серверных обработчиков — в `ObjectModule.bsl`, область `ПрограммныйИнтерфейс`
- Для клиентских обработчиков — в модуль формы (найти через Glob: `src/{{ProcessorName}}/Forms/*/Ext/Form/Module.bsl`) - Для клиентских обработчиков — в модуль формы (найти через Glob: `src/{{ProcessorName}}/Forms/*/Ext/Form/Module.bsl`)
6. Если обработчик (`ВыполнитьКоманду` / `Печать`) уже есть — добавь ветку, не создавай дубль процедуры 6. Если обработчик (`ВыполнитьКоманду` / `Печать`) уже есть — добавь ветку, не создавай дубль процедуры
7. Используй табы для отступов 7. Используй табы для отступов
## Пример ## Пример
Пользователь: `/epf-bsp-add-command МояОбработка ЗаказПокупателя серверный "Заказ покупателя"` Пользователь: `/epf-bsp-add-command МояОбработка ЗаказПокупателя серверный "Заказ покупателя"`
В `СведенияОВнешнейОбработке()` перед `Возврат` добавится: В `СведенияОВнешнейОбработке()` перед `Возврат` добавится:
```bsl ```bsl
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить(); НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = НСтр("ru = 'Заказ покупателя'"); НоваяКоманда.Представление = НСтр("ru = 'Заказ покупателя'");
НоваяКоманда.Идентификатор = "ЗаказПокупателя"; НоваяКоманда.Идентификатор = "ЗаказПокупателя";
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода(); НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
НоваяКоманда.ПоказыватьОповещение = Ложь; НоваяКоманда.ПоказыватьОповещение = Ложь;
``` ```
И в существующую процедуру `ВыполнитьКоманду` добавится блок обработки. И в существующую процедуру `ВыполнитьКоманду` добавится блок обработки.
@@ -1,208 +1,208 @@
--- ---
name: epf-bsp-init name: epf-bsp-init
description: Сформировать функцию `СведенияОВнешнейОбработке` в модуле объекта обработки — описание для подключения через подсистему БСП «Дополнительные отчёты и обработки». Используй когда нужно сделать обработку совместимой с БСП, подключаемой через «Дополнительные отчёты и обработки» description: Сформировать функцию `СведенияОВнешнейОбработке` в модуле объекта обработки — описание для подключения через подсистему БСП «Дополнительные отчёты и обработки». Используй когда нужно сделать обработку совместимой с БСП, подключаемой через «Дополнительные отчёты и обработки»
argument-hint: <ProcessorName> <Вид> argument-hint: <ProcessorName> <Вид>
allowed-tools: allowed-tools:
- Read - Read
- Edit - Edit
- Glob - Glob
- Grep - Grep
--- ---
# /epf-bsp-init — Регистрация обработки в БСП # /epf-bsp-init — Регистрация обработки в БСП
Добавляет в модуль объекта обработки функцию `СведенияОВнешнейОбработке()`, необходимую для регистрации в подсистеме «Дополнительные отчёты и обработки» БСП. Добавляет в модуль объекта обработки функцию `СведенияОВнешнейОбработке()`, необходимую для регистрации в подсистеме «Дополнительные отчёты и обработки» БСП.
## Usage ## Usage
``` ```
/epf-bsp-init <ProcessorName> <Вид> [Назначение...] /epf-bsp-init <ProcessorName> <Вид> [Назначение...]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|---------------|:------------:|--------------|---------------------------------------------------------| |---------------|:------------:|--------------|---------------------------------------------------------|
| ProcessorName | да | — | Имя обработки (должна быть создана через `/epf-init`) | | ProcessorName | да | — | Имя обработки (должна быть создана через `/epf-init`) |
| Вид | да | — | Вид обработки (см. маппинг ниже) | | Вид | да | — | Вид обработки (см. маппинг ниже) |
| Назначение | * | — | Объекты метаданных для назначаемых видов | | Назначение | * | — | Объекты метаданных для назначаемых видов |
| SrcDir | нет | `src` | Каталог исходников | | SrcDir | нет | `src` | Каталог исходников |
\* Назначение обязательно для видов: ЗаполнениеОбъекта, Отчет, ПечатнаяФорма, СозданиеСвязанныхОбъектов. \* Назначение обязательно для видов: ЗаполнениеОбъекта, Отчет, ПечатнаяФорма, СозданиеСвязанныхОбъектов.
## Маппинг вида обработки ## Маппинг вида обработки
Пользователь может указать вид в свободной форме. Определи нужный по контексту: Пользователь может указать вид в свободной форме. Определи нужный по контексту:
| Пользователь пишет | Вид | API-метод | | Пользователь пишет | Вид | API-метод |
|-------------------------------------------|----------------------------|----------------------------------------------| |-------------------------------------------|----------------------------|----------------------------------------------|
| доп обработка, обработка, глобальная | ДополнительнаяОбработка | `ВидОбработкиДополнительнаяОбработка()` | | доп обработка, обработка, глобальная | ДополнительнаяОбработка | `ВидОбработкиДополнительнаяОбработка()` |
| доп отчёт, глобальный отчёт | ДополнительныйОтчет | `ВидОбработкиДополнительныйОтчет()` | | доп отчёт, глобальный отчёт | ДополнительныйОтчет | `ВидОбработкиДополнительныйОтчет()` |
| заполнение, заполнить | ЗаполнениеОбъекта | `ВидОбработкиЗаполнениеОбъекта()` | | заполнение, заполнить | ЗаполнениеОбъекта | `ВидОбработкиЗаполнениеОбъекта()` |
| отчёт (назначаемый, для объекта) | Отчет | `ВидОбработкиОтчет()` | | отчёт (назначаемый, для объекта) | Отчет | `ВидОбработкиОтчет()` |
| печатная форма, печать | ПечатнаяФорма | `ВидОбработкиПечатнаяФорма()` | | печатная форма, печать | ПечатнаяФорма | `ВидОбработкиПечатнаяФорма()` |
| создание связанных объектов | СозданиеСвязанныхОбъектов | `ВидОбработкиСозданиеСвязанныхОбъектов()` | | создание связанных объектов | СозданиеСвязанныхОбъектов | `ВидОбработкиСозданиеСвязанныхОбъектов()` |
## Тип команды по умолчанию ## Тип команды по умолчанию
| Вид | ТипКоманды по умолчанию | | Вид | ТипКоманды по умолчанию |
|----------------------------|-------------------------------------------| |----------------------------|-------------------------------------------|
| ДополнительнаяОбработка | `ТипКомандыОткрытиеФормы()` | | ДополнительнаяОбработка | `ТипКомандыОткрытиеФормы()` |
| ДополнительныйОтчет | `ТипКомандыОткрытиеФормы()` | | ДополнительныйОтчет | `ТипКомандыОткрытиеФормы()` |
| ЗаполнениеОбъекта | `ТипКомандыВызовСерверногоМетода()` | | ЗаполнениеОбъекта | `ТипКомандыВызовСерверногоМетода()` |
| Отчет | `ТипКомандыОткрытиеФормы()` | | Отчет | `ТипКомандыОткрытиеФормы()` |
| ПечатнаяФорма | `ТипКомандыВызовСерверногоМетода()` | | ПечатнаяФорма | `ТипКомандыВызовСерверногоМетода()` |
| СозданиеСвязанныхОбъектов | `ТипКомандыВызовСерверногоМетода()` | | СозданиеСвязанныхОбъектов | `ТипКомандыВызовСерверногоМетода()` |
## Шаблон: СведенияОВнешнейОбработке ## Шаблон: СведенияОВнешнейОбработке
Базовый шаблон — одинаковый для всех видов, отличаются только вызовы API-методов и условные секции. Базовый шаблон — одинаковый для всех видов, отличаются только вызовы API-методов и условные секции.
```bsl ```bsl
Функция СведенияОВнешнейОбработке() Экспорт Функция СведенияОВнешнейОбработке() Экспорт
МетаданныеОбработки = Метаданные(); МетаданныеОбработки = Метаданные();
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1"); ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ВидОбработки}}; ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ВидОбработки}};
ПараметрыРегистрации.Версия = "1.0"; ПараметрыРегистрации.Версия = "1.0";
{{СЕКЦИЯ_НАЗНАЧЕНИЕ}} {{СЕКЦИЯ_НАЗНАЧЕНИЕ}}
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить(); НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = МетаданныеОбработки.Представление(); НоваяКоманда.Представление = МетаданныеОбработки.Представление();
НоваяКоманда.Идентификатор = МетаданныеОбработки.Имя; НоваяКоманда.Идентификатор = МетаданныеОбработки.Имя;
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ТипКоманды}}; НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ТипКоманды}};
НоваяКоманда.ПоказыватьОповещение = Ложь; НоваяКоманда.ПоказыватьОповещение = Ложь;
{{СЕКЦИЯ_МОДИФИКАТОР}} {{СЕКЦИЯ_МОДИФИКАТОР}}
Возврат ПараметрыРегистрации; Возврат ПараметрыРегистрации;
КонецФункции КонецФункции
``` ```
### Подстановки ### Подстановки
- `{{ВидОбработки}}` — API-метод из таблицы маппинга вида - `{{ВидОбработки}}` — API-метод из таблицы маппинга вида
- `{{ТипКоманды}}` — API-метод из таблицы типа команды по умолчанию - `{{ТипКоманды}}` — API-метод из таблицы типа команды по умолчанию
### Условные секции ### Условные секции
**`{{СЕКЦИЯ_НАЗНАЧЕНИЕ}}`** — только для назначаемых видов (ЗаполнениеОбъекта, Отчет, ПечатнаяФорма, СозданиеСвязанныхОбъектов). Одна строка на каждый объект: **`{{СЕКЦИЯ_НАЗНАЧЕНИЕ}}`** — только для назначаемых видов (ЗаполнениеОбъекта, Отчет, ПечатнаяФорма, СозданиеСвязанныхОбъектов). Одна строка на каждый объект:
```bsl ```bsl
ПараметрыРегистрации.Назначение.Добавить("Документ.СчетНаОплату"); ПараметрыРегистрации.Назначение.Добавить("Документ.СчетНаОплату");
``` ```
Формат имени объекта: `ИмяКлассаОбъектаМетаданного.ИмяОбъекта` (например `Документ.СчетНаОплату`, `Справочник.Контрагенты`). Формат имени объекта: `ИмяКлассаОбъектаМетаданного.ИмяОбъекта` (например `Документ.СчетНаОплату`, `Справочник.Контрагенты`).
Для глобальных видов (ДополнительнаяОбработка, ДополнительныйОтчет) — секция не нужна, удалить вместе с пустой строкой. Для глобальных видов (ДополнительнаяОбработка, ДополнительныйОтчет) — секция не нужна, удалить вместе с пустой строкой.
**`{{СЕКЦИЯ_МОДИФИКАТОР}}`** — только для ПечатнаяФорма: **`{{СЕКЦИЯ_МОДИФИКАТОР}}`** — только для ПечатнаяФорма:
```bsl ```bsl
НоваяКоманда.Модификатор = "ПечатьMXL"; НоваяКоманда.Модификатор = "ПечатьMXL";
``` ```
Для остальных видов — удалить вместе с пустой строкой. Для остальных видов — удалить вместе с пустой строкой.
## Шаблоны серверных обработчиков ## Шаблоны серверных обработчиков
Для видов с типом команды `ВызовСерверногоМетода` добавь соответствующую процедуру-обработчик в ту же область `ПрограммныйИнтерфейс`, после `СведенияОВнешнейОбработке`. Для видов с типом команды `ВызовСерверногоМетода` добавь соответствующую процедуру-обработчик в ту же область `ПрограммныйИнтерфейс`, после `СведенияОВнешнейОбработке`.
### Для ЗаполнениеОбъекта / СозданиеСвязанныхОбъектов ### Для ЗаполнениеОбъекта / СозданиеСвязанныхОбъектов
```bsl ```bsl
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт
// TODO: Реализация // TODO: Реализация
КонецПроцедуры КонецПроцедуры
``` ```
### Для ПечатнаяФорма ### Для ПечатнаяФорма
```bsl ```bsl
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
// TODO: Реализация // TODO: Реализация
КонецПроцедуры КонецПроцедуры
``` ```
### Для ДополнительнаяОбработка / ДополнительныйОтчет (с ВызовСерверногоМетода) ### Для ДополнительнаяОбработка / ДополнительныйОтчет (с ВызовСерверногоМетода)
Если пользователь явно выбрал серверный метод вместо открытия формы: Если пользователь явно выбрал серверный метод вместо открытия формы:
```bsl ```bsl
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт
// TODO: Реализация // TODO: Реализация
КонецПроцедуры КонецПроцедуры
``` ```
Обрати внимание: у глобальных обработок нет параметра `ОбъектыНазначения`. Обрати внимание: у глобальных обработок нет параметра `ОбъектыНазначения`.
## Инструкции ## Инструкции
1. Найди `ObjectModule.bsl` через Glob: `src/{{ProcessorName}}/Ext/ObjectModule.bsl` 1. Найди `ObjectModule.bsl` через Glob: `src/{{ProcessorName}}/Ext/ObjectModule.bsl`
2. Прочитай файл 2. Прочитай файл
3. Если `СведенияОВнешнейОбработке` уже есть — сообщи пользователю и не дублируй 3. Если `СведенияОВнешнейОбработке` уже есть — сообщи пользователю и не дублируй
4. Если файл не найден — предложи сначала вызвать `/epf-init` 4. Если файл не найден — предложи сначала вызвать `/epf-init`
5. Найди область `#Область ПрограммныйИнтерфейс` ... `#КонецОбласти` 5. Найди область `#Область ПрограммныйИнтерфейс` ... `#КонецОбласти`
6. Вставь функцию `СведенияОВнешнейОбработке()` внутрь этой области 6. Вставь функцию `СведенияОВнешнейОбработке()` внутрь этой области
7. Если вид требует серверный обработчик — вставь его тоже в эту область, после функции 7. Если вид требует серверный обработчик — вставь его тоже в эту область, после функции
8. Используй табы для отступов (как в исходном файле) 8. Используй табы для отступов (как в исходном файле)
## Пример ## Пример
Пользователь: `/epf-bsp-init МояОбработка печатная форма для Документ.СчетНаОплату` Пользователь: `/epf-bsp-init МояОбработка печатная форма для Документ.СчетНаОплату`
Результат в `ObjectModule.bsl`: Результат в `ObjectModule.bsl`:
```bsl ```bsl
#Область ОписаниеПеременных #Область ОписаниеПеременных
#КонецОбласти #КонецОбласти
#Область ПрограммныйИнтерфейс #Область ПрограммныйИнтерфейс
Функция СведенияОВнешнейОбработке() Экспорт Функция СведенияОВнешнейОбработке() Экспорт
МетаданныеОбработки = Метаданные(); МетаданныеОбработки = Метаданные();
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1"); ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма(); ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма();
ПараметрыРегистрации.Версия = "1.0"; ПараметрыРегистрации.Версия = "1.0";
ПараметрыРегистрации.Назначение.Добавить("Документ.СчетНаОплату"); ПараметрыРегистрации.Назначение.Добавить("Документ.СчетНаОплату");
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить(); НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = МетаданныеОбработки.Представление(); НоваяКоманда.Представление = МетаданныеОбработки.Представление();
НоваяКоманда.Идентификатор = МетаданныеОбработки.Имя; НоваяКоманда.Идентификатор = МетаданныеОбработки.Имя;
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода(); НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
НоваяКоманда.ПоказыватьОповещение = Ложь; НоваяКоманда.ПоказыватьОповещение = Ложь;
НоваяКоманда.Модификатор = "ПечатьMXL"; НоваяКоманда.Модификатор = "ПечатьMXL";
Возврат ПараметрыРегистрации; Возврат ПараметрыРегистрации;
КонецФункции КонецФункции
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
// TODO: Реализация // TODO: Реализация
КонецПроцедуры КонецПроцедуры
#КонецОбласти #КонецОбласти
#Область СлужебныеПроцедурыИФункции #Область СлужебныеПроцедурыИФункции
#КонецОбласти #КонецОбласти
``` ```
## Дальнейшие шаги ## Дальнейшие шаги
- Добавить ещё команду: `/epf-bsp-add-command` - Добавить ещё команду: `/epf-bsp-add-command`
- Добавить форму: `/form-add` - Добавить форму: `/form-add`
- Добавить макет: `/template-add` - Добавить макет: `/template-add`
- Собрать EPF: `/epf-build` - Собрать EPF: `/epf-build`
@@ -1,69 +1,69 @@
--- ---
name: epf-build name: epf-build
description: Собрать внешнюю обработку 1С (EPF/ERF) из XML-исходников. Используй когда пользователь просит собрать, скомпилировать обработку или получить EPF/ERF файл из исходников description: Собрать внешнюю обработку 1С (EPF/ERF) из XML-исходников. Используй когда пользователь просит собрать, скомпилировать обработку или получить EPF/ERF файл из исходников
argument-hint: <ProcessorName> argument-hint: <ProcessorName>
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- Grep - Grep
--- ---
# /epf-build — Сборка обработки # /epf-build — Сборка обработки
## Usage ## Usage
``` ```
/epf-build <ProcessorName> [SrcDir] [OutDir] /epf-build <ProcessorName> [SrcDir] [OutDir]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|---------------|:------------:|--------------|--------------------------------------| |---------------|:------------:|--------------|--------------------------------------|
| ProcessorName | да | — | Имя обработки (имя корневого XML) | | ProcessorName | да | — | Имя обработки (имя корневого XML) |
| SrcDir | нет | `src` | Каталог исходников | | SrcDir | нет | `src` | Каталог исходников |
| OutDir | нет | `build` | Каталог для результата | | OutDir | нет | `build` | Каталог для результата |
## Параметры подключения (опционально) ## Параметры подключения (опционально)
Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы. Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы.
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу: 1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
5. Если ветка не совпала — используй `default` 5. Если ветка не совпала — используй `default`
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для EPF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки. 6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для EPF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников | | `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников |
| `-OutputFile <путь>` | да | Путь к выходному EPF/ERF-файлу | | `-OutputFile <путь>` | да | Путь к выходному EPF/ERF-файлу |
> `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных > `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных
## Примеры ## Примеры
```powershell ```powershell
# Сборка обработки (файловая база) # Сборка обработки (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf" powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf" powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
``` ```
@@ -1,198 +1,225 @@
# epf-build v1.1 — Build external data processor or report (EPF/ERF) from XML sources # epf-build v1.4 — Build external data processor or report (EPF/ERF) from XML sources
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Сборка внешней обработки/отчёта 1С из XML-исходников Сборка внешней обработки/отчёта 1С из XML-исходников
.DESCRIPTION .DESCRIPTION
Собирает EPF/ERF-файл из XML-исходников с помощью платформы 1С. Собирает EPF/ERF-файл из XML-исходников с помощью платформы 1С.
Общий скрипт для epf-build и erf-build. Общий скрипт для epf-build и erf-build.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER SourceFile .PARAMETER SourceFile
Путь к корневому XML-файлу исходников Путь к корневому XML-файлу исходников
.PARAMETER OutputFile .PARAMETER OutputFile
Путь к выходному EPF/ERF-файлу Путь к выходному EPF/ERF-файлу
.EXAMPLE .EXAMPLE
.\epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src\МояОбработка.xml" -OutputFile "build\МояОбработка.epf" .\epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src\МояОбработка.xml" -OutputFile "build\МояОбработка.epf"
.EXAMPLE .EXAMPLE
.\epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src\МойОтчёт.xml" -OutputFile "build\МойОтчёт.erf" .\epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src\МойОтчёт.xml" -OutputFile "build\МойОтчёт.erf"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$SourceFile, [string]$SourceFile,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$OutputFile [string]$OutputFile
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Auto-create stub database if no connection specified --- # --- Detect engine (ibcmd vs 1cv8) by exe name ---
$autoCreatedBase = $null $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { if ($engine -eq "ibcmd" -and $InfoBaseServer -and $InfoBaseRef) {
$sourceDir = Split-Path $SourceFile -Parent Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath or omit for stub)" -ForegroundColor Red
$autoBasePath = Join-Path $env:TEMP "epf_stub_db_$(Get-Random)" exit 1
$stubScript = Join-Path $PSScriptRoot "stub-db-create.ps1" }
Write-Host "No database specified. Creating temporary stub database..."
$stubArgs = "-SourceDir `"$sourceDir`" -V8Path `"$V8Path`" -TempBasePath `"$autoBasePath`"" # --- Auto-create stub database if no connection specified ---
$stubProc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$stubScript`" $stubArgs" -NoNewWindow -Wait -PassThru $autoCreatedBase = $null
if ($stubProc.ExitCode -ne 0) { if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
Write-Host "Error: failed to create stub database" -ForegroundColor Red $sourceDir = Split-Path $SourceFile -Parent
exit 1 $autoBasePath = Join-Path $env:TEMP "epf_stub_db_$(Get-Random)"
} $stubScript = Join-Path $PSScriptRoot "stub-db-create.ps1"
$InfoBasePath = $autoBasePath Write-Host "No database specified. Creating temporary stub database..."
$autoCreatedBase = $autoBasePath $stubArgs = "-SourceDir `"$sourceDir`" -V8Path `"$V8Path`" -TempBasePath `"$autoBasePath`""
} $stubProc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$stubScript`" $stubArgs" -NoNewWindow -Wait -PassThru
if ($stubProc.ExitCode -ne 0) {
# --- Validate source file --- Write-Host "Error: failed to create stub database" -ForegroundColor Red
if (-not (Test-Path $SourceFile)) { exit 1
Write-Host "Error: source file not found: $SourceFile" -ForegroundColor Red }
exit 1 $InfoBasePath = $autoBasePath
} $autoCreatedBase = $autoBasePath
}
# --- Ensure output directory exists ---
$outDir = Split-Path $OutputFile -Parent # --- Validate source file ---
if ($outDir -and -not (Test-Path $outDir)) { if (-not (Test-Path $SourceFile)) {
New-Item -ItemType Directory -Path $outDir -Force | Out-Null Write-Host "Error: source file not found: $SourceFile" -ForegroundColor Red
} exit 1
}
# --- Temp dir ---
$tempDir = Join-Path $env:TEMP "epf_build_$(Get-Random)" # --- Ensure output directory exists ---
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null $outDir = Split-Path $OutputFile -Parent
if ($outDir -and -not (Test-Path $outDir)) {
try { New-Item -ItemType Directory -Path $outDir -Force | Out-Null
# --- Build arguments --- }
$arguments = @("DESIGNER")
# --- Temp dir ---
if ($InfoBaseServer -and $InfoBaseRef) { $tempDir = Join-Path $env:TEMP "epf_build_$(Get-Random)"
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`"" New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
} else {
$arguments += "/F", "`"$InfoBasePath`"" try {
} if ($engine -eq "ibcmd") {
# --- ibcmd branch: build EPF/ERF via config import --out ---
if ($UserName) { $arguments += "/N`"$UserName`"" } $srcDir = Split-Path $SourceFile -Parent
if ($Password) { $arguments += "/P`"$Password`"" } $arguments = @("infobase", "config", "import", "$srcDir", "--out=$OutputFile", "--db-path=$InfoBasePath")
if ($UserName) { $arguments += "--user=$UserName" }
$arguments += "/LoadExternalDataProcessorOrReportFromFiles", "`"$SourceFile`"", "`"$OutputFile`"" if ($Password) { $arguments += "--password=$Password" }
$arguments += "--data=$tempDir"
# --- Output --- Write-Host "Running: ibcmd $($arguments -join ' ')"
$outFile = Join-Path $tempDir "build_log.txt" $output = & $V8Path @arguments 2>&1
$arguments += "/Out", "`"$outFile`"" $exitCode = $LASTEXITCODE
$arguments += "/DisableStartupDialogs" if ($exitCode -eq 0) {
Write-Host "External data processor/report built successfully: $OutputFile" -ForegroundColor Green
# --- Execute --- } else {
Write-Host "Running: 1cv8.exe $($arguments -join ' ')" Write-Host "Error building external data processor/report (code: $exitCode)" -ForegroundColor Red
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru }
$exitCode = $process.ExitCode if ($output) { Write-Host ($output | Out-String) }
exit $exitCode
# --- Result --- }
if ($exitCode -eq 0) {
Write-Host "Build completed successfully: $OutputFile" -ForegroundColor Green # --- 1cv8 branch ---
} else { # --- Build arguments ---
Write-Host "Error building (code: $exitCode)" -ForegroundColor Red $arguments = @("DESIGNER")
}
if ($InfoBaseServer -and $InfoBaseRef) {
if (Test-Path $outFile) { $arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue } else {
if ($logContent) { $arguments += "/F", "`"$InfoBasePath`""
Write-Host "--- Log ---" }
Write-Host $logContent
Write-Host "--- End ---" if ($UserName) { $arguments += "/N`"$UserName`"" }
} if ($Password) { $arguments += "/P`"$Password`"" }
}
$arguments += "/LoadExternalDataProcessorOrReportFromFiles", "`"$SourceFile`"", "`"$OutputFile`""
exit $exitCode
# --- Output ---
} finally { $outFile = Join-Path $tempDir "build_log.txt"
if (Test-Path $tempDir) { $arguments += "/Out", "`"$outFile`""
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue $arguments += "/DisableStartupDialogs"
}
if ($autoCreatedBase -and (Test-Path $autoCreatedBase)) { # --- Execute ---
Remove-Item -Path $autoCreatedBase -Recurse -Force -ErrorAction SilentlyContinue Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
} $process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
} $exitCode = $process.ExitCode
# --- Result ---
if ($exitCode -eq 0) {
Write-Host "Build completed successfully: $OutputFile" -ForegroundColor Green
} else {
Write-Host "Error building (code: $exitCode)" -ForegroundColor Red
}
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
if ($autoCreatedBase -and (Test-Path $autoCreatedBase)) {
Remove-Item -Path $autoCreatedBase -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# epf-build v1.1 — Build external data processor or report (EPF/ERF) from XML sources # epf-build v1.4 — Build external data processor or report (EPF/ERF) from XML sources
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -84,6 +85,10 @@ def main():
# --- Resolve V8Path --- # --- Resolve V8Path ---
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
if engine == "ibcmd" and args.InfoBaseServer and args.InfoBaseRef:
print("Error: ibcmd supports file infobases only (use -InfoBasePath or omit for stub)", file=sys.stderr)
sys.exit(1)
# --- Auto-create stub database if no connection specified --- # --- Auto-create stub database if no connection specified ---
auto_created_base = None auto_created_base = None
@@ -117,6 +122,29 @@ def main():
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
try: try:
if engine == "ibcmd":
# --- ibcmd branch: build EPF/ERF via config import --out ---
src_dir = os.path.dirname(os.path.abspath(args.SourceFile))
arguments = ["infobase", "config", "import", src_dir, f"--out={args.OutputFile}", f"--db-path={args.InfoBasePath}"]
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print(f"External data processor/report built successfully: {args.OutputFile}")
else:
print(f"Error building external data processor/report (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Build arguments --- # --- Build arguments ---
arguments = ["DESIGNER"] arguments = ["DESIGNER"]
@@ -1,4 +1,4 @@
# stub-db-create v1.0 — Create temp 1C infobase with metadata stubs for EPF/ERF build # stub-db-create v1.2 — Create temp 1C infobase with metadata stubs for EPF/ERF build
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -1252,6 +1252,29 @@ $propsXml </Properties>$childObjLine
} }
} }
# --- 5a. Stub via ibcmd (one call: create [--import --apply]) ---
$stubEngine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
if ($stubEngine -eq "ibcmd") {
Write-Host "Creating infobase (ibcmd): $TempBasePath"
$ibData = Join-Path $env:TEMP "stub_data_$(Get-Random)"
New-Item -ItemType Directory -Path $ibData -Force | Out-Null
$ibArgs = @("infobase", "create", "--db-path=$TempBasePath", "--create-database")
if ($hasRefTypes) { $ibArgs += "--import=$(Join-Path $TempBasePath 'cfg')", "--apply", "--force" }
$ibArgs += "--data=$ibData"
$ibOut = & $V8Path @ibArgs 2>&1
$ibRc = $LASTEXITCODE
Remove-Item -Path $ibData -Recurse -Force -ErrorAction SilentlyContinue
if ($ibRc -ne 0) {
if ($ibOut) { Write-Host ($ibOut | Out-String) }
Write-Error "Failed to create stub infobase (code: $ibRc)"
exit 1
}
if ($hasRefTypes) { Remove-Item -Path (Join-Path $TempBasePath "cfg") -Recurse -Force -ErrorAction SilentlyContinue }
Write-Host "[OK] Stub database created: $TempBasePath"
Write-Host $TempBasePath
exit 0
}
# --- 5. Create infobase --- # --- 5. Create infobase ---
Write-Host "Creating infobase: $TempBasePath" Write-Host "Creating infobase: $TempBasePath"
$createArgs = "CREATEINFOBASE File=`"$TempBasePath`" /DisableStartupDialogs" $createArgs = "CREATEINFOBASE File=`"$TempBasePath`" /DisableStartupDialogs"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# stub-db-create v1.0 — Create temp 1C infobase with metadata stubs for EPF/ERF build # stub-db-create v1.2 — Create temp 1C infobase with metadata stubs for EPF/ERF build
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -1034,6 +1034,32 @@ def main():
if register_columns: if register_columns:
print('WARNING: Register column categories (Dimension/Resource/Attribute) are guessed. Form field bindings may not survive round-trip through a real database.') print('WARNING: Register column categories (Dimension/Resource/Attribute) are guessed. Form field bindings may not survive round-trip through a real database.')
# Stub via ibcmd (one call: create [--import --apply])
stub_engine = "ibcmd" if os.path.basename(args.V8Path).lower().startswith("ibcmd") else "1cv8"
if stub_engine == "ibcmd":
import shutil
print(f'Creating infobase (ibcmd): {temp_base}')
ib_data = tempfile.mkdtemp(prefix="stub_data_")
ib_args = [args.V8Path, 'infobase', 'create', f'--db-path={temp_base}', '--create-database']
if has_ref_types:
ib_args += [f'--import={os.path.join(temp_base, "cfg")}', '--apply', '--force']
ib_args.append(f'--data={ib_data}')
result = subprocess.run(ib_args, capture_output=True, encoding='utf-8', errors='replace')
shutil.rmtree(ib_data, ignore_errors=True)
if result.returncode != 0:
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
print(f'Failed to create stub infobase (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
if has_ref_types:
import shutil
shutil.rmtree(os.path.join(temp_base, 'cfg'), ignore_errors=True)
print(f'[OK] Stub database created: {temp_base}')
print(temp_base)
sys.exit(0)
# Create infobase # Create infobase
print(f'Creating infobase: {temp_base}') print(f'Creating infobase: {temp_base}')
result = subprocess.run( result = subprocess.run(
@@ -1,69 +1,69 @@
--- ---
name: epf-dump name: epf-dump
description: Разобрать EPF-файл обработки 1С (EPF/ERF) в XML-исходники. Используй когда пользователь просит разобрать, декомпилировать обработку, получить исходники из EPF/ERF файла description: Разобрать EPF-файл обработки 1С (EPF/ERF) в XML-исходники. Используй когда пользователь просит разобрать, декомпилировать обработку, получить исходники из EPF/ERF файла
argument-hint: <EpfFile> argument-hint: <EpfFile>
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- Grep - Grep
--- ---
# /epf-dump — Разборка обработки # /epf-dump — Разборка обработки
## Usage ## Usage
``` ```
/epf-dump <EpfFile> [OutDir] /epf-dump <EpfFile> [OutDir]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|----------|:------------:|--------------|-------------------------------------| |----------|:------------:|--------------|-------------------------------------|
| EpfFile | да | — | Путь к EPF-файлу | | EpfFile | да | — | Путь к EPF-файлу |
| OutDir | нет | `src` | Каталог для выгрузки исходников | | OutDir | нет | `src` | Каталог для выгрузки исходников |
## Параметры подключения (обязательно) ## Параметры подключения (обязательно)
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются. Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу: 1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
5. Если ветка не совпала — используй `default` 5. Если ветка не совпала — используй `default`
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`. 6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-InputFile <путь>` | да | Путь к EPF/ERF-файлу | | `-InputFile <путь>` | да | Путь к EPF/ERF-файлу |
| `-OutputDir <путь>` | да | Каталог для выгрузки исходников | | `-OutputDir <путь>` | да | Каталог для выгрузки исходников |
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` | | `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
> `*` — обязательно хотя бы одно подключение. Без базы скрипт завершится с ошибкой (dump в пустой базе безвозвратно теряет ссылочные типы) > `*` — обязательно хотя бы одно подключение. Без базы скрипт завершится с ошибкой (dump в пустой базе безвозвратно теряет ссылочные типы)
## Примеры ## Примеры
```powershell ```powershell
# Разборка обработки (файловая база) # Разборка обработки (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src" powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src" powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src"
``` ```
@@ -1,192 +1,224 @@
# epf-dump v1.1 — Dump external data processor or report (EPF/ERF) to XML sources # epf-dump v1.4 — Dump external data processor or report (EPF/ERF) to XML sources
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
Разборка внешней обработки/отчёта 1С в XML-исходники Разборка внешней обработки/отчёта 1С в XML-исходники
.DESCRIPTION .DESCRIPTION
Разбирает EPF/ERF-файл во XML-исходники с помощью платформы 1С. Разбирает EPF/ERF-файл во XML-исходники с помощью платформы 1С.
Общий скрипт для epf-dump и erf-dump. Общий скрипт для epf-dump и erf-dump.
.PARAMETER V8Path .PARAMETER V8Path
Путь к каталогу bin платформы или к 1cv8.exe Путь к каталогу bin платформы или к 1cv8.exe
.PARAMETER InfoBasePath .PARAMETER InfoBasePath
Путь к файловой информационной базе Путь к файловой информационной базе
.PARAMETER InfoBaseServer .PARAMETER InfoBaseServer
Сервер 1С (для серверной базы) Сервер 1С (для серверной базы)
.PARAMETER InfoBaseRef .PARAMETER InfoBaseRef
Имя базы на сервере Имя базы на сервере
.PARAMETER UserName .PARAMETER UserName
Имя пользователя 1С Имя пользователя 1С
.PARAMETER Password .PARAMETER Password
Пароль пользователя Пароль пользователя
.PARAMETER InputFile .PARAMETER InputFile
Путь к EPF/ERF-файлу Путь к EPF/ERF-файлу
.PARAMETER OutputDir .PARAMETER OutputDir
Каталог для выгрузки исходников Каталог для выгрузки исходников
.PARAMETER Format .PARAMETER Format
Формат выгрузки: Hierarchical или Plain (по умолчанию Hierarchical) Формат выгрузки: Hierarchical или Plain (по умолчанию Hierarchical)
.EXAMPLE .EXAMPLE
.\epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build\МояОбработка.epf" -OutputDir "src" .\epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build\МояОбработка.epf" -OutputDir "src"
.EXAMPLE .EXAMPLE
.\epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build\МойОтчёт.erf" -OutputDir "src" .\epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build\МойОтчёт.erf" -OutputDir "src"
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$V8Path, [string]$V8Path,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBasePath, [string]$InfoBasePath,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseServer, [string]$InfoBaseServer,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$InfoBaseRef, [string]$InfoBaseRef,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$UserName, [string]$UserName,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$Password, [string]$Password,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$InputFile, [string]$InputFile,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$OutputDir, [string]$OutputDir,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ValidateSet("Hierarchical", "Plain")] [ValidateSet("Hierarchical", "Plain")]
[string]$Format = "Hierarchical" [string]$Format = "Hierarchical"
) )
$OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve V8Path --- # --- Resolve V8Path ---
function Find-ProjectV8Path { function Find-ProjectV8Path {
$dir = (Get-Location).Path $dir = (Get-Location).Path
while ($dir) { while ($dir) {
$pf = Join-Path $dir ".v8-project.json" $pf = Join-Path $dir ".v8-project.json"
if (Test-Path $pf) { if (Test-Path $pf) {
try { try {
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json $j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($j.v8path) { return [string]$j.v8path } if ($j.v8path) { return [string]$j.v8path }
} catch {} } catch {}
return $null return $null
} }
$parent = Split-Path $dir -Parent $parent = Split-Path $dir -Parent
if (-not $parent -or $parent -eq $dir) { break } if (-not $parent -or $parent -eq $dir) { break }
$dir = $parent $dir = $parent
} }
return $null return $null
} }
if (-not $V8Path) { if (-not $V8Path) {
$V8Path = Find-ProjectV8Path $V8Path = Find-ProjectV8Path
} }
if (-not $V8Path) { if (-not $V8Path) {
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue | $found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending | Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
Select-Object -First 1 Select-Object -First 1
if ($found) { if ($found) {
$V8Path = $found.FullName $V8Path = $found.FullName
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
} else { } else {
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
exit 1 exit 1
} }
} }
if (Test-Path $V8Path -PathType Container) { if (Test-Path $V8Path -PathType Container) {
$V8Path = Join-Path $V8Path "1cv8.exe" $V8Path = Join-Path $V8Path "1cv8.exe"
} }
if (-not (Test-Path $V8Path)) { if (-not (Test-Path $V8Path)) {
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
exit 1 exit 1
} }
# --- Validate database connection --- # --- Validate database connection ---
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) { if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
Write-Host "Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef" -ForegroundColor Red Write-Host "Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef" -ForegroundColor Red
Write-Host "Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly." -ForegroundColor Yellow Write-Host "Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly." -ForegroundColor Yellow
exit 1 exit 1
} }
# --- Validate input file --- # --- Detect engine (ibcmd vs 1cv8) by exe name ---
if (-not (Test-Path $InputFile)) { $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red if ($engine -eq "ibcmd") {
exit 1 if (-not $InfoBasePath) {
} Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
exit 1
# --- Ensure output directory exists --- }
if (-not (Test-Path $OutputDir)) { if ($Format -eq "Plain") {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null Write-Host "Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
} exit 1
}
# --- Temp dir --- }
$tempDir = Join-Path $env:TEMP "epf_dump_$(Get-Random)"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null # --- Validate input file ---
if (-not (Test-Path $InputFile)) {
try { Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red
# --- Build arguments --- exit 1
$arguments = @("DESIGNER") }
if ($InfoBaseServer -and $InfoBaseRef) { # --- Ensure output directory exists ---
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`"" if (-not (Test-Path $OutputDir)) {
} else { New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
$arguments += "/F", "`"$InfoBasePath`"" }
}
# --- Temp dir ---
if ($UserName) { $arguments += "/N`"$UserName`"" } $tempDir = Join-Path $env:TEMP "epf_dump_$(Get-Random)"
if ($Password) { $arguments += "/P`"$Password`"" } New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
$arguments += "/DumpExternalDataProcessorOrReportToFiles", "`"$OutputDir`"", "`"$InputFile`"" try {
$arguments += "-Format", $Format if ($engine -eq "ibcmd") {
# --- ibcmd branch: dump EPF/ERF via config export --file ---
# --- Output --- $arguments = @("infobase", "config", "export", "--file=$InputFile", "$OutputDir", "--db-path=$InfoBasePath")
$outFile = Join-Path $tempDir "dump_log.txt" if ($UserName) { $arguments += "--user=$UserName" }
$arguments += "/Out", "`"$outFile`"" if ($Password) { $arguments += "--password=$Password" }
$arguments += "/DisableStartupDialogs" $arguments += "--data=$tempDir"
Write-Host "Running: ibcmd $($arguments -join ' ')"
# --- Execute --- $output = & $V8Path @arguments 2>&1
Write-Host "Running: 1cv8.exe $($arguments -join ' ')" $exitCode = $LASTEXITCODE
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru if ($exitCode -eq 0) {
$exitCode = $process.ExitCode Write-Host "External data processor/report dumped successfully to: $OutputDir" -ForegroundColor Green
} else {
# --- Result --- Write-Host "Error dumping external data processor/report (code: $exitCode)" -ForegroundColor Red
if ($exitCode -eq 0) { }
Write-Host "Dump completed successfully to: $OutputDir" -ForegroundColor Green if ($output) { Write-Host ($output | Out-String) }
} else { exit $exitCode
Write-Host "Error dumping (code: $exitCode)" -ForegroundColor Red }
}
# --- 1cv8 branch ---
if (Test-Path $outFile) { # --- Build arguments ---
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue $arguments = @("DESIGNER")
if ($logContent) {
Write-Host "--- Log ---" if ($InfoBaseServer -and $InfoBaseRef) {
Write-Host $logContent $arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
Write-Host "--- End ---" } else {
} $arguments += "/F", "`"$InfoBasePath`""
} }
exit $exitCode if ($UserName) { $arguments += "/N`"$UserName`"" }
if ($Password) { $arguments += "/P`"$Password`"" }
} finally {
if (Test-Path $tempDir) { $arguments += "/DumpExternalDataProcessorOrReportToFiles", "`"$OutputDir`"", "`"$InputFile`""
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue $arguments += "-Format", $Format
}
} # --- Output ---
$outFile = Join-Path $tempDir "dump_log.txt"
$arguments += "/Out", "`"$outFile`""
$arguments += "/DisableStartupDialogs"
# --- Execute ---
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
$exitCode = $process.ExitCode
# --- Result ---
if ($exitCode -eq 0) {
Write-Host "Dump completed successfully to: $OutputDir" -ForegroundColor Green
} else {
Write-Host "Error dumping (code: $exitCode)" -ForegroundColor Red
}
if (Test-Path $outFile) {
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($logContent) {
Write-Host "--- Log ---"
Write-Host $logContent
Write-Host "--- End ---"
}
}
exit $exitCode
} finally {
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# epf-dump v1.1 — Dump external data processor or report (EPF/ERF) to XML sources # epf-dump v1.4 — Dump external data processor or report (EPF/ERF) to XML sources
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import atexit
import glob import glob
import json import json
import os import os
@@ -90,12 +91,20 @@ def main():
# --- Resolve V8Path --- # --- Resolve V8Path ---
v8path = resolve_v8path(args.V8Path) v8path = resolve_v8path(args.V8Path)
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
# --- Validate database connection --- # --- Validate database connection ---
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef): if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
print("Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef", file=sys.stderr) print("Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef", file=sys.stderr)
print("Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly.") print("Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly.")
sys.exit(1) sys.exit(1)
if engine == "ibcmd":
if not args.InfoBasePath:
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
sys.exit(1)
if args.Format == "Plain":
print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
sys.exit(1)
# --- Validate input file --- # --- Validate input file ---
if not os.path.isfile(args.InputFile): if not os.path.isfile(args.InputFile):
@@ -111,6 +120,28 @@ def main():
os.makedirs(temp_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True)
try: try:
if engine == "ibcmd":
# --- ibcmd branch: dump EPF/ERF via config export --file ---
arguments = ["infobase", "config", "export", f"--file={args.InputFile}", args.OutputDir, f"--db-path={args.InfoBasePath}"]
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
if args.UserName:
arguments.append(f"--user={args.UserName}")
if args.Password:
arguments.append(f"--password={args.Password}")
arguments.append(f"--data={ib_data}")
print(f"Running: ibcmd {' '.join(arguments)}")
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
if result.returncode == 0:
print(f"External data processor/report dumped successfully to: {args.OutputDir}")
else:
print(f"Error dumping external data processor/report (code: {result.returncode})", file=sys.stderr)
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)
# --- Build arguments --- # --- Build arguments ---
arguments = ["DESIGNER"] arguments = ["DESIGNER"]
@@ -1,41 +1,41 @@
--- ---
name: epf-init name: epf-init
description: Создать пустую внешнюю обработку 1С (scaffold XML-исходников). Используй когда нужно создать новую внешнюю обработку с нуля description: Создать пустую внешнюю обработку 1С (scaffold XML-исходников). Используй когда нужно создать новую внешнюю обработку с нуля
argument-hint: <Name> [Synonym] argument-hint: <Name> [Synonym]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Write - Write
- Edit - Edit
- Glob - Glob
- Grep - Grep
--- ---
# /epf-init — Создание новой обработки # /epf-init — Создание новой обработки
Генерирует минимальный набор XML-исходников для внешней обработки 1С: корневой файл метаданных и каталог обработки. Генерирует минимальный набор XML-исходников для внешней обработки 1С: корневой файл метаданных и каталог обработки.
## Usage ## Usage
``` ```
/epf-init <Name> [Synonym] [SrcDir] /epf-init <Name> [Synonym] [SrcDir]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|-----------|:------------:|--------------|-------------------------------------| |-----------|:------------:|--------------|-------------------------------------|
| Name | да | — | Имя обработки (латиница/кириллица) | | Name | да | — | Имя обработки (латиница/кириллица) |
| Synonym | нет | = Name | Синоним (отображаемое имя) | | Synonym | нет | = Name | Синоним (отображаемое имя) |
| SrcDir | нет | `src` | Каталог исходников относительно CWD | | SrcDir | нет | `src` | Каталог исходников относительно CWD |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] powershell.exe -NoProfile -File ".opencode/skills/epf-init/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"]
``` ```
## Дальнейшие шаги ## Дальнейшие шаги
- Добавить форму: `/form-add` - Добавить форму: `/form-add`
- Добавить макет: `/template-add` - Добавить макет: `/template-add`
- Добавить справку: `/help-add` - Добавить справку: `/help-add`
- Собрать EPF: `/epf-build` - Собрать EPF: `/epf-build`
@@ -1,90 +1,90 @@
# epf-init v1.1 — Init 1C external data processor scaffold # epf-init v1.1 — Init 1C external data processor scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$Name, [string]$Name,
[string]$Synonym = $Name, [string]$Synonym = $Name,
[string]$SrcDir = "src" [string]$SrcDir = "src"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8 [Console]::InputEncoding = [System.Text.Encoding]::UTF8
$uuid1 = [guid]::NewGuid().ToString() $uuid1 = [guid]::NewGuid().ToString()
$uuid2 = [guid]::NewGuid().ToString() $uuid2 = [guid]::NewGuid().ToString()
$uuid3 = [guid]::NewGuid().ToString() $uuid3 = [guid]::NewGuid().ToString()
$uuid4 = [guid]::NewGuid().ToString() $uuid4 = [guid]::NewGuid().ToString()
$xml = @" $xml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<ExternalDataProcessor uuid="$uuid1"> <ExternalDataProcessor uuid="$uuid1">
<InternalInfo> <InternalInfo>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>c3831ec8-d8d5-4f93-8a22-f9bfae07327f</xr:ClassId> <xr:ClassId>c3831ec8-d8d5-4f93-8a22-f9bfae07327f</xr:ClassId>
<xr:ObjectId>$uuid2</xr:ObjectId> <xr:ObjectId>$uuid2</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
<xr:GeneratedType name="ExternalDataProcessorObject.$Name" category="Object"> <xr:GeneratedType name="ExternalDataProcessorObject.$Name" category="Object">
<xr:TypeId>$uuid3</xr:TypeId> <xr:TypeId>$uuid3</xr:TypeId>
<xr:ValueId>$uuid4</xr:ValueId> <xr:ValueId>$uuid4</xr:ValueId>
</xr:GeneratedType> </xr:GeneratedType>
</InternalInfo> </InternalInfo>
<Properties> <Properties>
<Name>$Name</Name> <Name>$Name</Name>
<Synonym> <Synonym>
<v8:item> <v8:item>
<v8:lang>ru</v8:lang> <v8:lang>ru</v8:lang>
<v8:content>$Synonym</v8:content> <v8:content>$Synonym</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment/> <Comment/>
<DefaultForm/> <DefaultForm/>
<AuxiliaryForm/> <AuxiliaryForm/>
</Properties> </Properties>
<ChildObjects/> <ChildObjects/>
</ExternalDataProcessor> </ExternalDataProcessor>
</MetaDataObject> </MetaDataObject>
"@ "@
$rootFile = Join-Path $SrcDir "$Name.xml" $rootFile = Join-Path $SrcDir "$Name.xml"
$processorDir = Join-Path $SrcDir $Name $processorDir = Join-Path $SrcDir $Name
if (Test-Path $rootFile) { if (Test-Path $rootFile) {
Write-Error "Файл уже существует: $rootFile" Write-Error "Файл уже существует: $rootFile"
exit 1 exit 1
} }
if (-not (Test-Path $SrcDir)) { if (-not (Test-Path $SrcDir)) {
New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null
} }
$extDir = Join-Path $processorDir "Ext" $extDir = Join-Path $processorDir "Ext"
New-Item -ItemType Directory -Path $extDir -Force | Out-Null New-Item -ItemType Directory -Path $extDir -Force | Out-Null
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc) [System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
# --- Модуль объекта --- # --- Модуль объекта ---
$moduleBsl = @" $moduleBsl = @"
#Область ОписаниеПеременных #Область ОписаниеПеременных
#КонецОбласти #КонецОбласти
#Область ПрограммныйИнтерфейс #Область ПрограммныйИнтерфейс
#КонецОбласти #КонецОбласти
#Область СлужебныеПроцедурыИФункции #Область СлужебныеПроцедурыИФункции
#КонецОбласти #КонецОбласти
"@ "@
$modulePath = Join-Path $extDir "ObjectModule.bsl" $modulePath = Join-Path $extDir "ObjectModule.bsl"
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc) [System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
Write-Host "[OK] Создана обработка: $rootFile" Write-Host "[OK] Создана обработка: $rootFile"
Write-Host " Каталог: $processorDir" Write-Host " Каталог: $processorDir"
Write-Host " Модуль: $modulePath" Write-Host " Модуль: $modulePath"
@@ -1,30 +1,30 @@
--- ---
name: epf-validate name: epf-validate
description: Валидация внешней обработки 1С (EPF). Используй после создания или модификации обработки для проверки корректности description: Валидация внешней обработки 1С (EPF). Используй после создания или модификации обработки для проверки корректности
argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30] argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /epf-validate — валидация внешней обработки (EPF) # /epf-validate — валидация внешней обработки (EPF)
Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Также работает для внешних отчётов (ERF). Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Также работает для внешних отчётов (ERF).
## Параметры ## Параметры
| Параметр | Обяз. | Умолч. | Описание | | Параметр | Обяз. | Умолч. | Описание |
|------------|:-----:|---------|-------------------------------------------------| |------------|:-----:|---------|-------------------------------------------------|
| ObjectPath | да | — | Путь к корневому XML или каталогу обработки | | ObjectPath | да | — | Путь к корневому XML или каталогу обработки |
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) | | Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка" powershell.exe -NoProfile -File ".opencode/skills/epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка/МояОбработка.xml" powershell.exe -NoProfile -File ".opencode/skills/epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка/МояОбработка.xml"
``` ```
@@ -1,71 +1,71 @@
--- ---
name: erf-build name: erf-build
description: Собрать внешний отчёт 1С (ERF) из XML-исходников. Используй когда пользователь просит собрать, скомпилировать отчёт или получить ERF файл из исходников description: Собрать внешний отчёт 1С (ERF) из XML-исходников. Используй когда пользователь просит собрать, скомпилировать отчёт или получить ERF файл из исходников
argument-hint: <ReportName> argument-hint: <ReportName>
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- Grep - Grep
--- ---
# /erf-build — Сборка отчёта # /erf-build — Сборка отчёта
## Usage ## Usage
``` ```
/erf-build <ReportName> [SrcDir] [OutDir] /erf-build <ReportName> [SrcDir] [OutDir]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|--------------------------------------| |------------|:------------:|--------------|--------------------------------------|
| ReportName | да | — | Имя отчёта (имя корневого XML) | | ReportName | да | — | Имя отчёта (имя корневого XML) |
| SrcDir | нет | `src` | Каталог исходников | | SrcDir | нет | `src` | Каталог исходников |
| OutDir | нет | `build` | Каталог для результата | | OutDir | нет | `build` | Каталог для результата |
## Параметры подключения (опционально) ## Параметры подключения (опционально)
Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы. Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы.
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу: 1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
5. Если ветка не совпала — используй `default` 5. Если ветка не совпала — используй `default`
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для ERF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки. 6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для ERF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
Используй общий скрипт из epf-build: Используй общий скрипт из epf-build:
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников | | `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников |
| `-OutputFile <путь>` | да | Путь к выходному ERF-файлу | | `-OutputFile <путь>` | да | Путь к выходному ERF-файлу |
> `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных > `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных
## Примеры ## Примеры
```powershell ```powershell
# Сборка отчёта (файловая база) # Сборка отчёта (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf" powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf" powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
``` ```
@@ -1,71 +1,71 @@
--- ---
name: erf-dump name: erf-dump
description: Разобрать ERF-файл отчёта 1С в XML-исходники. Используй когда пользователь просит разобрать, декомпилировать отчёт, получить исходники из ERF файла description: Разобрать ERF-файл отчёта 1С в XML-исходники. Используй когда пользователь просит разобрать, декомпилировать отчёт, получить исходники из ERF файла
argument-hint: <ErfFile> argument-hint: <ErfFile>
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
- Grep - Grep
--- ---
# /erf-dump — Разборка отчёта # /erf-dump — Разборка отчёта
## Usage ## Usage
``` ```
/erf-dump <ErfFile> [OutDir] /erf-dump <ErfFile> [OutDir]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|----------|:------------:|--------------|-------------------------------------| |----------|:------------:|--------------|-------------------------------------|
| ErfFile | да | — | Путь к ERF-файлу | | ErfFile | да | — | Путь к ERF-файлу |
| OutDir | нет | `src` | Каталог для выгрузки исходников | | OutDir | нет | `src` | Каталог для выгрузки исходников |
## Параметры подключения (обязательно) ## Параметры подключения (обязательно)
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются. Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу: 1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую 2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json` 3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches` 4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
5. Если ветка не совпала — используй `default` 5. Если ветка не совпала — используй `default`
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`. 6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1` Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`. Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
## Команда ## Команда
Используй общий скрипт из epf-dump: Используй общий скрипт из epf-dump:
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" <параметры> powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
| Параметр | Обязательный | Описание | | Параметр | Обязательный | Описание |
|----------|:------------:|----------| |----------|:------------:|----------|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) | | `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
| `-InfoBasePath <путь>` | * | Файловая база | | `-InfoBasePath <путь>` | * | Файловая база |
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) | | `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
| `-InfoBaseRef <имя>` | * | Имя базы на сервере | | `-InfoBaseRef <имя>` | * | Имя базы на сервере |
| `-UserName <имя>` | нет | Имя пользователя | | `-UserName <имя>` | нет | Имя пользователя |
| `-Password <пароль>` | нет | Пароль | | `-Password <пароль>` | нет | Пароль |
| `-InputFile <путь>` | да | Путь к ERF-файлу | | `-InputFile <путь>` | да | Путь к ERF-файлу |
| `-OutputDir <путь>` | да | Каталог для выгрузки исходников | | `-OutputDir <путь>` | да | Каталог для выгрузки исходников |
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` | | `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
> `*` — обязательно хотя бы одно подключение. Без базы скрипт завершится с ошибкой (dump в пустой базе безвозвратно теряет ссылочные типы) > `*` — обязательно хотя бы одно подключение. Без базы скрипт завершится с ошибкой (dump в пустой базе безвозвратно теряет ссылочные типы)
## Примеры ## Примеры
```powershell ```powershell
# Разборка отчёта (файловая база) # Разборка отчёта (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src" powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src" powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
``` ```
@@ -1,42 +1,42 @@
--- ---
name: erf-init name: erf-init
description: Создать пустой внешний отчёт 1С (scaffold XML-исходников). Используй когда нужно создать новый внешний отчёт с нуля description: Создать пустой внешний отчёт 1С (scaffold XML-исходников). Используй когда нужно создать новый внешний отчёт с нуля
argument-hint: <Name> [Synonym] [--with-skd] argument-hint: <Name> [Synonym] [--with-skd]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Write - Write
- Edit - Edit
- Glob - Glob
- Grep - Grep
--- ---
# /erf-init — Создание нового отчёта # /erf-init — Создание нового отчёта
Генерирует минимальный набор XML-исходников для внешнего отчёта 1С: корневой файл метаданных и каталог отчёта. Генерирует минимальный набор XML-исходников для внешнего отчёта 1С: корневой файл метаданных и каталог отчёта.
## Usage ## Usage
``` ```
/erf-init <Name> [Synonym] [SrcDir] [--with-skd] /erf-init <Name> [Synonym] [SrcDir] [--with-skd]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|-----------|:------------:|--------------|---------------------------------------| |-----------|:------------:|--------------|---------------------------------------|
| Name | да | — | Имя отчёта (латиница/кириллица) | | Name | да | — | Имя отчёта (латиница/кириллица) |
| Synonym | нет | = Name | Синоним (отображаемое имя) | | Synonym | нет | = Name | Синоним (отображаемое имя) |
| SrcDir | нет | `src` | Каталог исходников относительно CWD | | SrcDir | нет | `src` | Каталог исходников относительно CWD |
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema | | --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD] powershell.exe -NoProfile -File ".opencode/skills/erf-init/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD]
``` ```
## Дальнейшие шаги ## Дальнейшие шаги
- Добавить форму: `/form-add` - Добавить форму: `/form-add`
- Добавить макет: `/template-add` - Добавить макет: `/template-add`
- Добавить справку: `/help-add` - Добавить справку: `/help-add`
- Собрать ERF: `/erf-build` - Собрать ERF: `/erf-build`
@@ -1,180 +1,180 @@
# erf-init v1.1 — Init 1C external report scaffold # erf-init v1.1 — Init 1C external report scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$Name, [string]$Name,
[string]$Synonym = $Name, [string]$Synonym = $Name,
[string]$SrcDir = "src", [string]$SrcDir = "src",
[switch]$WithSKD [switch]$WithSKD
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8 [Console]::InputEncoding = [System.Text.Encoding]::UTF8
$uuid1 = [guid]::NewGuid().ToString() $uuid1 = [guid]::NewGuid().ToString()
$uuid2 = [guid]::NewGuid().ToString() $uuid2 = [guid]::NewGuid().ToString()
$uuid3 = [guid]::NewGuid().ToString() $uuid3 = [guid]::NewGuid().ToString()
$uuid4 = [guid]::NewGuid().ToString() $uuid4 = [guid]::NewGuid().ToString()
# --- Формируем Properties --- # --- Формируем Properties ---
$mainDCSValue = "" $mainDCSValue = ""
$childObjectsContent = "" $childObjectsContent = ""
if ($WithSKD) { if ($WithSKD) {
$mainDCSValue = "ExternalReport.$Name.Template.ОсновнаяСхемаКомпоновкиДанных" $mainDCSValue = "ExternalReport.$Name.Template.ОсновнаяСхемаКомпоновкиДанных"
$childObjectsContent = @" $childObjectsContent = @"
<Template>ОсновнаяСхемаКомпоновкиДанных</Template> <Template>ОсновнаяСхемаКомпоновкиДанных</Template>
"@ "@
} }
$mainDCSElement = if ($mainDCSValue) { $mainDCSElement = if ($mainDCSValue) {
"<MainDataCompositionSchema>$mainDCSValue</MainDataCompositionSchema>" "<MainDataCompositionSchema>$mainDCSValue</MainDataCompositionSchema>"
} else { } else {
"<MainDataCompositionSchema/>" "<MainDataCompositionSchema/>"
} }
$childObjectsXml = if ($childObjectsContent) { $childObjectsXml = if ($childObjectsContent) {
"<ChildObjects>$childObjectsContent</ChildObjects>" "<ChildObjects>$childObjectsContent</ChildObjects>"
} else { } else {
"<ChildObjects/>" "<ChildObjects/>"
} }
$xml = @" $xml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<ExternalReport uuid="$uuid1"> <ExternalReport uuid="$uuid1">
<InternalInfo> <InternalInfo>
<xr:ContainedObject> <xr:ContainedObject>
<xr:ClassId>e41aff26-25cf-4bb6-b6c1-3f478a75f374</xr:ClassId> <xr:ClassId>e41aff26-25cf-4bb6-b6c1-3f478a75f374</xr:ClassId>
<xr:ObjectId>$uuid2</xr:ObjectId> <xr:ObjectId>$uuid2</xr:ObjectId>
</xr:ContainedObject> </xr:ContainedObject>
<xr:GeneratedType name="ExternalReportObject.$Name" category="Object"> <xr:GeneratedType name="ExternalReportObject.$Name" category="Object">
<xr:TypeId>$uuid3</xr:TypeId> <xr:TypeId>$uuid3</xr:TypeId>
<xr:ValueId>$uuid4</xr:ValueId> <xr:ValueId>$uuid4</xr:ValueId>
</xr:GeneratedType> </xr:GeneratedType>
</InternalInfo> </InternalInfo>
<Properties> <Properties>
<Name>$Name</Name> <Name>$Name</Name>
<Synonym> <Synonym>
<v8:item> <v8:item>
<v8:lang>ru</v8:lang> <v8:lang>ru</v8:lang>
<v8:content>$Synonym</v8:content> <v8:content>$Synonym</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment/> <Comment/>
<DefaultForm/> <DefaultForm/>
<AuxiliaryForm/> <AuxiliaryForm/>
$mainDCSElement $mainDCSElement
<DefaultSettingsForm/> <DefaultSettingsForm/>
<AuxiliarySettingsForm/> <AuxiliarySettingsForm/>
<DefaultVariantForm/> <DefaultVariantForm/>
<VariantsStorage/> <VariantsStorage/>
<SettingsStorage/> <SettingsStorage/>
</Properties> </Properties>
$childObjectsXml $childObjectsXml
</ExternalReport> </ExternalReport>
</MetaDataObject> </MetaDataObject>
"@ "@
$rootFile = Join-Path $SrcDir "$Name.xml" $rootFile = Join-Path $SrcDir "$Name.xml"
$reportDir = Join-Path $SrcDir $Name $reportDir = Join-Path $SrcDir $Name
if (Test-Path $rootFile) { if (Test-Path $rootFile) {
Write-Error "Файл уже существует: $rootFile" Write-Error "Файл уже существует: $rootFile"
exit 1 exit 1
} }
if (-not (Test-Path $SrcDir)) { if (-not (Test-Path $SrcDir)) {
New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null
} }
$extDir = Join-Path $reportDir "Ext" $extDir = Join-Path $reportDir "Ext"
New-Item -ItemType Directory -Path $extDir -Force | Out-Null New-Item -ItemType Directory -Path $extDir -Force | Out-Null
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc) [System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
# --- Модуль объекта --- # --- Модуль объекта ---
$moduleBsl = @" $moduleBsl = @"
#Область ОписаниеПеременных #Область ОписаниеПеременных
#КонецОбласти #КонецОбласти
#Область ПрограммныйИнтерфейс #Область ПрограммныйИнтерфейс
#КонецОбласти #КонецОбласти
#Область СлужебныеПроцедурыИФункции #Область СлужебныеПроцедурыИФункции
#КонецОбласти #КонецОбласти
"@ "@
$modulePath = Join-Path $extDir "ObjectModule.bsl" $modulePath = Join-Path $extDir "ObjectModule.bsl"
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc) [System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
Write-Host "[OK] Создан отчёт: $rootFile" Write-Host "[OK] Создан отчёт: $rootFile"
Write-Host " Каталог: $reportDir" Write-Host " Каталог: $reportDir"
Write-Host " Модуль: $modulePath" Write-Host " Модуль: $modulePath"
# --- СКД-макет (если --WithSKD) --- # --- СКД-макет (если --WithSKD) ---
if ($WithSKD) { if ($WithSKD) {
$templatesDir = Join-Path $reportDir "Templates" $templatesDir = Join-Path $reportDir "Templates"
$skdName = "ОсновнаяСхемаКомпоновкиДанных" $skdName = "ОсновнаяСхемаКомпоновкиДанных"
$skdMetaPath = Join-Path $templatesDir "$skdName.xml" $skdMetaPath = Join-Path $templatesDir "$skdName.xml"
$skdExtDir = Join-Path (Join-Path $templatesDir $skdName) "Ext" $skdExtDir = Join-Path (Join-Path $templatesDir $skdName) "Ext"
New-Item -ItemType Directory -Path $skdExtDir -Force | Out-Null New-Item -ItemType Directory -Path $skdExtDir -Force | Out-Null
$skdUuid = [guid]::NewGuid().ToString() $skdUuid = [guid]::NewGuid().ToString()
$skdMetaXml = @" $skdMetaXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"> <MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Template uuid="$skdUuid"> <Template uuid="$skdUuid">
<Properties> <Properties>
<Name>$skdName</Name> <Name>$skdName</Name>
<Synonym> <Synonym>
<v8:item> <v8:item>
<v8:lang>ru</v8:lang> <v8:lang>ru</v8:lang>
<v8:content>Основная схема компоновки данных</v8:content> <v8:content>Основная схема компоновки данных</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment/> <Comment/>
<TemplateType>DataCompositionSchema</TemplateType> <TemplateType>DataCompositionSchema</TemplateType>
</Properties> </Properties>
</Template> </Template>
</MetaDataObject> </MetaDataObject>
"@ "@
[System.IO.File]::WriteAllText($skdMetaPath, $skdMetaXml, $enc) [System.IO.File]::WriteAllText($skdMetaPath, $skdMetaXml, $enc)
$skdContent = @" $skdContent = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" <DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common" xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource> <dataSource>
<name>ИсточникДанных1</name> <name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType> <dataSourceType>Local</dataSourceType>
</dataSource> </dataSource>
</DataCompositionSchema> </DataCompositionSchema>
"@ "@
$skdFilePath = Join-Path $skdExtDir "Template.xml" $skdFilePath = Join-Path $skdExtDir "Template.xml"
[System.IO.File]::WriteAllText($skdFilePath, $skdContent, $enc) [System.IO.File]::WriteAllText($skdFilePath, $skdContent, $enc)
Write-Host " СКД: $skdMetaPath" Write-Host " СКД: $skdMetaPath"
Write-Host " Тело: $skdFilePath" Write-Host " Тело: $skdFilePath"
} }
@@ -1,32 +1,32 @@
--- ---
name: erf-validate name: erf-validate
description: Валидация внешнего отчёта 1С (ERF). Используй после создания или модификации отчёта для проверки корректности description: Валидация внешнего отчёта 1С (ERF). Используй после создания или модификации отчёта для проверки корректности
argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30] argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Glob - Glob
--- ---
# /erf-validate — валидация внешнего отчёта (ERF) # /erf-validate — валидация внешнего отчёта (ERF)
Проверяет структурную корректность XML-исходников внешнего отчёта: корневую структуру, InternalInfo, свойства (включая MainDataCompositionSchema), ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Проверяет структурную корректность XML-исходников внешнего отчёта: корневую структуру, InternalInfo, свойства (включая MainDataCompositionSchema), ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов.
Использует тот же скрипт, что и `/epf-validate` — автоопределение по типу элемента (ExternalReport). Использует тот же скрипт, что и `/epf-validate` — автоопределение по типу элемента (ExternalReport).
## Параметры ## Параметры
| Параметр | Обяз. | Умолч. | Описание | | Параметр | Обяз. | Умолч. | Описание |
|------------|:-----:|---------|-------------------------------------------------| |------------|:-----:|---------|-------------------------------------------------|
| ObjectPath | да | — | Путь к корневому XML или каталогу отчёта | | ObjectPath | да | — | Путь к корневому XML или каталогу отчёта |
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) | | Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | | OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МойОтчёт" powershell.exe -NoProfile -File ".opencode/skills/epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МойОтчёт"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МойОтчёт/МойОтчёт.xml" powershell.exe -NoProfile -File ".opencode/skills/epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МойОтчёт/МойОтчёт.xml"
``` ```
@@ -1,71 +1,71 @@
--- ---
name: form-add name: form-add
description: Добавить пустую управляемую форму к объекту 1С. Используй когда нужно создать у объекта новую форму description: Добавить пустую управляемую форму к объекту 1С. Используй когда нужно создать у объекта новую форму
argument-hint: <ObjectPath> <FormName> [Purpose] [--set-default] argument-hint: <ObjectPath> <FormName> [Purpose] [--set-default]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
- Write - Write
- Edit - Edit
- Glob - Glob
- Grep - Grep
--- ---
# /form-add — Добавление формы к объекту конфигурации # /form-add — Добавление формы к объекту конфигурации
Создаёт управляемую форму (metadata XML + Form.xml + Module.bsl) и регистрирует её в корневом XML объекта конфигурации (Document, Catalog, InformationRegister и др.). Создаёт управляемую форму (metadata XML + Form.xml + Module.bsl) и регистрирует её в корневом XML объекта конфигурации (Document, Catalog, InformationRegister и др.).
## Usage ## Usage
``` ```
/form-add <ObjectPath> <FormName> [Purpose] [Synonym] [--set-default] /form-add <ObjectPath> <FormName> [Purpose] [Synonym] [--set-default]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|-------------|:------------:|--------------|----------------------------------------------| |-------------|:------------:|--------------|----------------------------------------------|
| ObjectPath | да | — | Путь к XML-файлу объекта (Documents/Док.xml) | | ObjectPath | да | — | Путь к XML-файлу объекта (Documents/Док.xml) |
| FormName | да | — | Имя формы (ФормаДокумента) | | FormName | да | — | Имя формы (ФормаДокумента) |
| Purpose | нет | Object | Назначение: Object, List, Choice, Record | | Purpose | нет | Object | Назначение: Object, List, Choice, Record |
| Synonym | нет | = FormName | Синоним формы | | Synonym | нет | = FormName | Синоним формы |
| --set-default | нет | авто | Установить как форму по умолчанию | | --set-default | нет | авто | Установить как форму по умолчанию |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-add.ps1" -ObjectPath "<ObjectPath>" -FormName "<FormName>" [-Purpose "<Purpose>"] [-Synonym "<Synonym>"] [-SetDefault] powershell.exe -NoProfile -File ".opencode/skills/form-add/scripts/form-add.ps1" -ObjectPath "<ObjectPath>" -FormName "<FormName>" [-Purpose "<Purpose>"] [-Synonym "<Synonym>"] [-SetDefault]
``` ```
## Purpose — назначение формы ## Purpose — назначение формы
| Purpose | Допустимые типы объектов | Основной реквизит | DefaultForm-свойство | | Purpose | Допустимые типы объектов | Основной реквизит | DefaultForm-свойство |
|---------|-------------------------|-------------------|---------------------| |---------|-------------------------|-------------------|---------------------|
| Object | Document, Catalog, DataProcessor, Report, ExternalDataProcessor, ExternalReport, ChartOf*, ExchangePlan, BusinessProcess, Task | Объект (тип: *Object.Имя) | DefaultObjectForm (DefaultForm для DataProcessor/Report/ExternalDataProcessor/ExternalReport) | | Object | Document, Catalog, DataProcessor, Report, ExternalDataProcessor, ExternalReport, ChartOf*, ExchangePlan, BusinessProcess, Task | Объект (тип: *Object.Имя) | DefaultObjectForm (DefaultForm для DataProcessor/Report/ExternalDataProcessor/ExternalReport) |
| List | Все кроме DataProcessor | Список (DynamicList) | DefaultListForm | | List | Все кроме DataProcessor | Список (DynamicList) | DefaultListForm |
| Choice | Document, Catalog, ChartOf*, ExchangePlan, BusinessProcess, Task | Список (DynamicList) | DefaultChoiceForm | | Choice | Document, Catalog, ChartOf*, ExchangePlan, BusinessProcess, Task | Список (DynamicList) | DefaultChoiceForm |
| Record | InformationRegister | Запись (InformationRegisterRecordManager) | DefaultRecordForm | | Record | InformationRegister | Запись (InformationRegisterRecordManager) | DefaultRecordForm |
## Примеры ## Примеры
``` ```
# Форма документа # Форма документа
/form-add Documents/АвансовыйОтчет.xml ФормаДокумента --purpose Object /form-add Documents/АвансовыйОтчет.xml ФормаДокумента --purpose Object
# Форма списка каталога # Форма списка каталога
/form-add Catalogs/Контрагенты.xml ФормаСписка --purpose List /form-add Catalogs/Контрагенты.xml ФормаСписка --purpose List
# Форма записи регистра сведений # Форма записи регистра сведений
/form-add InformationRegisters/КурсыВалют.xml ФормаЗаписи --purpose Record /form-add InformationRegisters/КурсыВалют.xml ФормаЗаписи --purpose Record
# Форма выбора с синонимом # Форма выбора с синонимом
/form-add Catalogs/Номенклатура.xml ФормаВыбора --purpose Choice --synonym "Выбор номенклатуры" /form-add Catalogs/Номенклатура.xml ФормаВыбора --purpose Choice --synonym "Выбор номенклатуры"
# Установить как форму по умолчанию # Установить как форму по умолчанию
/form-add Documents/Заказ.xml ФормаДокументаНовая --purpose Object --set-default /form-add Documents/Заказ.xml ФормаДокументаНовая --purpose Object --set-default
``` ```
## Workflow ## Workflow
1. `/form-add` — создать каркас формы 1. `/form-add` — создать каркас формы
2. `/form-compile` или `/form-edit` — наполнить Form.xml элементами 2. `/form-compile` или `/form-edit` — наполнить Form.xml элементами
3. `/form-validate` — проверить корректность 3. `/form-validate` — проверить корректность
4. `/form-info` — проанализировать результат 4. `/form-info` — проанализировать результат
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More