mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-11 22:13:20 +03:00
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:
@@ -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"
|
||||
|
||||
@@ -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.Контрагенты"
|
||||
```
|
||||
@@ -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
|
||||
@@ -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 ==="
|
||||
}
|
||||
@@ -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 "МоёРасширение"
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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 # Спецификация подсистем и командного интерфейса
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user