feat(cfe): add configuration extension skills

Add 5 new skills for 1C configuration extensions (CFE):
- cfe-init: create extension scaffold with Configuration.xml, Languages, Roles
- cfe-borrow: borrow objects from configuration with ObjectBelonging=Adopted
- cfe-patch-method: generate &Перед, &После, &ИзменениеИКонтроль decorators
- cfe-validate: validate extension structure with 9 checks
- cfe-diff: analyze extension and check transfer status

Remove ConfigDumpInfo.xml from cf-init scaffold as it's not required
for 1C configuration source files. Add cfe-guide documentation.
This commit is contained in:
Nick Shirokov
2026-02-15 16:13:55 +03:00
parent aae69664c5
commit b8e3107d14
16 changed files with 2346 additions and 14 deletions
+2 -3
View File
@@ -1,6 +1,6 @@
---
name: cf-init
description: Создать пустую конфигурацию 1С (scaffold XML-исходников) — Configuration.xml, ConfigDumpInfo.xml, Languages/
description: Создать пустую конфигурацию 1С (scaffold XML-исходников) — Configuration.xml, Languages/
argument-hint: <Name> [-Synonym <name>] [-OutputDir src]
allowed-tools:
- Bash
@@ -10,7 +10,7 @@ allowed-tools:
# /cf-init — Создание пустой конфигурации 1С
Создаёт scaffold исходников пустой конфигурации 1С: `Configuration.xml`, `ConfigDumpInfo.xml`, `Languages/Русский.xml`.
Создаёт scaffold исходников пустой конфигурации 1С: `Configuration.xml`, `Languages/Русский.xml`.
## Параметры и команда
@@ -32,7 +32,6 @@ powershell.exe -NoProfile -File .claude\skills\cf-init\scripts\cf-init.ps1 -Name
```
<OutputDir>/
├── Configuration.xml # Корневой файл — все свойства
├── ConfigDumpInfo.xml # Служебный файл (минимальный)
└── Languages/
└── Русский.xml # Язык по умолчанию
```
@@ -172,14 +172,6 @@ $cfgXml = @"
</MetaDataObject>
"@
# --- ConfigDumpInfo.xml ---
$dumpInfoXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<ConfigDumpInfo xmlns="http://v8.1c.ru/8.3/xcf/dumpinfo" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" format="Hierarchical" version="2.17">
<ConfigVersions/>
</ConfigDumpInfo>
"@
# --- Languages/Русский.xml ---
$langXml = @"
<?xml version="1.0" encoding="UTF-8"?>
@@ -213,8 +205,6 @@ if (-not (Test-Path $langDir)) {
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
$dumpInfoFile = Join-Path $OutputDir "ConfigDumpInfo.xml"
[System.IO.File]::WriteAllText($dumpInfoFile, $dumpInfoXml, $enc)
$langFile = Join-Path $langDir "Русский.xml"
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
@@ -222,5 +212,4 @@ $langFile = Join-Path $langDir "Русский.xml"
Write-Host "[OK] Создана конфигурация: $Name"
Write-Host " Каталог: $OutputDir"
Write-Host " Configuration.xml: $cfgFile"
Write-Host " ConfigDumpInfo.xml: $dumpInfoFile"
Write-Host " Languages: $langFile"
+15
View File
@@ -0,0 +1,15 @@
---
name: cfe-borrow
description: Заимствование объектов из конфигурации 1С в расширение (CFE) — справочники, документы, общие модули, перечисления
argument-hint: -ExtensionPath <path> -ConfigPath <path> -Object "Catalog.Контрагенты"
allowed-tools:
- Bash
- Read
- Glob
---
Заимствует объекты из конфигурации в расширение. Создаёт XML-файлы с ObjectBelonging=Adopted.
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-borrow\scripts\cfe-borrow.ps1 -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
```
+48
View File
@@ -0,0 +1,48 @@
# /cfe-borrow — Заимствование объектов из конфигурации в расширение
Заимствует объекты из основной конфигурации в расширение. Создаёт минимальные XML-файлы с `ObjectBelonging=Adopted` и `ExtendedConfigurationObject`, добавляет запись в ChildObjects расширения.
## Параметры
| Параметр | Описание |
|----------|----------|
| `ExtensionPath` | Путь к каталогу расширения (обязат.) |
| `ConfigPath` | Путь к конфигурации-источнику (обязат.) |
| `Object` | Что заимствовать, batch через `;;` (обязат.) |
## Формат -Object
- `Catalog.Контрагенты` — справочник
- `CommonModule.РаботаСФайлами` — общий модуль
- `Enum.ВидыОплат` — перечисление
- `Document.РеализацияТоваров` — документ
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — batch
## Алгоритм
1. Загружает Configuration.xml расширения, определяет NamePrefix
2. Загружает XML объекта из конфигурации-источника
3. Создаёт XML заимствованного объекта:
- `ObjectBelonging: Adopted`
- `ExtendedConfigurationObject: <uuid из конфигурации>`
- `Name: <имя из конфигурации>`
- `InternalInfo` с `GeneratedType` (копируется из конфигурации)
4. Добавляет в `ChildObjects` расширения в каноническую позицию
5. Создаёт каталог и XML-файл
## Примеры
```powershell
# Заимствовать справочник
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
# Несколько объектов
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодульСервер ;; Enum.ВидыОплат"
```
## Верификация
```
/cfe-borrow -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
/cfe-validate src — проверить результат
```
@@ -0,0 +1,587 @@
# cfe-borrow v1.0 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$ExtensionPath,
[Parameter(Mandatory)][string]$ConfigPath,
[Parameter(Mandatory)][string]$Object
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
function Info([string]$msg) { Write-Host "[INFO] $msg" }
function Warn([string]$msg) { Write-Host "[WARN] $msg" }
# --- 1. Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (Test-Path $ExtensionPath -PathType Container) {
$candidate = Join-Path $ExtensionPath "Configuration.xml"
if (Test-Path $candidate) { $ExtensionPath = $candidate }
else { Write-Error "No Configuration.xml in extension directory: $ExtensionPath"; exit 1 }
}
if (-not (Test-Path $ExtensionPath)) { Write-Error "Extension file not found: $ExtensionPath"; exit 1 }
$extResolvedPath = (Resolve-Path $ExtensionPath).Path
$extDir = Split-Path $extResolvedPath -Parent
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
}
if (Test-Path $ConfigPath -PathType Container) {
$candidate = Join-Path $ConfigPath "Configuration.xml"
if (Test-Path $candidate) { $ConfigPath = $candidate }
else { Write-Error "No Configuration.xml in config directory: $ConfigPath"; exit 1 }
}
if (-not (Test-Path $ConfigPath)) { Write-Error "Config file not found: $ConfigPath"; exit 1 }
$cfgResolvedPath = (Resolve-Path $ConfigPath).Path
$cfgDir = Split-Path $cfgResolvedPath -Parent
# --- 2. Load extension Configuration.xml ---
$script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($extResolvedPath)
$script:mdNs = "http://v8.1c.ru/8.3/MDClasses"
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
$script:xsiNs = "http://www.w3.org/2001/XMLSchema-instance"
$script:v8Ns = "http://v8.1c.ru/8.1/data/core"
$root = $script:xmlDoc.DocumentElement
$script:cfgEl = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Configuration") {
$script:cfgEl = $child; break
}
}
if (-not $script:cfgEl) { Write-Error "No <Configuration> element found in extension"; exit 1 }
$script:propsEl = $null
$script:childObjsEl = $null
foreach ($child in $script:cfgEl.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq "Properties") { $script:propsEl = $child }
if ($child.LocalName -eq "ChildObjects") { $script:childObjsEl = $child }
}
if (-not $script:propsEl) { Write-Error "No <Properties> element found in extension"; exit 1 }
if (-not $script:childObjsEl) { Write-Error "No <ChildObjects> element found in extension"; exit 1 }
# --- 3. Extract NamePrefix ---
$script:namePrefix = ""
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "NamePrefix") {
$script:namePrefix = $child.InnerText.Trim(); break
}
}
Info "Extension NamePrefix: $($script:namePrefix)"
# --- 4. Type mappings ---
$childTypeDirMap = @{
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
"CommonModule"="CommonModules"; "CommonPicture"="CommonPictures"
"CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates"
"ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors"
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants"
"FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes"
"FunctionalOptionsParameter"="FunctionalOptionsParameters"
"CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals"
"SessionParameter"="SessionParameters"; "StyleItem"="StyleItems"
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
"XDTOPackage"="XDTOPackages"; "WebService"="WebServices"
"HTTPService"="HTTPServices"; "WSReference"="WSReferences"
"CommonAttribute"="CommonAttributes"; "Style"="Styles"
}
# --- 5. Canonical type order (44 types) ---
$script:typeOrder = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
# --- 6. GeneratedType patterns per type ---
$script:generatedTypes = @{
"Catalog" = @(
@{ prefix = "CatalogObject"; category = "Object" }
@{ prefix = "CatalogRef"; category = "Ref" }
@{ prefix = "CatalogSelection"; category = "Selection" }
@{ prefix = "CatalogList"; category = "List" }
@{ prefix = "CatalogManager"; category = "Manager" }
)
"Document" = @(
@{ prefix = "DocumentObject"; category = "Object" }
@{ prefix = "DocumentRef"; category = "Ref" }
@{ prefix = "DocumentSelection"; category = "Selection" }
@{ prefix = "DocumentList"; category = "List" }
@{ prefix = "DocumentManager"; category = "Manager" }
)
"Enum" = @(
@{ prefix = "EnumRef"; category = "Ref" }
@{ prefix = "EnumManager"; category = "Manager" }
@{ prefix = "EnumList"; category = "List" }
)
"Constant" = @(
@{ prefix = "ConstantManager"; category = "Manager" }
@{ prefix = "ConstantValueManager"; category = "ValueManager" }
@{ prefix = "ConstantValueKey"; category = "ValueKey" }
)
"InformationRegister" = @(
@{ prefix = "InformationRegisterRecord"; category = "Record" }
@{ prefix = "InformationRegisterManager"; category = "Manager" }
@{ prefix = "InformationRegisterSelection"; category = "Selection" }
@{ prefix = "InformationRegisterList"; category = "List" }
@{ prefix = "InformationRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "InformationRegisterRecordKey"; category = "RecordKey" }
@{ prefix = "InformationRegisterRecordManager"; category = "RecordManager" }
)
"AccumulationRegister" = @(
@{ prefix = "AccumulationRegisterRecord"; category = "Record" }
@{ prefix = "AccumulationRegisterManager"; category = "Manager" }
@{ prefix = "AccumulationRegisterSelection"; category = "Selection" }
@{ prefix = "AccumulationRegisterList"; category = "List" }
@{ prefix = "AccumulationRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "AccumulationRegisterRecordKey"; category = "RecordKey" }
)
"AccountingRegister" = @(
@{ prefix = "AccountingRegisterRecord"; category = "Record" }
@{ prefix = "AccountingRegisterManager"; category = "Manager" }
@{ prefix = "AccountingRegisterSelection"; category = "Selection" }
@{ prefix = "AccountingRegisterList"; category = "List" }
@{ prefix = "AccountingRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "AccountingRegisterRecordKey"; category = "RecordKey" }
)
"CalculationRegister" = @(
@{ prefix = "CalculationRegisterRecord"; category = "Record" }
@{ prefix = "CalculationRegisterManager"; category = "Manager" }
@{ prefix = "CalculationRegisterSelection"; category = "Selection" }
@{ prefix = "CalculationRegisterList"; category = "List" }
@{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" }
)
"ChartOfAccounts" = @(
@{ prefix = "ChartOfAccountsObject"; category = "Object" }
@{ prefix = "ChartOfAccountsRef"; category = "Ref" }
@{ prefix = "ChartOfAccountsSelection"; category = "Selection" }
@{ prefix = "ChartOfAccountsList"; category = "List" }
@{ prefix = "ChartOfAccountsManager"; category = "Manager" }
)
"ChartOfCharacteristicTypes" = @(
@{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" }
@{ prefix = "ChartOfCharacteristicTypesRef"; category = "Ref" }
@{ prefix = "ChartOfCharacteristicTypesSelection"; category = "Selection" }
@{ prefix = "ChartOfCharacteristicTypesList"; category = "List" }
@{ prefix = "ChartOfCharacteristicTypesManager"; category = "Manager" }
)
"ChartOfCalculationTypes" = @(
@{ prefix = "ChartOfCalculationTypesObject"; category = "Object" }
@{ prefix = "ChartOfCalculationTypesRef"; category = "Ref" }
@{ prefix = "ChartOfCalculationTypesSelection"; category = "Selection" }
@{ prefix = "ChartOfCalculationTypesList"; category = "List" }
@{ prefix = "ChartOfCalculationTypesManager"; category = "Manager" }
@{ prefix = "DisplacingCalculationTypes"; category = "DisplacingCalculationTypes" }
@{ prefix = "BaseCalculationTypes"; category = "BaseCalculationTypes" }
@{ prefix = "LeadingCalculationTypes"; category = "LeadingCalculationTypes" }
)
"BusinessProcess" = @(
@{ prefix = "BusinessProcessObject"; category = "Object" }
@{ prefix = "BusinessProcessRef"; category = "Ref" }
@{ prefix = "BusinessProcessSelection"; category = "Selection" }
@{ prefix = "BusinessProcessList"; category = "List" }
@{ prefix = "BusinessProcessManager"; category = "Manager" }
)
"Task" = @(
@{ prefix = "TaskObject"; category = "Object" }
@{ prefix = "TaskRef"; category = "Ref" }
@{ prefix = "TaskSelection"; category = "Selection" }
@{ prefix = "TaskList"; category = "List" }
@{ prefix = "TaskManager"; category = "Manager" }
)
"ExchangePlan" = @(
@{ prefix = "ExchangePlanObject"; category = "Object" }
@{ prefix = "ExchangePlanRef"; category = "Ref" }
@{ prefix = "ExchangePlanSelection"; category = "Selection" }
@{ prefix = "ExchangePlanList"; category = "List" }
@{ prefix = "ExchangePlanManager"; category = "Manager" }
)
"DocumentJournal" = @(
@{ prefix = "DocumentJournalSelection"; category = "Selection" }
@{ prefix = "DocumentJournalList"; category = "List" }
@{ prefix = "DocumentJournalManager"; category = "Manager" }
)
"Report" = @(
@{ prefix = "ReportObject"; category = "Object" }
)
"DataProcessor" = @(
@{ prefix = "DataProcessorObject"; category = "Object" }
)
}
# Types that need ChildObjects element
$typesWithChildObjects = @(
"Catalog","Document","ExchangePlan","ChartOfAccounts",
"ChartOfCharacteristicTypes","ChartOfCalculationTypes",
"BusinessProcess","Task","Enum",
"InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister"
)
# CommonModule properties to copy from source
$commonModuleProps = @("Global","ClientManagedApplication","Server","ExternalConnection","ClientOrdinaryApplication","ServerCall")
# --- 7. XML manipulation helpers (from cf-edit) ---
function Get-ChildIndent($container) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
if ($child.Value -match '^\r?\n(\t+)$') { return $Matches[1] }
if ($child.Value -match '^\r?\n(\t+)') { return $Matches[1] }
}
}
$depth = 0; $current = $container
while ($current -and $current -ne $script:xmlDoc.DocumentElement) { $depth++; $current = $current.ParentNode }
return "`t" * ($depth + 1)
}
function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
$ws = $script:xmlDoc.CreateWhitespace("`r`n$childIndent")
if ($refNode) {
$container.InsertBefore($ws, $refNode) | Out-Null
$container.InsertBefore($newNode, $ws) | Out-Null
} else {
$trailing = $container.LastChild
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
$container.InsertBefore($ws, $trailing) | Out-Null
$container.InsertBefore($newNode, $trailing) | Out-Null
} else {
$container.AppendChild($ws) | Out-Null
$container.AppendChild($newNode) | Out-Null
$parentIndent = if ($childIndent.Length -gt 1) { $childIndent.Substring(0, $childIndent.Length - 1) } else { "" }
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
}
function Expand-SelfClosingElement($container, $parentIndent) {
if (-not $container.HasChildNodes -or $container.IsEmpty) {
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
# --- 8. Namespaces declaration for object XML ---
$script:xmlnsDecl = '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"'
# --- 9. Parse -Object into items ---
$items = @()
foreach ($part in $Object.Split(";;")) {
$trimmed = $part.Trim()
if ($trimmed) { $items += $trimmed }
}
if ($items.Count -eq 0) {
Write-Error "No objects specified in -Object"
exit 1
}
# --- 10. Helper: read source object XML ---
function Read-SourceObject {
param([string]$typeName, [string]$objName)
$dirName = $childTypeDirMap[$typeName]
if (-not $dirName) {
Write-Error "Unknown type '$typeName'"
exit 1
}
$srcFile = Join-Path (Join-Path $cfgDir $dirName) "${objName}.xml"
if (-not (Test-Path $srcFile)) {
Write-Error "Source object not found: $srcFile"
exit 1
}
$srcDoc = New-Object System.Xml.XmlDocument
$srcDoc.PreserveWhitespace = $false
$srcDoc.Load($srcFile)
$srcNs = New-Object System.Xml.XmlNamespaceManager($srcDoc.NameTable)
$srcNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$srcNs.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
# Find the type element (e.g. <Catalog uuid="...">)
$srcRoot = $srcDoc.DocumentElement
$srcEl = $null
foreach ($c in $srcRoot.ChildNodes) {
if ($c.NodeType -eq 'Element') { $srcEl = $c; break }
}
if (-not $srcEl) {
Write-Error "No metadata element found in ${dirName}/${objName}.xml"
exit 1
}
# Extract uuid
$srcUuid = $srcEl.GetAttribute("uuid")
if (-not $srcUuid) {
Write-Error "No uuid attribute on source element in ${dirName}/${objName}.xml"
exit 1
}
# Extract properties for CommonModule
$srcProps = @{}
$propsNode = $srcEl.SelectSingleNode("md:Properties", $srcNs)
if ($propsNode) {
foreach ($propName in $commonModuleProps) {
$propNode = $propsNode.SelectSingleNode("md:${propName}", $srcNs)
if ($propNode) {
$srcProps[$propName] = $propNode.InnerText.Trim()
}
}
}
return @{
Uuid = $srcUuid
Properties = $srcProps
Element = $srcEl
NsManager = $srcNs
}
}
# --- 11. Helper: generate InternalInfo XML ---
function Build-InternalInfoXml {
param([string]$typeName, [string]$objName, [string]$indent)
$types = $script:generatedTypes[$typeName]
if (-not $types -or $types.Count -eq 0) {
return "${indent}<InternalInfo/>"
}
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("${indent}<InternalInfo>") | Out-Null
# ExchangePlan: ThisNode UUID before GeneratedTypes
if ($typeName -eq "ExchangePlan") {
$thisNodeUuid = [guid]::NewGuid().ToString()
$sb.AppendLine("${indent}`t<xr:ThisNode>${thisNodeUuid}</xr:ThisNode>") | Out-Null
}
foreach ($gt in $types) {
$fullName = "$($gt.prefix).${objName}"
$typeId = [guid]::NewGuid().ToString()
$valueId = [guid]::NewGuid().ToString()
$sb.AppendLine("${indent}`t<xr:GeneratedType name=`"${fullName}`" category=`"$($gt.category)`">") | Out-Null
$sb.AppendLine("${indent}`t`t<xr:TypeId>${typeId}</xr:TypeId>") | Out-Null
$sb.AppendLine("${indent}`t`t<xr:ValueId>${valueId}</xr:ValueId>") | Out-Null
$sb.AppendLine("${indent}`t</xr:GeneratedType>") | Out-Null
}
$sb.Append("${indent}</InternalInfo>") | Out-Null
return $sb.ToString()
}
# --- 12. Helper: build borrowed object XML ---
function Build-BorrowedObjectXml {
param(
[string]$typeName,
[string]$objName,
[string]$sourceUuid,
[hashtable]$sourceProps
)
$newUuid = [guid]::NewGuid().ToString()
$internalInfoXml = Build-InternalInfoXml $typeName $objName "`t`t"
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">") | Out-Null
$sb.AppendLine("`t<${typeName} uuid=`"${newUuid}`">") | Out-Null
# InternalInfo
$sb.AppendLine($internalInfoXml) | Out-Null
# Properties
$sb.AppendLine("`t`t<Properties>") | Out-Null
$sb.AppendLine("`t`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
$sb.AppendLine("`t`t`t<Name>${objName}</Name>") | Out-Null
$sb.AppendLine("`t`t`t<Comment/>") | Out-Null
$sb.AppendLine("`t`t`t<ExtendedConfigurationObject>${sourceUuid}</ExtendedConfigurationObject>") | Out-Null
# CommonModule: extra properties from source
if ($typeName -eq "CommonModule") {
foreach ($propName in $commonModuleProps) {
$propVal = "false"
if ($sourceProps.ContainsKey($propName)) {
$propVal = $sourceProps[$propName]
}
$sb.AppendLine("`t`t`t<${propName}>${propVal}</${propName}>") | Out-Null
}
}
$sb.AppendLine("`t`t</Properties>") | Out-Null
# ChildObjects (for types that need it)
if ($typesWithChildObjects -contains $typeName) {
$sb.AppendLine("`t`t<ChildObjects/>") | Out-Null
}
$sb.AppendLine("`t</${typeName}>") | Out-Null
$sb.Append("</MetaDataObject>") | Out-Null
return $sb.ToString()
}
# --- 13. Helper: add object to extension ChildObjects ---
function Add-ToChildObjects {
param([string]$typeName, [string]$objName)
$cfgIndent = Get-ChildIndent $script:cfgEl
# Expand self-closing ChildObjects if needed
if (-not $script:childObjsEl.HasChildNodes -or $script:childObjsEl.IsEmpty) {
Expand-SelfClosingElement $script:childObjsEl $cfgIndent
}
$childIndent = Get-ChildIndent $script:childObjsEl
$typeIdx = $script:typeOrder.IndexOf($typeName)
if ($typeIdx -lt 0) {
Write-Error "Unknown type '$typeName' for ChildObjects ordering"
exit 1
}
# Dedup check
foreach ($child in $script:childObjsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $typeName -and $child.InnerText -eq $objName) {
Warn "Already in ChildObjects: ${typeName}.${objName}"
return
}
}
# Find insertion point: after last element of same type, or before first element of later type
$insertBefore = $null
$lastSameType = $null
foreach ($child in $script:childObjsEl.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$childTypeIdx = $script:typeOrder.IndexOf($child.LocalName)
if ($childTypeIdx -lt 0) { continue }
if ($child.LocalName -eq $typeName) {
# Same type -- check alphabetical order
if ($child.InnerText -gt $objName -and -not $insertBefore) {
$insertBefore = $child
}
$lastSameType = $child
} elseif ($childTypeIdx -gt $typeIdx -and -not $insertBefore) {
# First element of a later type -- insert before it
$insertBefore = $child
}
}
# Create element
$newEl = $script:xmlDoc.CreateElement($typeName, $script:mdNs)
$newEl.InnerText = $objName
if ($insertBefore) {
Insert-BeforeElement $script:childObjsEl $newEl $insertBefore $childIndent
} else {
Insert-BeforeElement $script:childObjsEl $newEl $null $childIndent
}
Info "Added to ChildObjects: ${typeName}.${objName}"
}
# --- 14. Process each item ---
$borrowedFiles = @()
$borrowedCount = 0
foreach ($item in $items) {
$dotIdx = $item.IndexOf(".")
if ($dotIdx -lt 1) {
Write-Error "Invalid format '${item}', expected 'Type.Name'"
exit 1
}
$typeName = $item.Substring(0, $dotIdx)
$objName = $item.Substring($dotIdx + 1)
if (-not $childTypeDirMap.ContainsKey($typeName)) {
Write-Error "Unknown type '${typeName}'"
exit 1
}
$dirName = $childTypeDirMap[$typeName]
Info "Borrowing ${typeName}.${objName}..."
# Read source object
$src = Read-SourceObject $typeName $objName
Info " Source UUID: $($src.Uuid)"
# Build borrowed object XML
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
# Create directory in extension if needed
$targetDir = Join-Path $extDir $dirName
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
}
# Write borrowed object XML with UTF-8 BOM
$targetFile = Join-Path $targetDir "${objName}.xml"
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
Info " Created: $targetFile"
# Add to ChildObjects
Add-ToChildObjects $typeName $objName
$borrowedFiles += $targetFile
$borrowedCount++
}
# --- 15. Save modified Configuration.xml ---
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
$settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$script:xmlDoc.Save($writer)
$writer.Flush(); $writer.Close()
$bytes = $memStream.ToArray()
$memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($extResolvedPath, $text, $utf8Bom)
Info "Saved: $extResolvedPath"
# --- 16. Summary ---
Write-Host ""
Write-Host "=== cfe-borrow summary ==="
Write-Host " Extension: $extDir"
Write-Host " Config: $cfgDir"
Write-Host " Borrowed: $borrowedCount object(s)"
foreach ($f in $borrowedFiles) {
Write-Host " - $f"
}
exit 0
+15
View File
@@ -0,0 +1,15 @@
---
name: cfe-diff
description: Анализ и сравнение расширения конфигурации 1С (CFE) — обзор изменений, проверка переноса
argument-hint: -ExtensionPath <path> -ConfigPath <path> [-Mode A|B]
allowed-tools:
- Bash
- Read
- Glob
---
Анализирует расширение: Mode A — обзор изменений, Mode B — проверка переноса в конфигурацию.
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-diff\scripts\cfe-diff.ps1 -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
```
@@ -0,0 +1,392 @@
# cfe-diff v1.0 — Analyze and compare 1C configuration extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ExtensionPath,
[Parameter(Mandatory)]
[string]$ConfigPath,
[ValidateSet("A","B")]
[string]$Mode = "A"
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
}
if (Test-Path $ExtensionPath -PathType Leaf) { $ExtensionPath = Split-Path $ExtensionPath -Parent }
if (Test-Path $ConfigPath -PathType Leaf) { $ConfigPath = Split-Path $ConfigPath -Parent }
$extCfg = Join-Path $ExtensionPath "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 $srcCfg)) { Write-Error "Config Configuration.xml not found: $srcCfg"; exit 1 }
# --- Type -> directory mapping ---
$childTypeDirMap = @{
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
"CommonModule"="CommonModules"; "CommonPicture"="CommonPictures"
"CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates"
"ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors"
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants"
"FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes"
"FunctionalOptionsParameter"="FunctionalOptionsParameters"
"CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals"
"SessionParameter"="SessionParameters"; "StyleItem"="StyleItems"
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
"CommonAttribute"="CommonAttributes"
}
# --- Parse extension Configuration.xml ---
$extDoc = New-Object System.Xml.XmlDocument
$extDoc.PreserveWhitespace = $false
$extDoc.Load($extCfg)
$ns = New-Object System.Xml.XmlNamespaceManager($extDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$extProps = $extDoc.SelectSingleNode("//md:Configuration/md:Properties", $ns)
$extNameNode = $extProps.SelectSingleNode("md:Name", $ns)
$extName = if ($extNameNode) { $extNameNode.InnerText } else { "?" }
$prefixNode = $extProps.SelectSingleNode("md:NamePrefix", $ns)
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "" }
$purposeNode = $extProps.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns)
$purpose = if ($purposeNode) { $purposeNode.InnerText } else { "?" }
Write-Host "=== cfe-diff Mode ${Mode}: $extName (${purpose}) ==="
Write-Host " NamePrefix: $namePrefix"
Write-Host ""
# --- Collect ChildObjects ---
$childObjNode = $extDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns)
if (-not $childObjNode) {
Write-Host "[WARN] No ChildObjects in extension"
exit 0
}
$objects = @()
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq "Language") { continue }
$objects += @{ Type = $child.LocalName; Name = $child.InnerText }
}
if ($objects.Count -eq 0) {
Write-Host "No objects (besides Language) in extension."
exit 0
}
# --- Helper: check if object is borrowed ---
function Get-ObjectInfo {
param([string]$objType, [string]$objName)
if (-not $childTypeDirMap.ContainsKey($objType)) { return $null }
$dirName = $childTypeDirMap[$objType]
$objFile = Join-Path (Join-Path $ExtensionPath $dirName) "${objName}.xml"
if (-not (Test-Path $objFile)) { return @{ Borrowed = $false; File = $objFile; Exists = $false } }
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $false
$doc.Load($objFile)
$objNs = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$objEl = $null
foreach ($c in $doc.DocumentElement.ChildNodes) {
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
}
if (-not $objEl) { return @{ Borrowed = $false; File = $objFile; Exists = $true } }
$propsEl = $objEl.SelectSingleNode("md:Properties", $objNs)
$obNode = if ($propsEl) { $propsEl.SelectSingleNode("md:ObjectBelonging", $objNs) } else { $null }
$info = @{
Borrowed = ($obNode -and $obNode.InnerText -eq "Adopted")
File = $objFile
Exists = $true
Type = $objType
Name = $objName
DirName = $dirName
ObjElement = $objEl
ObjNs = $objNs
}
return $info
}
# --- Helper: find .bsl files for object ---
function Get-BslFiles {
param([string]$objType, [string]$objName)
if (-not $childTypeDirMap.ContainsKey($objType)) { return @() }
$dirName = $childTypeDirMap[$objType]
$objDir = Join-Path (Join-Path $ExtensionPath $dirName) $objName
if (-not (Test-Path $objDir -PathType Container)) { return @() }
$bslFiles = @()
$extDir = Join-Path $objDir "Ext"
if (Test-Path $extDir) {
$items = Get-ChildItem -Path $extDir -Filter "*.bsl" -ErrorAction SilentlyContinue
foreach ($item in $items) { $bslFiles += $item.FullName }
}
# Forms
$formsDir = Join-Path $objDir "Forms"
if (Test-Path $formsDir) {
$formModules = Get-ChildItem -Path $formsDir -Recurse -Filter "Module.bsl" -ErrorAction SilentlyContinue
foreach ($fm in $formModules) { $bslFiles += $fm.FullName }
}
return $bslFiles
}
# --- Helper: parse interceptors from .bsl ---
function Get-Interceptors {
param([string]$bslPath)
if (-not (Test-Path $bslPath)) { return @() }
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
$interceptors = @()
$i = 0
while ($i -lt $lines.Count) {
$line = $lines[$i].Trim()
if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
$type = $Matches[1]
$method = $Matches[2]
$interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath }
}
$i++
}
return $interceptors
}
# --- Helper: extract #Вставка blocks from .bsl ---
function Get-InsertionBlocks {
param([string]$bslPath)
if (-not (Test-Path $bslPath)) { return @() }
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
$blocks = @()
$inBlock = $false
$blockLines = @()
$startLine = 0
for ($i = 0; $i -lt $lines.Count; $i++) {
$line = $lines[$i].Trim()
if ($line -eq "#Вставка") {
$inBlock = $true
$blockLines = @()
$startLine = $i + 1
} elseif ($line -eq "#КонецВставки" -and $inBlock) {
$inBlock = $false
$blocks += @{
StartLine = $startLine
EndLine = $i + 1
Code = ($blockLines -join "`n").Trim()
File = $bslPath
}
} elseif ($inBlock) {
$blockLines += $lines[$i]
}
}
return $blocks
}
# ============================================================
# MODE A: Extension overview
# ============================================================
if ($Mode -eq "A") {
$borrowedList = @()
$ownList = @()
foreach ($obj in $objects) {
$info = Get-ObjectInfo $obj.Type $obj.Name
if (-not $info) {
Write-Host " [?] $($obj.Type).$($obj.Name) — unknown type"
continue
}
if (-not $info.Exists) {
Write-Host " [?] $($obj.Type).$($obj.Name) — file not found"
continue
}
if ($info.Borrowed) {
$borrowedList += $obj
Write-Host " [BORROWED] $($obj.Type).$($obj.Name)"
# Find .bsl files and interceptors
$bslFiles = Get-BslFiles $obj.Type $obj.Name
foreach ($bsl in $bslFiles) {
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
$interceptors = Get-Interceptors $bsl
if ($interceptors.Count -gt 0) {
foreach ($ic in $interceptors) {
Write-Host " &$($ic.Type)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
}
} else {
Write-Host " $relPath (no interceptors)"
}
}
# Check for own attributes/forms in ChildObjects
if ($info.ObjElement) {
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
if ($childObj) {
$ownAttrs = 0
$ownForms = 0
$ownTS = 0
$borrowedItems = 0
foreach ($c in $childObj.ChildNodes) {
if ($c.NodeType -ne 'Element') { continue }
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
if ($cProps) {
$cOb = $cProps.SelectSingleNode("md:ObjectBelonging", $info.ObjNs)
if ($cOb -and $cOb.InnerText -eq "Adopted") {
$borrowedItems++
continue
}
}
switch ($c.LocalName) {
"Attribute" { $ownAttrs++ }
"TabularSection" { $ownTS++ }
"Form" { $ownForms++ }
}
}
$parts = @()
if ($ownAttrs -gt 0) { $parts += "$ownAttrs own attrs" }
if ($ownTS -gt 0) { $parts += "$ownTS own TS" }
if ($ownForms -gt 0) { $parts += "$ownForms own forms" }
if ($borrowedItems -gt 0) { $parts += "$borrowedItems borrowed items" }
if ($parts.Count -gt 0) {
Write-Host " ChildObjects: $($parts -join ', ')"
}
}
}
} else {
$ownList += $obj
Write-Host " [OWN] $($obj.Type).$($obj.Name)"
# Brief info for own objects
if ($info.ObjElement) {
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
if ($childObj) {
$attrs = 0; $forms = 0; $ts = 0
foreach ($c in $childObj.ChildNodes) {
if ($c.NodeType -ne 'Element') { continue }
switch ($c.LocalName) {
"Attribute" { $attrs++ }
"TabularSection" { $ts++ }
"Form" { $forms++ }
}
}
$parts = @()
if ($attrs -gt 0) { $parts += "$attrs attrs" }
if ($ts -gt 0) { $parts += "$ts TS" }
if ($forms -gt 0) { $parts += "$forms forms" }
if ($parts.Count -gt 0) {
Write-Host " $($parts -join ', ')"
}
}
}
}
}
Write-Host ""
Write-Host "=== Summary: $($borrowedList.Count) borrowed, $($ownList.Count) own objects ==="
}
# ============================================================
# MODE B: Transfer check
# ============================================================
if ($Mode -eq "B") {
$transferred = 0
$notTransferred = 0
$needsReview = 0
foreach ($obj in $objects) {
$info = Get-ObjectInfo $obj.Type $obj.Name
if (-not $info -or -not $info.Exists -or -not $info.Borrowed) { continue }
# Find .bsl files with &ИзменениеИКонтроль
$bslFiles = Get-BslFiles $obj.Type $obj.Name
foreach ($bsl in $bslFiles) {
$interceptors = Get-Interceptors $bsl
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
if ($macInterceptors.Count -eq 0) { continue }
foreach ($ic in $macInterceptors) {
$methodName = $ic.Method
$relBsl = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
# Find #Вставка blocks in this file
$insertBlocks = Get-InsertionBlocks $bsl
if ($insertBlocks.Count -eq 0) {
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — no #Вставка blocks"
$needsReview++
continue
}
# Find corresponding module in config
if (-not $childTypeDirMap.ContainsKey($obj.Type)) { continue }
$dirName = $childTypeDirMap[$obj.Type]
$configBsl = $bsl.Replace($ExtensionPath, $ConfigPath)
if (-not (Test-Path $configBsl)) {
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — config module not found"
$needsReview++
continue
}
$configContent = [System.IO.File]::ReadAllText($configBsl, [System.Text.Encoding]::UTF8)
$allTransferred = $true
foreach ($block in $insertBlocks) {
$code = $block.Code
if (-not $code) { continue }
# Normalize whitespace for comparison
$codeNorm = $code -replace '\s+', ' '
$configNorm = $configContent -replace '\s+', ' '
if ($configNorm.Contains($codeNorm)) {
# Found in config
} else {
$allTransferred = $false
}
}
if ($allTransferred) {
Write-Host " [TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — $($insertBlocks.Count) block(s)"
$transferred++
} else {
Write-Host " [NOT_TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — some blocks not found in config"
$notTransferred++
}
}
}
}
Write-Host ""
Write-Host "=== Transfer check: $transferred transferred, $notTransferred not transferred, $needsReview needs review ==="
}
+15
View File
@@ -0,0 +1,15 @@
---
name: cfe-init
description: Создать расширение конфигурации 1С (CFE) — scaffold XML-исходников расширения
argument-hint: <Name> [-Purpose Patch|Customization|AddOn] [-CompatibilityMode Version8_3_24]
allowed-tools:
- Bash
- Read
- Glob
---
Создаёт scaffold расширения конфигурации 1С: `Configuration.xml`, `Languages/Русский.xml`, опционально `Roles/`.
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-init\scripts\cfe-init.ps1 -Name "МоёРасширение"
```
+57
View File
@@ -0,0 +1,57 @@
# /cfe-init — Создание расширения конфигурации 1С (CFE)
Создаёт scaffold исходников расширения конфигурации 1С: `Configuration.xml`, `Languages/Русский.xml`, опционально `Roles/`.
## Рекомендуемый пайплайн
Перед вызовом cfe-init рекомендуется запустить `cf-info -ConfigPath <путь к конфигурации> -Mode brief`, чтобы получить:
- `CompatibilityMode` → передать в `-CompatibilityMode`
- Версию конфигурации → по умолчанию `-Version` = `<ВерсияКонфигурации>.1`
## Параметры
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `Name` | Имя расширения (обязат.) | — |
| `Synonym` | Синоним | = Name |
| `NamePrefix` | Префикс собственных объектов | = Name + "_" |
| `OutputDir` | Каталог для создания | `src` |
| `Purpose` | Назначение: `Patch` / `Customization` / `AddOn` | `Customization` |
| `Version` | Версия расширения | — |
| `Vendor` | Поставщик | — |
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
| `NoRole` | Без основной роли | false |
## Что создаётся
```
<OutputDir>/
├── Configuration.xml # Свойства расширения
├── Languages/
│ └── Русский.xml # Язык (заимствованный формат)
└── Roles/ # Если не -NoRole
└── <Prefix>ОсновнаяРоль.xml
```
## Примеры
```powershell
# Расширение-исправление для ERP
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir test-tmp/cfe
# Расширение-адаптация с версией
... -Name МоёРасширение -Purpose Customization -Version "1.0.0.1" -Vendor "Компания" -OutputDir test-tmp/cfe2
# Без роли
... -Name Расш2 -Purpose AddOn -NoRole -OutputDir test-tmp/cfe3
# С явным префиксом
... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -OutputDir test-tmp/cfe4
```
## Верификация
```
/cfe-init МоёРасширение -OutputDir test-tmp/cfe
/cfe-validate test-tmp/cfe — валидировать
```
@@ -0,0 +1,208 @@
# cfe-init v1.0 — Create 1C configuration extension scaffold (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$Name,
[string]$Synonym = $Name,
[string]$NamePrefix,
[string]$OutputDir = "src",
[ValidateSet("Patch","Customization","AddOn")]
[string]$Purpose = "Customization",
[string]$Version,
[string]$Vendor,
[string]$CompatibilityMode = "Version8_3_24",
[switch]$NoRole
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Default NamePrefix ---
if (-not $NamePrefix) {
$NamePrefix = "${Name}_"
}
# --- Resolve output dir ---
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
$OutputDir = Join-Path (Get-Location).Path $OutputDir
}
# --- Check existing ---
$cfgFile = Join-Path $OutputDir "Configuration.xml"
if (Test-Path $cfgFile) {
Write-Error "Configuration.xml already exists: $cfgFile"
exit 1
}
# --- Generate UUIDs ---
$uuidCfg = [guid]::NewGuid().ToString()
$uuidLang = [guid]::NewGuid().ToString()
$uuidRole = [guid]::NewGuid().ToString()
# 7 ContainedObject ObjectIds
$co1 = [guid]::NewGuid().ToString()
$co2 = [guid]::NewGuid().ToString()
$co3 = [guid]::NewGuid().ToString()
$co4 = [guid]::NewGuid().ToString()
$co5 = [guid]::NewGuid().ToString()
$co6 = [guid]::NewGuid().ToString()
$co7 = [guid]::NewGuid().ToString()
# --- Synonym XML ---
$synonymXml = ""
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"
}
# --- Optional properties ---
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
# --- Role name ---
$roleName = "${NamePrefix}ОсновнаяРоль"
# --- DefaultRoles XML ---
$defaultRolesXml = ""
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"
}
# --- ChildObjects ---
$childObjectsXml = "`r`n`t`t`t<Language>Русский</Language>"
if (-not $NoRole) {
$childObjectsXml += "`r`n`t`t`t<Role>$roleName</Role>"
}
$childObjectsXml += "`r`n`t`t"
# --- Configuration.xml ---
$cfgXml = @"
<?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">
<Configuration uuid="$uuidCfg">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>$co1</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>$co2</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>$co3</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>$co4</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>$co5</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>$co6</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>$co7</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
<Synonym>$synonymXml</Synonym>
<Comment/>
<ConfigurationExtensionPurpose>$Purpose</ConfigurationExtensionPurpose>
<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
<NamePrefix>$([System.Security.SecurityElement]::Escape($NamePrefix))</NamePrefix>
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles>$defaultRolesXml</DefaultRoles>
<Vendor>$vendorXml</Vendor>
<Version>$versionXml</Version>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>
<DetailedInformation/>
<Copyright/>
<VendorInformationAddress/>
<ConfigurationInformationAddress/>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
</Properties>
<ChildObjects>$childObjectsXml</ChildObjects>
</Configuration>
</MetaDataObject>
"@
# --- Languages/Русский.xml (adopted format) ---
$langXml = @"
<?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">
<Language uuid="$uuidLang">
<InternalInfo/>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>Русский</Name>
<Comment/>
<ExtendedConfigurationObject>00000000-0000-0000-0000-000000000000</ExtendedConfigurationObject>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
"@
# --- Role XML ---
$roleXml = @"
<?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">
<Role uuid="$uuidRole">
<Properties>
<Name>$([System.Security.SecurityElement]::Escape($roleName))</Name>
<Synonym/>
<Comment/>
</Properties>
</Role>
</MetaDataObject>
"@
# --- Create directories ---
if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
$langDir = Join-Path $OutputDir "Languages"
if (-not (Test-Path $langDir)) {
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
}
# --- Write files with UTF-8 BOM ---
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
$langFile = Join-Path $langDir "Русский.xml"
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
# --- Role ---
if (-not $NoRole) {
$roleDir = Join-Path $OutputDir "Roles"
if (-not (Test-Path $roleDir)) {
New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
}
$roleFile = Join-Path $roleDir "$roleName.xml"
[System.IO.File]::WriteAllText($roleFile, $roleXml, $enc)
}
# --- Output ---
Write-Host "[OK] Создано расширение: $Name"
Write-Host " Каталог: $OutputDir"
Write-Host " Назначение: $Purpose"
Write-Host " Префикс: $NamePrefix"
Write-Host " Configuration.xml: $cfgFile"
Write-Host " Languages: $langFile"
if (-not $NoRole) {
Write-Host " Role: $roleFile"
}
+15
View File
@@ -0,0 +1,15 @@
---
name: cfe-patch-method
description: Генерация перехватчика метода в расширении 1С (CFE) — &Перед, &После, &ИзменениеИКонтроль
argument-hint: -ExtensionPath <path> -ModulePath "Catalog.X.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
allowed-tools:
- Bash
- Read
- Glob
---
Генерирует .bsl файл с декоратором перехвата для заимствованного объекта расширения.
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-patch-method\scripts\cfe-patch-method.ps1 -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
```
@@ -0,0 +1,186 @@
# cfe-patch-method v1.0 — Generate method interceptor for 1C extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ExtensionPath,
[Parameter(Mandatory)]
[string]$ModulePath,
[Parameter(Mandatory)]
[string]$MethodName,
[Parameter(Mandatory)]
[ValidateSet("Before","After","ModificationAndControl")]
[string]$InterceptorType,
[string]$Context = "НаСервере",
[switch]$IsFunction
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve extension path ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (Test-Path $ExtensionPath -PathType Leaf) {
$ExtensionPath = Split-Path $ExtensionPath -Parent
}
$cfgFile = Join-Path $ExtensionPath "Configuration.xml"
if (-not (Test-Path $cfgFile)) {
Write-Error "Configuration.xml not found in: $ExtensionPath"
exit 1
}
# --- Read NamePrefix from Configuration.xml ---
$cfgDoc = New-Object System.Xml.XmlDocument
$cfgDoc.PreserveWhitespace = $false
$cfgDoc.Load($cfgFile)
$cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgDoc.NameTable)
$cfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$propsNode = $cfgDoc.SelectSingleNode("//md:Configuration/md:Properties", $cfgNs)
$prefixNode = if ($propsNode) { $propsNode.SelectSingleNode("md:NamePrefix", $cfgNs) } else { $null }
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "Расш_" }
# --- Map ModulePath to file path ---
# ModulePath formats:
# Catalog.X.ObjectModule -> Catalogs/X/Ext/ObjectModule.bsl
# Catalog.X.ManagerModule -> Catalogs/X/Ext/ManagerModule.bsl
# Catalog.X.Form.Y -> Catalogs/X/Forms/Y/Ext/Form/Module.bsl
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
# Document.X.ObjectModule -> Documents/X/Ext/ObjectModule.bsl
# Document.X.ManagerModule -> Documents/X/Ext/ManagerModule.bsl
# Document.X.Form.Y -> Documents/X/Forms/Y/Ext/Form/Module.bsl
$typeDirMap = @{
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
"CommonModule"="CommonModules"; "Report"="Reports"; "DataProcessor"="DataProcessors"
"ExchangePlan"="ExchangePlans"; "ChartOfAccounts"="ChartsOfAccounts"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
"AccountingRegister"="AccountingRegisters"; "CalculationRegister"="CalculationRegisters"
}
$parts = $ModulePath.Split(".")
if ($parts.Count -lt 2) {
Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module or CommonModule.Name"
exit 1
}
$objType = $parts[0]
$objName = $parts[1]
if (-not $typeDirMap.ContainsKey($objType)) {
Write-Error "Unknown object type: $objType"
exit 1
}
$dirName = $typeDirMap[$objType]
$bslFile = $null
if ($objType -eq "CommonModule") {
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
$bslFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Ext") "Module.bsl"
} elseif ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
# Type.X.Form.Y -> Types/X/Forms/Y/Ext/Form/Module.bsl
$formName = $parts[3]
$bslFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext") "Form") "Module.bsl"
} elseif ($parts.Count -ge 3) {
# Type.X.ObjectModule -> Types/X/Ext/ObjectModule.bsl
$moduleName = $parts[2]
$moduleFileName = switch ($moduleName) {
"ObjectModule" { "ObjectModule.bsl" }
"ManagerModule" { "ManagerModule.bsl" }
"RecordSetModule" { "RecordSetModule.bsl" }
"CommandModule" { "CommandModule.bsl" }
default { "$moduleName.bsl" }
}
$bslFile = Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) (Join-Path "Ext" $moduleFileName)
} else {
Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module, Type.Name.Form.FormName, or CommonModule.Name"
exit 1
}
# --- Map InterceptorType to decorator ---
$decorator = switch ($InterceptorType) {
"Before" { "&Перед" }
"After" { "&После" }
"ModificationAndControl" { "&ИзменениеИКонтроль" }
}
# --- Map Context to annotation ---
$contextAnnotation = switch ($Context) {
"НаСервере" { "&НаСервере" }
"НаКлиенте" { "&НаКлиенте" }
"НаСервереБезКонтекста" { "&НаСервереБезКонтекста" }
default { "&$Context" }
}
# --- Procedure name ---
$procName = "${namePrefix}${MethodName}"
# --- Generate BSL code ---
$keyword = if ($IsFunction) { "Функция" } else { "Процедура" }
$endKeyword = if ($IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }
$bodyLines = @()
switch ($InterceptorType) {
"Before" {
$bodyLines += "`t// TODO: код перед вызовом оригинального метода"
}
"After" {
$bodyLines += "`t// TODO: код после вызова оригинального метода"
}
"ModificationAndControl" {
$bodyLines += "`t// Скопируйте тело оригинального метода и внесите изменения,"
$bodyLines += "`t// используя маркеры #Удаление / #КонецУдаления и #Вставка / #КонецВставки"
}
}
if ($IsFunction) {
$bodyLines += "`t"
$bodyLines += "`tВозврат Неопределено; // TODO: заменить на реальное возвращаемое значение"
}
$bslCode = @()
$bslCode += "$contextAnnotation"
$bslCode += "${decorator}(`"$MethodName`")"
$bslCode += "$keyword ${procName}()"
$bslCode += $bodyLines
$bslCode += "$endKeyword"
$bslText = ($bslCode -join "`r`n") + "`r`n"
# --- Check if file exists and append ---
$bslDir = Split-Path $bslFile -Parent
if (-not (Test-Path $bslDir)) {
New-Item -ItemType Directory -Path $bslDir -Force | Out-Null
}
$enc = New-Object System.Text.UTF8Encoding($true)
if (Test-Path $bslFile) {
# Append to existing file
$existing = [System.IO.File]::ReadAllText($bslFile, $enc)
$separator = "`r`n"
if ($existing -and -not $existing.EndsWith("`n")) {
$separator = "`r`n`r`n"
}
$newContent = $existing + $separator + $bslText
[System.IO.File]::WriteAllText($bslFile, $newContent, $enc)
Write-Host "[OK] Добавлен перехватчик в существующий файл"
} else {
[System.IO.File]::WriteAllText($bslFile, $bslText, $enc)
Write-Host "[OK] Создан файл модуля"
}
Write-Host " Файл: $bslFile"
Write-Host " Декоратор: $decorator(`"$MethodName`")"
Write-Host " Процедура: ${procName}()"
Write-Host " Контекст: $contextAnnotation"
+15
View File
@@ -0,0 +1,15 @@
---
name: cfe-validate
description: Валидация структурной корректности расширения конфигурации 1С (CFE) — корневая структура, свойства, состав, заимствованные объекты
argument-hint: <ExtensionPath> [-MaxErrors 30]
allowed-tools:
- Bash
- Read
- Glob
---
Валидирует расширение конфигурации 1С: XML-структура, свойства, ChildObjects, заимствованные объекты.
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-validate\scripts\cfe-validate.ps1 -ExtensionPath src
```
@@ -0,0 +1,607 @@
# cfe-validate v1.0 — Validate 1C configuration extension structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ExtensionPath,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (Test-Path $ExtensionPath -PathType Container) {
$candidate = Join-Path $ExtensionPath "Configuration.xml"
if (Test-Path $candidate) {
$ExtensionPath = $candidate
} else {
Write-Host "[ERROR] No Configuration.xml found in directory: $ExtensionPath"
exit 1
}
}
if (-not (Test-Path $ExtensionPath)) {
Write-Host "[ERROR] File not found: $ExtensionPath"
exit 1
}
$resolvedPath = (Resolve-Path $ExtensionPath).Path
$configDir = Split-Path $resolvedPath -Parent
# --- Output infrastructure ---
$script:errors = 0
$script:warnings = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
function Out-Line {
param([string]$msg)
$script:output.AppendLine($msg) | Out-Null
}
function Report-OK {
param([string]$msg)
Out-Line "[OK] $msg"
}
function Report-Error {
param([string]$msg)
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) {
$script:stopped = $true
}
}
function Report-Warn {
param([string]$msg)
$script:warnings++
Out-Line "[WARN] $msg"
}
$finalize = {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ==="
$result = $script:output.ToString()
Write-Host $result
if ($OutFile) {
$utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
}
# --- Reference tables ---
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
# 7 fixed ClassIds for Configuration
$validClassIds = @(
"9cd510cd-abfc-11d4-9434-004095e12fc7",
"9fcd25a0-4822-11d4-9414-008048da11f9",
"e3687481-0a87-462c-a166-9f34594f9bba",
"9de14907-ec23-4a07-96f0-85521cb6b53b",
"51f2d5d8-ea4d-4064-8892-82951750031e",
"e68182ea-4237-4383-967f-90c1e3370bc7",
"fb282519-d103-4dd3-bc12-cb271d631dfc"
)
# 44 types in canonical order
$childObjectTypes = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
# Type -> directory mapping
$childTypeDirMap = @{
"Language"="Languages"; "Subsystem"="Subsystems"; "StyleItem"="StyleItems"; "Style"="Styles"
"CommonPicture"="CommonPictures"; "SessionParameter"="SessionParameters"; "Role"="Roles"
"CommonTemplate"="CommonTemplates"; "FilterCriterion"="FilterCriteria"; "CommonModule"="CommonModules"
"CommonAttribute"="CommonAttributes"; "ExchangePlan"="ExchangePlans"; "XDTOPackage"="XDTOPackages"
"WebService"="WebServices"; "HTTPService"="HTTPServices"; "WSReference"="WSReferences"
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
"SettingsStorage"="SettingsStorages"; "FunctionalOption"="FunctionalOptions"
"FunctionalOptionsParameter"="FunctionalOptionsParameters"; "DefinedType"="DefinedTypes"
"CommonCommand"="CommonCommands"; "CommandGroup"="CommandGroups"; "Constant"="Constants"
"CommonForm"="CommonForms"; "Catalog"="Catalogs"; "Document"="Documents"
"DocumentNumerator"="DocumentNumerators"; "Sequence"="Sequences"
"DocumentJournal"="DocumentJournals"; "Enum"="Enums"; "Report"="Reports"
"DataProcessor"="DataProcessors"; "InformationRegister"="InformationRegisters"
"AccumulationRegister"="AccumulationRegisters"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"
"CalculationRegister"="CalculationRegisters"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"IntegrationService"="IntegrationServices"
}
# Valid enum values for extension properties
$validEnumValues = @{
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
"DefaultRunMode" = @("ManagedApplication","OrdinaryApplication","Auto")
"ScriptVariant" = @("Russian","English")
"InterfaceCompatibilityMode" = @("Taxi","TaxiEnableVersion8_2","Version8_2")
}
# --- 1. Parse XML ---
Out-Line ""
$xmlDoc = $null
try {
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $false
$xmlDoc.Load($resolvedPath)
} catch {
Out-Line "=== Validation: Extension (parse failed) ==="
Out-Line ""
Report-Error "1. XML parse failed: $($_.Exception.Message)"
& $finalize
exit 1
}
# --- Register namespaces ---
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("app", "http://v8.1c.ru/8.2/managed-application/core")
$root = $xmlDoc.DocumentElement
# --- Check 1: Root structure ---
$check1Ok = $true
$expectedNs = "http://v8.1c.ru/8.3/MDClasses"
if ($root.LocalName -ne "MetaDataObject") {
Report-Error "1. Root element is '$($root.LocalName)', expected 'MetaDataObject'"
& $finalize
exit 1
}
if ($root.NamespaceURI -ne $expectedNs) {
Report-Error "1. Root namespace is '$($root.NamespaceURI)', expected '$expectedNs'"
$check1Ok = $false
}
$version = $root.GetAttribute("version")
if (-not $version) {
Report-Warn "1. Missing version attribute on MetaDataObject"
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
}
# Must have Configuration child
$cfgNode = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Configuration" -and $child.NamespaceURI -eq $expectedNs) {
$cfgNode = $child; break
}
}
if (-not $cfgNode) {
Report-Error "1. No <Configuration> element found inside MetaDataObject"
& $finalize
exit 1
}
# UUID
$cfgUuid = $cfgNode.GetAttribute("uuid")
if (-not $cfgUuid) {
Report-Error "1. Missing uuid on <Configuration>"
$check1Ok = $false
} elseif ($cfgUuid -notmatch $guidPattern) {
Report-Error "1. Invalid uuid '$cfgUuid' on <Configuration>"
$check1Ok = $false
}
# Get name early for header
$propsNode = $cfgNode.SelectSingleNode("md:Properties", $ns)
$nameNode = if ($propsNode) { $propsNode.SelectSingleNode("md:Name", $ns) } else { $null }
$objName = if ($nameNode -and $nameNode.InnerText) { $nameNode.InnerText } else { "(unknown)" }
$script:output.Insert(0, "=== Validation: Extension.$objName ===$([Environment]::NewLine)") | Out-Null
if ($check1Ok) {
Report-OK "1. Root structure: MetaDataObject/Configuration, version $version"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 2: InternalInfo ---
$internalInfo = $cfgNode.SelectSingleNode("md:InternalInfo", $ns)
$check2Ok = $true
if (-not $internalInfo) {
Report-Error "2. InternalInfo: missing"
} else {
$contained = $internalInfo.SelectNodes("xr:ContainedObject", $ns)
if ($contained.Count -ne 7) {
Report-Warn "2. InternalInfo: expected 7 ContainedObject, found $($contained.Count)"
}
$foundClassIds = @{}
foreach ($co in $contained) {
$classId = $co.SelectSingleNode("xr:ClassId", $ns)
$objectId = $co.SelectSingleNode("xr:ObjectId", $ns)
if (-not $classId -or -not $classId.InnerText) {
Report-Error "2. ContainedObject missing ClassId"
$check2Ok = $false
continue
}
$cid = $classId.InnerText
if ($validClassIds -notcontains $cid) {
Report-Error "2. Unknown ClassId: $cid"
$check2Ok = $false
}
if ($foundClassIds.ContainsKey($cid)) {
Report-Error "2. Duplicate ClassId: $cid"
$check2Ok = $false
}
$foundClassIds[$cid] = $true
if (-not $objectId -or -not $objectId.InnerText) {
Report-Error "2. ContainedObject missing ObjectId for ClassId $cid"
$check2Ok = $false
} elseif ($objectId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid ObjectId '$($objectId.InnerText)' for ClassId $cid"
$check2Ok = $false
}
}
$missingIds = @($validClassIds | Where-Object { -not $foundClassIds.ContainsKey($_) })
if ($missingIds.Count -gt 0) {
Report-Warn "2. Missing ClassIds: $($missingIds.Count) of 7"
}
if ($check2Ok) {
Report-OK "2. InternalInfo: $($contained.Count) ContainedObject, all ClassIds valid"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 3: Extension-specific properties ---
if (-not $propsNode) {
Report-Error "3. Properties block missing"
} else {
$check3Ok = $true
# ObjectBelonging = Adopted
$obNode = $propsNode.SelectSingleNode("md:ObjectBelonging", $ns)
if (-not $obNode -or $obNode.InnerText -ne "Adopted") {
Report-Error "3. ObjectBelonging must be 'Adopted', got '$($obNode.InnerText)'"
$check3Ok = $false
}
# Name
if (-not $nameNode -or -not $nameNode.InnerText) {
Report-Error "3. Name is missing or empty"
$check3Ok = $false
} else {
$nameVal = $nameNode.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "3. Name '$nameVal' is not a valid 1C identifier"
$check3Ok = $false
}
}
# ConfigurationExtensionPurpose
$purposeNode = $propsNode.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns)
$validPurposes = @("Patch","Customization","AddOn")
if (-not $purposeNode -or -not $purposeNode.InnerText) {
Report-Error "3. ConfigurationExtensionPurpose is missing"
$check3Ok = $false
} elseif ($validPurposes -notcontains $purposeNode.InnerText) {
Report-Error "3. ConfigurationExtensionPurpose '$($purposeNode.InnerText)' invalid (expected: Patch, Customization, AddOn)"
$check3Ok = $false
}
# NamePrefix
$prefixNode = $propsNode.SelectSingleNode("md:NamePrefix", $ns)
if (-not $prefixNode -or -not $prefixNode.InnerText) {
Report-Warn "3. NamePrefix is empty"
}
# KeepMappingToExtendedConfigurationObjectsByIDs
$keepMapNode = $propsNode.SelectSingleNode("md:KeepMappingToExtendedConfigurationObjectsByIDs", $ns)
if (-not $keepMapNode) {
Report-Warn "3. KeepMappingToExtendedConfigurationObjectsByIDs is missing"
}
# DefaultLanguage
$defLangNode = $propsNode.SelectSingleNode("md:DefaultLanguage", $ns)
$defLang = if ($defLangNode -and $defLangNode.InnerText) { $defLangNode.InnerText } else { "" }
if ($check3Ok) {
$purposeVal = if ($purposeNode) { $purposeNode.InnerText } else { "?" }
$prefixVal = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "(empty)" }
Report-OK "3. Extension properties: Name=`"$objName`", Purpose=$purposeVal, Prefix=$prefixVal"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 4: Enum property values ---
if ($propsNode) {
$enumChecked = 0
$check4Ok = $true
foreach ($propName in $validEnumValues.Keys) {
$propNode = $propsNode.SelectSingleNode("md:$propName", $ns)
if ($propNode -and $propNode.InnerText) {
$val = $propNode.InnerText
$allowed = $validEnumValues[$propName]
if ($allowed -notcontains $val) {
Report-Error "4. Property '$propName' has invalid value '$val'"
$check4Ok = $false
}
$enumChecked++
}
}
if ($check4Ok) {
Report-OK "4. Property values: $enumChecked enum properties checked"
}
} else {
Report-Warn "4. No Properties block to check"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 5: ChildObjects — valid types, no duplicates, order ---
$childObjNode = $cfgNode.SelectSingleNode("md:ChildObjects", $ns)
if (-not $childObjNode) {
Report-Error "5. ChildObjects block missing"
} else {
$check5Ok = $true
$totalCount = 0
$typeCounts = @{}
$duplicates = @{}
$typeFirstIndex = @{}
$lastTypeOrder = -1
$orderOk = $true
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
$objNameVal = $child.InnerText
$typeIdx = $childObjectTypes.IndexOf($typeName)
if ($typeIdx -lt 0) {
Report-Error "5. Unknown type '$typeName' in ChildObjects"
$check5Ok = $false
} else {
if (-not $typeFirstIndex.ContainsKey($typeName)) {
$typeFirstIndex[$typeName] = $typeIdx
if ($typeIdx -lt $lastTypeOrder) {
Report-Warn "5. Type '$typeName' is out of canonical order (after type at position $lastTypeOrder)"
$orderOk = $false
}
$lastTypeOrder = $typeIdx
}
}
if (-not $typeCounts.ContainsKey($typeName)) { $typeCounts[$typeName] = @{} }
if ($typeCounts[$typeName].ContainsKey($objNameVal)) {
if (-not $duplicates.ContainsKey("$typeName.$objNameVal")) {
Report-Error "5. Duplicate: $typeName.$objNameVal"
$duplicates["$typeName.$objNameVal"] = $true
$check5Ok = $false
}
} else {
$typeCounts[$typeName][$objNameVal] = $true
}
$totalCount++
}
$typeCount = $typeCounts.Count
if ($check5Ok) {
$orderInfo = if ($orderOk) { ", order correct" } else { "" }
Report-OK "5. ChildObjects: $typeCount types, $totalCount objects${orderInfo}"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 6: DefaultLanguage references existing Language in ChildObjects ---
if ($defLang -and $childObjNode) {
$langName = $defLang
if ($langName.StartsWith("Language.")) {
$langName = $langName.Substring(9)
}
$found = $false
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Language" -and $child.InnerText -eq $langName) {
$found = $true; break
}
}
if ($found) {
Report-OK "6. DefaultLanguage `"$defLang`" found in ChildObjects"
} else {
Report-Error "6. DefaultLanguage `"$defLang`" not found in ChildObjects"
}
} else {
if (-not $defLang) {
Report-Warn "6. Cannot check DefaultLanguage (empty)"
} else {
Report-Warn "6. Cannot check DefaultLanguage (no ChildObjects)"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 7: Language files exist ---
if ($childObjNode) {
$langNames = @()
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Language") {
$langNames += $child.InnerText
}
}
if ($langNames.Count -gt 0) {
$existCount = 0
foreach ($ln in $langNames) {
$langFile = Join-Path (Join-Path $configDir "Languages") "$ln.xml"
if (Test-Path $langFile) {
$existCount++
} else {
Report-Warn "7. Language file missing: Languages/$ln.xml"
}
}
if ($existCount -eq $langNames.Count) {
Report-OK "7. Language files: $existCount/$($langNames.Count) exist"
}
} else {
Report-Warn "7. No Language entries in ChildObjects"
}
} else {
Report-Warn "7. Cannot check language files (no ChildObjects)"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 8: Object directories exist ---
if ($childObjNode) {
$dirsToCheck = @{}
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
if ($typeName -eq "Language") { continue }
if ($childTypeDirMap.ContainsKey($typeName)) {
$dirName = $childTypeDirMap[$typeName]
if (-not $dirsToCheck.ContainsKey($dirName)) {
$dirsToCheck[$dirName] = 0
}
$dirsToCheck[$dirName] = $dirsToCheck[$dirName] + 1
}
}
$missingDirs = @()
foreach ($dir in $dirsToCheck.Keys) {
$dirPath = Join-Path $configDir $dir
if (-not (Test-Path $dirPath -PathType Container)) {
$missingDirs += "$dir ($($dirsToCheck[$dir]) objects)"
}
}
if ($missingDirs.Count -eq 0) {
Report-OK "8. Object directories: $($dirsToCheck.Count) directories, all exist"
} else {
foreach ($md in $missingDirs) {
Report-Warn "8. Missing directory: $md"
}
}
} else {
Report-OK "8. Object directories: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 9: Borrowed objects validation ---
if ($childObjNode) {
$borrowedCount = 0
$borrowedOk = 0
$check9Ok = $true
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
$childName = $child.InnerText
if ($typeName -eq "Language") { continue }
if (-not $childTypeDirMap.ContainsKey($typeName)) { continue }
$dirName = $childTypeDirMap[$typeName]
$objFile = Join-Path (Join-Path $configDir $dirName) "$childName.xml"
if (-not (Test-Path $objFile)) { continue }
# Parse object XML
$objDoc = $null
try {
$objDoc = New-Object System.Xml.XmlDocument
$objDoc.PreserveWhitespace = $false
$objDoc.Load($objFile)
} catch {
Report-Warn "9. Cannot parse $dirName/$childName.xml: $($_.Exception.Message)"
continue
}
$objNs = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$objNs.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
# Find the object element (Catalog, Document, etc.)
$objRoot = $objDoc.DocumentElement
$objEl = $null
foreach ($c in $objRoot.ChildNodes) {
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
}
if (-not $objEl) { continue }
$objProps = $objEl.SelectSingleNode("md:Properties", $objNs)
if (-not $objProps) { continue }
$obNode = $objProps.SelectSingleNode("md:ObjectBelonging", $objNs)
if ($obNode -and $obNode.InnerText -eq "Adopted") {
$borrowedCount++
# Check ExtendedConfigurationObject
$extObj = $objProps.SelectSingleNode("md:ExtendedConfigurationObject", $objNs)
if (-not $extObj -or -not $extObj.InnerText) {
Report-Error "9. Borrowed ${typeName}.${childName}: missing ExtendedConfigurationObject"
$check9Ok = $false
} elseif ($extObj.InnerText -notmatch $guidPattern) {
Report-Error "9. Borrowed ${typeName}.${childName}: invalid ExtendedConfigurationObject UUID '$($extObj.InnerText)'"
$check9Ok = $false
} else {
$borrowedOk++
}
}
if ($script:stopped) { break }
}
if ($borrowedCount -eq 0) {
Report-OK "9. Borrowed objects: none found"
} elseif ($check9Ok) {
Report-OK "9. Borrowed objects: $borrowedOk/$borrowedCount validated"
}
}
# --- Final output ---
& $finalize
if ($script:errors -gt 0) {
exit 1
}
exit 0
+9
View File
@@ -30,6 +30,7 @@
| Схема компоновки (СКД) | 4 навыка `/skd-*` | Анализ, генерация из JSON DSL, точечное редактирование, валидация схем компоновки данных | [Подробнее](docs/skd-guide.md) |
| Метаданные конфигурации | 4 навыка `/meta-*` | Создание, анализ, редактирование, валидация объектов метаданных (23 типа) | [Подробнее](docs/meta-guide.md) |
| Корневая конфигурация | 4 навыка `/cf-*` | Создание, анализ, редактирование, валидация корневых файлов конфигурации | [Подробнее](docs/cf-guide.md) |
| Расширения (CFE) | 5 навыков `/cfe-*` | Создание, заимствование, перехват методов, валидация, анализ расширений | [Подробнее](docs/cfe-guide.md) |
| Подсистемы (Subsystem) | 4 навыка `/subsystem-*` | Анализ, создание, редактирование, валидация подсистем конфигурации | — |
| Командный интерфейс (CI) | 2 навыка `/interface-*` | Редактирование и валидация CommandInterface.xml подсистем | — |
| Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — |
@@ -56,6 +57,7 @@
- [Объекты конфигурации](docs/1c-config-objects-spec.md) — XML-формат объектов метаданных конфигурации (23 типа)
- [Подсистемы и командный интерфейс](docs/1c-subsystem-spec.md) — XML-формат подсистем, CommandInterface.xml, секции видимости/размещения/порядка
- [Корневая конфигурация](docs/1c-configuration-spec.md) — XML-формат Configuration.xml, ConfigDumpInfo.xml, Languages/, 44 типа ChildObjects
- [Расширения конфигурации (CFE)](docs/1c-extension-spec.md) — XML-формат выгрузки расширений конфигурации
## Структура репозитория
@@ -99,6 +101,11 @@
├── cf-init/ # Создание пустой конфигурации
├── cf-edit/ # Редактирование конфигурации
├── cf-validate/ # Валидация конфигурации
├── cfe-init/ # Создание расширения
├── cfe-borrow/ # Заимствование объектов
├── cfe-patch-method/ # Перехват методов
├── cfe-validate/ # Валидация расширения
├── cfe-diff/ # Анализ и сравнение
├── subsystem-info/ # Анализ структуры подсистемы
├── subsystem-compile/ # Создание подсистемы из JSON
├── subsystem-edit/ # Редактирование подсистемы
@@ -114,6 +121,7 @@ docs/
├── skd-guide.md # Гайд: схема компоновки данных
├── meta-guide.md # Гайд: объекты метаданных конфигурации
├── cf-guide.md # Гайд: корневые файлы конфигурации
├── cfe-guide.md # Гайд: расширения конфигурации (CFE)
├── 1c-epf-spec.md # Спецификация XML-формата (EPF)
├── 1c-erf-spec.md # Спецификация XML-формата (ERF)
├── 1c-form-spec.md # Спецификация управляемых форм
@@ -128,5 +136,6 @@ docs/
├── 1c-dcs-spec.md # Спецификация СКД (DataCompositionSchema)
├── skd-dsl-spec.md # Спецификация SKD DSL
├── role-dsl-spec.md # Спецификация Role DSL
├── 1c-extension-spec.md # Спецификация расширений конфигурации (CFE)
└── 1c-subsystem-spec.md # Спецификация подсистем и командного интерфейса
```
+175
View File
@@ -0,0 +1,175 @@
# Расширения конфигурации (CFE)
Навыки группы `/cfe-*` позволяют создавать, заимствовать объекты, перехватывать методы, проверять и анализировать расширения конфигурации 1С.
## Навыки
| Навык | Параметры | Описание |
|-------|-----------|----------|
| `/cfe-init` | `<Name> [-Purpose Patch\|Customization\|AddOn] [-CompatibilityMode]` | Создание расширения (scaffold XML-исходников) |
| `/cfe-borrow` | `-ExtensionPath <path> -ConfigPath <path> -Object "Type.Name"` | Заимствование объектов из конфигурации |
| `/cfe-patch-method` | `-ExtensionPath <path> -ModulePath "Type.Name.Module" -MethodName "X" -InterceptorType Before` | Генерация перехватчика метода |
| `/cfe-validate` | `<ExtensionPath> [-MaxErrors 30]` | Валидация структурной корректности (9 проверок) |
| `/cfe-diff` | `-ExtensionPath <path> -ConfigPath <path> [-Mode A\|B]` | Анализ расширения и проверка переноса |
## Рабочий цикл
```
cf-info (версия, совместимость)
/cfe-init → scaffold расширения
/cfe-borrow → заимствование объектов из конфигурации
/cfe-patch-method → перехват методов
/cfe-validate → проверка корректности
/cfe-diff Mode A → обзор изменений
```
## Типичные сценарии
### Создание расширения для исправления бага
```
> Создай расширение для исправления бага в справочнике Контрагенты,
конфигурация ERP в C:\cfsrc\erp
```
Claude выполнит:
1. `/cf-info C:\cfsrc\erp -Mode brief` — получить версию и режим совместимости
2. `/cfe-init` — создать расширение с нужным `CompatibilityMode`
3. `/cfe-borrow` — заимствовать `Catalog.Контрагенты`
4. `/cfe-patch-method` — создать перехватчик нужного метода
5. `/cfe-validate` — проверить результат
### Анализ существующего расширения
```
> Покажи что изменено в расширении src/
```
Claude вызовет `/cfe-diff -Mode A` и покажет: заимствованные объекты, перехватчики, собственные объекты.
### Проверка переноса изменений
```
> Проверь, все ли изменения из расширения перенесены в конфигурацию
```
Claude вызовет `/cfe-diff -Mode B` — найдёт блоки `#Вставка` и проверит их наличие в конфигурации.
## cfe-init — создание расширения
Параметры:
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `Name` | Имя расширения (обязат.) | — |
| `Synonym` | Синоним | = Name |
| `NamePrefix` | Префикс собственных объектов | = Name + "_" |
| `OutputDir` | Каталог | `src` |
| `Purpose` | Назначение | `Customization` |
| `Version` | Версия | — |
| `Vendor` | Поставщик | — |
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
| `NoRole` | Без основной роли | false |
Создаёт:
```
<OutputDir>/
├── Configuration.xml # Свойства расширения
├── Languages/
│ └── Русский.xml # Язык (Adopted)
└── Roles/ # Если не -NoRole
└── <Prefix>ОсновнаяРоль.xml
```
Назначение расширения (`Purpose`):
- `Patch` — исправление ошибок (минимальные изменения, только перехватчики)
- `Customization` — доработка (реквизиты, формы, модули)
- `AddOn` — дополнение (полноценный функционал)
## cfe-borrow — заимствование объектов
Заимствует объекты из основной конфигурации в расширение. Создаёт минимальные XML-файлы с `ObjectBelonging=Adopted` и `ExtendedConfigurationObject`.
Формат `-Object`:
- `Catalog.Контрагенты` — справочник
- `CommonModule.РаботаСФайлами` — общий модуль
- `Enum.ВидыОплат` — перечисление
- `Document.Заказ ;; Catalog.Товары` — несколько объектов через `;;`
Поддерживаемые типы: Catalog, Document, Enum, CommonModule, Report, DataProcessor, ExchangePlan, InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task, и другие (44 типа).
## cfe-patch-method — перехват методов
Генерирует `.bsl` файл с декоратором перехвата для заимствованного объекта.
Параметры:
| Параметр | Описание |
|----------|----------|
| `ModulePath` | `Catalog.X.ObjectModule`, `CommonModule.Y`, `Catalog.X.Form.Z` |
| `MethodName` | Имя перехватываемого метода |
| `InterceptorType` | `Before` / `After` / `ModificationAndControl` |
| `Context` | `НаСервере` / `НаКлиенте` / `НаСервереБезКонтекста` |
| `IsFunction` | Добавить `Возврат` |
Типы перехватчиков:
| Тип | Декоратор | Когда использовать |
|-----|-----------|-------------------|
| `Before` | `&Перед` | Выполнить код до вызова оригинального метода |
| `After` | `&После` | Выполнить код после вызова оригинального метода |
| `ModificationAndControl` | `&ИзменениеИКонтроль` | Полная замена тела метода с маркерами `#Вставка`/`#Удаление` |
Пример генерируемого кода (`Before`):
```bsl
&НаСервере
&Перед("ПриЗаписи")
Процедура Расш1_ПриЗаписи()
// TODO: код перед вызовом оригинального метода
КонецПроцедуры
```
## cfe-validate — проверки
| # | Проверка | Уровень |
|---|----------|---------|
| 1 | XML well-formedness, MetaDataObject/Configuration, version | ERROR |
| 2 | InternalInfo: 7 ContainedObject, валидные ClassId | ERROR |
| 3 | Extension properties: ObjectBelonging=Adopted, Name, Purpose, NamePrefix, KeepMapping | ERROR |
| 4 | Enum-значения (4 свойства) | ERROR |
| 5 | ChildObjects: валидные типы, нет дубликатов, порядок | ERROR/WARN |
| 6 | DefaultLanguage ссылается на существующий Language | ERROR |
| 7 | Файлы языков существуют | WARN |
| 8 | Каталоги объектов существуют | WARN |
| 9 | Заимствованные объекты: ObjectBelonging=Adopted, ExtendedConfigurationObject UUID | ERROR/WARN |
## cfe-diff — режимы
### Mode A — обзор расширения
Для каждого объекта показывает:
- `[BORROWED]` — заимствованный: перехватчики, собственные реквизиты/формы
- `[OWN]` — собственный: количество реквизитов, ТЧ, форм
### Mode B — проверка переноса
Для каждого `&ИзменениеИКонтроль` проверяет, перенесены ли блоки `#Вставка` в конфигурацию:
- `[TRANSFERRED]` — код найден в конфигурации
- `[NOT_TRANSFERRED]` — код не найден
- `[NEEDS_REVIEW]` — нет блоков `#Вставка` или модуль конфигурации не найден
## Связь с другими навыками
- `/cf-info` — получение версии и совместимости конфигурации перед `cfe-init`
- `/meta-compile` — создание собственных объектов расширения (реквизиты, ТЧ)
- `/form-compile`, `/form-edit` — создание и модификация форм расширения
- `/cfe-validate` — всегда проверяйте расширение после изменений
## Спецификации
- [1c-extension-spec.md](1c-extension-spec.md) — XML-формат выгрузки расширений конфигурации (CFE)