Compare commits

..
Author SHA1 Message Date
github-actions[bot] 9d5dad7b05 Auto-build: claude-code (python) from f522d29 2026-08-12 17:57:40 +00:00
3868 changed files with 5173 additions and 245661 deletions
-32
View File
@@ -1,32 +0,0 @@
{
"name": "cc-1c-skills",
"interface": {
"displayName": "1C Skills"
},
"plugins": [
{
"name": "1c-skills",
"source": {
"source": "url",
"url": "https://github.com/Nikolay-Shirokov/cc-1c-skills.git",
"ref": "port-codex"
},
"policy": {
"installation": "AVAILABLE"
},
"category": "Development"
},
{
"name": "1c-skills-py",
"source": {
"source": "url",
"url": "https://github.com/Nikolay-Shirokov/cc-1c-skills.git",
"ref": "port-codex-py"
},
"policy": {
"installation": "AVAILABLE"
},
"category": "Development"
}
]
}
-24
View File
@@ -1,24 +0,0 @@
{
"$schema": "https://json.schemastore.org/claude-code-marketplace-manifest.json",
"name": "cc-1c-skills",
"description": "Маркетплейс навыков для разработки на платформе 1С:Предприятие",
"owner": {
"name": "Nikolay Shirokov"
},
"plugins": [
{
"name": "1c-skills",
"source": "./",
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент."
},
{
"name": "1c-skills-py",
"source": {
"source": "github",
"repo": "Nikolay-Shirokov/cc-1c-skills",
"ref": "port-claude-code-py"
},
"description": "[Python] То же — для Linux/Mac или когда PowerShell недоступен."
}
]
}
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "1c-skills", "name": "1c-skills-py",
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.", "description": "[Python] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент. Linux/Mac или когда PowerShell недоступен.",
"author": { "author": {
"name": "Nikolay Shirokov" "name": "Nikolay Shirokov"
}, },
+1 -1
View File
@@ -24,7 +24,7 @@ allowed-tools:
| `NoValidate` | Пропустить авто-валидацию | | `NoValidate` | Пропустить авто-валидацию |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-edit.ps1" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1' python "${CLAUDE_SKILL_DIR}/scripts/cf-edit.py" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
``` ```
## Операции ## Операции
+1 -1
View File
@@ -23,7 +23,7 @@ allowed-tools:
| `OutFile` | Записать результат в файл (UTF-8 BOM) | | `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-info.ps1" -ConfigPath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/cf-info.py" -ConfigPath "<путь>"
``` ```
## Три режима ## Три режима
+1 -1
View File
@@ -24,7 +24,7 @@ allowed-tools:
| `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) | | `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-init.ps1" -Name "МояКонфигурация" python "${CLAUDE_SKILL_DIR}/scripts/cf-init.py" -Name "МояКонфигурация"
``` ```
## Примеры ## Примеры
+2 -2
View File
@@ -24,6 +24,6 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty" python "${CLAUDE_SKILL_DIR}/scripts/cf-validate.py" -ConfigPath "upload/cfempty"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty/Configuration.xml" python "${CLAUDE_SKILL_DIR}/scripts/cf-validate.py" -ConfigPath "upload/cfempty/Configuration.xml"
``` ```
+9 -7
View File
@@ -70,31 +70,33 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-borrow.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты" python "${CLAUDE_SKILL_DIR}/scripts/cfe-borrow.py" -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты"
``` ```
## Примеры ## Примеры
```powershell ```powershell
# Заимствовать один объект # Заимствовать один объект
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты" ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты"
# Заимствовать форму (автоматически заимствует родительский объект) # Заимствовать форму (автоматически заимствует родительский объект)
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты.Form.ФормаЭлемента" ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты.Form.ФормаЭлемента"
# Несколько объектов за раз # Несколько объектов за раз
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат" ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
# Заимствовать форму с основным реквизитом (реквизиты по DataPath формы) # Заимствовать форму с основным реквизитом (реквизиты по DataPath формы)
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute
# Заимствовать форму с ВСЕМИ реквизитами объекта # Заимствовать форму с ВСЕМИ реквизитами объекта
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All
``` ```
## Верификация ## Верификация
``` ```
/cfe-validate <ExtensionPath> /cfe-validate <ExtensionPath> -ConfigPath <ConfigPath>
``` ```
Конфигурацию-источник передавай и валидатору: заимствованные формы он проверяет по ней.
@@ -1,4 +1,4 @@
# cfe-borrow v1.20 — Borrow objects from configuration into extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) # cfe-borrow v1.25 — Borrow objects from configuration into extension (CFE) (не переносить FoldersOnTop — платформа его не хранит)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][string]$ExtensionPath, [Parameter(Mandatory)][string]$ExtensionPath,
@@ -38,6 +38,34 @@ function Strip-FormBindings {
return $xml return $xml
} }
# Ссылки параметров выбора (<ChoiceParameterLinks>/<xr:Link>) — привязка особого рода: путь лежит
# в <xr:DataPath> и обычным стриппингом не снимается. Когда основной реквизит не заимствован,
# текстовый «Объект.X» в расширении не разрешается («Неверный путь к полю»), поэтому Конфигуратор
# переписывает его в непрозрачную форму «1/0:<uuid реквизита объекта>» — ссылка остаётся рабочей.
# Реквизит, которого в источнике нет, недоступен и по uuid: такую связь вырезаем целиком.
# Путь всегда односегментный (корпусная проверка: 285 из 285 у УТ), глубже — тоже вырезаем.
function Rewrite-ChoiceParameterLinks {
param([string]$xml, $attrUuids)
if ($xml -notmatch '<ChoiceParameterLinks>') { return $xml }
$xml = [regex]::Replace($xml, '(?s)\s*<xr:Link>.*?</xr:Link>', {
param($m)
$link = $m.Value
$dp = [regex]::Match($link, '<xr:DataPath[^>]*>Объект\.([^<]+)</xr:DataPath>')
if (-not $dp.Success) { return $link }
$attrName = $dp.Groups[1].Value
if ($attrUuids.ContainsKey($attrName)) {
return [regex]::Replace($link, '(<xr:DataPath[^>]*>)Объект\.[^<]+(</xr:DataPath>)', "`${1}1/0:$($attrUuids[$attrName])`${2}")
}
return ''
})
# Опустевший контейнер платформе не нужен
$xml = [regex]::Replace($xml, '(?s)\s*<ChoiceParameterLinks>\s*</ChoiceParameterLinks>', '')
return $xml
}
# --- 1. Resolve paths --- # --- 1. Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) { if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath $ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
@@ -209,7 +237,8 @@ $script:generatedTypes = @{
@{ prefix = "AccumulationRegisterRecordKey"; category = "RecordKey" } @{ prefix = "AccumulationRegisterRecordKey"; category = "RecordKey" }
) )
"AccountingRegister" = @( "AccountingRegister" = @(
@{ prefix = "AccountingRegisterRecord"; category = "Record" } @{ prefix = "AccountingRegisterRecord"; category = "Record" }
@{ prefix = "AccountingRegisterExtDimensions"; category = "ExtDimensions" }
@{ prefix = "AccountingRegisterManager"; category = "Manager" } @{ prefix = "AccountingRegisterManager"; category = "Manager" }
@{ prefix = "AccountingRegisterSelection"; category = "Selection" } @{ prefix = "AccountingRegisterSelection"; category = "Selection" }
@{ prefix = "AccountingRegisterList"; category = "List" } @{ prefix = "AccountingRegisterList"; category = "List" }
@@ -223,6 +252,7 @@ $script:generatedTypes = @{
@{ prefix = "CalculationRegisterList"; category = "List" } @{ prefix = "CalculationRegisterList"; category = "List" }
@{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" } @{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" } @{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" }
@{ prefix = "RecalculationsManager"; category = "Recalcs" }
) )
"ChartOfAccounts" = @( "ChartOfAccounts" = @(
@{ prefix = "ChartOfAccountsObject"; category = "Object" } @{ prefix = "ChartOfAccountsObject"; category = "Object" }
@@ -230,12 +260,15 @@ $script:generatedTypes = @{
@{ prefix = "ChartOfAccountsSelection"; category = "Selection" } @{ prefix = "ChartOfAccountsSelection"; category = "Selection" }
@{ prefix = "ChartOfAccountsList"; category = "List" } @{ prefix = "ChartOfAccountsList"; category = "List" }
@{ prefix = "ChartOfAccountsManager"; category = "Manager" } @{ prefix = "ChartOfAccountsManager"; category = "Manager" }
@{ prefix = "ChartOfAccountsExtDimensionTypes"; category = "ExtDimensionTypes" }
@{ prefix = "ChartOfAccountsExtDimensionTypesRow"; category = "ExtDimensionTypesRow" }
) )
"ChartOfCharacteristicTypes" = @( "ChartOfCharacteristicTypes" = @(
@{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" } @{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" }
@{ prefix = "ChartOfCharacteristicTypesRef"; category = "Ref" } @{ prefix = "ChartOfCharacteristicTypesRef"; category = "Ref" }
@{ prefix = "ChartOfCharacteristicTypesSelection"; category = "Selection" } @{ prefix = "ChartOfCharacteristicTypesSelection"; category = "Selection" }
@{ prefix = "ChartOfCharacteristicTypesList"; category = "List" } @{ prefix = "ChartOfCharacteristicTypesList"; category = "List" }
@{ prefix = "Characteristic"; category = "Characteristic" }
@{ prefix = "ChartOfCharacteristicTypesManager"; category = "Manager" } @{ prefix = "ChartOfCharacteristicTypesManager"; category = "Manager" }
) )
"ChartOfCalculationTypes" = @( "ChartOfCalculationTypes" = @(
@@ -245,8 +278,11 @@ $script:generatedTypes = @{
@{ prefix = "ChartOfCalculationTypesList"; category = "List" } @{ prefix = "ChartOfCalculationTypesList"; category = "List" }
@{ prefix = "ChartOfCalculationTypesManager"; category = "Manager" } @{ prefix = "ChartOfCalculationTypesManager"; category = "Manager" }
@{ prefix = "DisplacingCalculationTypes"; category = "DisplacingCalculationTypes" } @{ prefix = "DisplacingCalculationTypes"; category = "DisplacingCalculationTypes" }
@{ prefix = "DisplacingCalculationTypesRow"; category = "DisplacingCalculationTypesRow" }
@{ prefix = "BaseCalculationTypes"; category = "BaseCalculationTypes" } @{ prefix = "BaseCalculationTypes"; category = "BaseCalculationTypes" }
@{ prefix = "BaseCalculationTypesRow"; category = "BaseCalculationTypesRow" }
@{ prefix = "LeadingCalculationTypes"; category = "LeadingCalculationTypes" } @{ prefix = "LeadingCalculationTypes"; category = "LeadingCalculationTypes" }
@{ prefix = "LeadingCalculationTypesRow"; category = "LeadingCalculationTypesRow" }
) )
"BusinessProcess" = @( "BusinessProcess" = @(
@{ prefix = "BusinessProcessObject"; category = "Object" } @{ prefix = "BusinessProcessObject"; category = "Object" }
@@ -254,6 +290,7 @@ $script:generatedTypes = @{
@{ prefix = "BusinessProcessSelection"; category = "Selection" } @{ prefix = "BusinessProcessSelection"; category = "Selection" }
@{ prefix = "BusinessProcessList"; category = "List" } @{ prefix = "BusinessProcessList"; category = "List" }
@{ prefix = "BusinessProcessManager"; category = "Manager" } @{ prefix = "BusinessProcessManager"; category = "Manager" }
@{ prefix = "BusinessProcessRoutePointRef"; category = "RoutePointRef" }
) )
"Task" = @( "Task" = @(
@{ prefix = "TaskObject"; category = "Object" } @{ prefix = "TaskObject"; category = "Object" }
@@ -440,6 +477,37 @@ if ($BorrowMainAttribute) {
} }
# --- 10. Helper: read source object XML --- # --- 10. Helper: read source object XML ---
# Имена реквизитов исходного объекта → uuid. Нужны для непрозрачной формы пути в ссылках
# параметров выбора (см. Rewrite-ChoiceParameterLinks).
function Get-SourceAttributeUuids {
param([string]$typeName, [string]$objName)
$result = @{}
$dirName = $childTypeDirMap[$typeName]
if (-not $dirName) { return $result }
$srcFile = Join-Path (Join-Path $cfgDir $dirName) "${objName}.xml"
if (-not (Test-Path $srcFile)) { return $result }
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $false
$doc.Load($srcFile)
$objEl = $null
foreach ($c in $doc.DocumentElement.ChildNodes) {
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
}
if (-not $objEl) { return $result }
$childObjects = $objEl.SelectSingleNode("*[local-name()='ChildObjects']")
if (-not $childObjects) { return $result }
foreach ($child in $childObjects.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -notin @('Attribute','TabularSection')) { continue }
$uuid = $child.GetAttribute("uuid")
$nameNode = $child.SelectSingleNode("*[local-name()='Properties']/*[local-name()='Name']")
if ($uuid -and $nameNode) { $result[$nameNode.InnerText.Trim()] = $uuid }
}
return $result
}
function Read-SourceObject { function Read-SourceObject {
param([string]$typeName, [string]$objName) param([string]$typeName, [string]$objName)
@@ -643,6 +711,11 @@ function Borrow-Form {
# Get OuterXml and strip redundant namespace redeclarations (they're on root <Form>) # Get OuterXml and strip redundant namespace redeclarations (they're on root <Form>)
$nsStripPattern = '\s+xmlns(?::\w+)?="[^"]*"' $nsStripPattern = '\s+xmlns(?::\w+)?="[^"]*"'
# uuid реквизитов объекта — только для формы без заимствованного основного реквизита:
# там ссылки параметров выбора переводятся на непрозрачную форму пути
$srcAttrUuids = @{}
if (-not $BorrowMainAttr) { $srcAttrUuids = Get-SourceAttributeUuids $typeName $objName }
# AutoCommandBar: keep ChildItems (buttons with CommandName→0), Autofill→false # AutoCommandBar: keep ChildItems (buttons with CommandName→0), Autofill→false
$autoCmdXml = "" $autoCmdXml = ""
if ($srcAutoCmd) { if ($srcAutoCmd) {
@@ -654,6 +727,7 @@ function Borrow-Form {
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '') $autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '')
# Strip data-binding tags whose root attribute isn't borrowed # Strip data-binding tags whose root attribute isn't borrowed
$autoCmdXml = Strip-FormBindings $autoCmdXml ([bool]$BorrowMainAttr) $autoCmdXml = Strip-FormBindings $autoCmdXml ([bool]$BorrowMainAttr)
if (-not $BorrowMainAttr) { $autoCmdXml = Rewrite-ChoiceParameterLinks $autoCmdXml $srcAttrUuids }
} }
# ChildItems: copy full tree, clean up base-config references # ChildItems: copy full tree, clean up base-config references
@@ -666,6 +740,7 @@ function Borrow-Form {
# Strip data-binding tags whose root attribute isn't borrowed # Strip data-binding tags whose root attribute isn't borrowed
# (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*/RowPicture*) # (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*/RowPicture*)
$childItemsXml = Strip-FormBindings $childItemsXml ([bool]$BorrowMainAttr) $childItemsXml = Strip-FormBindings $childItemsXml ([bool]$BorrowMainAttr)
if (-not $BorrowMainAttr) { $childItemsXml = Rewrite-ChoiceParameterLinks $childItemsXml $srcAttrUuids }
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension) # Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '') $childItemsXml = [regex]::Replace($childItemsXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '')
# Strip TypeLink blocks with human-readable DataPath (Items.XXX — can't convert to UUID) # Strip TypeLink blocks with human-readable DataPath (Items.XXX — can't convert to UUID)
@@ -883,6 +958,12 @@ function Borrow-Form {
$formXmlSb.Append("`t$childItemsXml") | Out-Null $formXmlSb.Append("`t$childItemsXml") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null $formXmlSb.Append("`r`n") | Out-Null
} }
# Секции основного реквизита исходной формы (<UseAlways>, <Columns>) — их нельзя терять
$mainAttrExtra = @()
if ($BorrowMainAttr) {
$mainAttrExtra = @(Get-MainAttributeExtraXml $srcFormEl $nsStripPattern)
}
# Attributes: empty or with MainAttribute when BorrowMainAttr # Attributes: empty or with MainAttribute when BorrowMainAttr
if ($BorrowMainAttr) { if ($BorrowMainAttr) {
$objTypePrefix = "" $objTypePrefix = ""
@@ -894,6 +975,9 @@ function Borrow-Form {
$formXmlSb.Append("`t`t`t<Type><v8:Type>${mainAttrType}</v8:Type></Type>`r`n") | Out-Null $formXmlSb.Append("`t`t`t<Type><v8:Type>${mainAttrType}</v8:Type></Type>`r`n") | Out-Null
$formXmlSb.Append("`t`t`t<MainAttribute>true</MainAttribute>`r`n") | Out-Null $formXmlSb.Append("`t`t`t<MainAttribute>true</MainAttribute>`r`n") | Out-Null
$formXmlSb.Append("`t`t`t<SavedData>true</SavedData>`r`n") | Out-Null $formXmlSb.Append("`t`t`t<SavedData>true</SavedData>`r`n") | Out-Null
foreach ($extraXml in $mainAttrExtra) {
$formXmlSb.Append("`t`t`t$extraXml`r`n") | Out-Null
}
$formXmlSb.Append("`t`t</Attribute>`r`n") | Out-Null $formXmlSb.Append("`t`t</Attribute>`r`n") | Out-Null
$formXmlSb.Append("`t</Attributes>") | Out-Null $formXmlSb.Append("`t</Attributes>") | Out-Null
} else { } else {
@@ -934,6 +1018,15 @@ function Borrow-Form {
$formXmlSb.Append("`t`t`t`t<Type><v8:Type>${mainAttrType}</v8:Type></Type>`r`n") | Out-Null $formXmlSb.Append("`t`t`t`t<Type><v8:Type>${mainAttrType}</v8:Type></Type>`r`n") | Out-Null
$formXmlSb.Append("`t`t`t`t<MainAttribute>true</MainAttribute>`r`n") | Out-Null $formXmlSb.Append("`t`t`t`t<MainAttribute>true</MainAttribute>`r`n") | Out-Null
$formXmlSb.Append("`t`t`t`t<SavedData>true</SavedData>`r`n") | Out-Null $formXmlSb.Append("`t`t`t`t<SavedData>true</SavedData>`r`n") | Out-Null
foreach ($extraXml in $mainAttrExtra) {
# В BaseForm та же секция на уровень глубже — приём переиндентации тот же, что у ChildItems
$exLines = $extraXml -split "`r?`n"
for ($li = 0; $li -lt $exLines.Count; $li++) {
if ($li -eq 0) { $formXmlSb.Append("`t`t`t`t$($exLines[$li])") | Out-Null }
else { $formXmlSb.Append("`t$($exLines[$li])") | Out-Null }
$formXmlSb.Append("`r`n") | Out-Null
}
}
$formXmlSb.Append("`t`t`t</Attribute>`r`n") | Out-Null $formXmlSb.Append("`t`t`t</Attribute>`r`n") | Out-Null
$formXmlSb.Append("`t`t</Attributes>") | Out-Null $formXmlSb.Append("`t`t</Attributes>") | Out-Null
} else { } else {
@@ -1120,6 +1213,24 @@ function Build-InternalInfoXml {
} }
# --- 11b. Collect DataPath references from source Form.xml --- # --- 11b. Collect DataPath references from source Form.xml ---
# --- 11b1. Секции основного реквизита исходной формы, кроме Type/MainAttribute/SavedData ---
# Это <UseAlways> и <Columns> (доп. колонки табличных частей, объявленные прямо в форме). Их
# переносит Конфигуратор, и без них платформа отвергает форму: «Неверный путь к данным» на
# колонке, которой у объекта нет (Объект.Товары.Артикул). Возвращает список OuterXml без xmlns.
function Get-MainAttributeExtraXml {
param($formEl, [string]$nsStripPattern)
$result = @()
$mainAttr = $formEl.SelectSingleNode("*[local-name()='Attributes']/*[local-name()='Attribute'][*[local-name()='MainAttribute']='true']")
if (-not $mainAttr) { return $result }
foreach ($child in $mainAttr.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -in @('Type','MainAttribute','SavedData')) { continue }
$result += [regex]::Replace($child.OuterXml, $nsStripPattern, '')
}
return $result
}
function Collect-FormDataPaths { function Collect-FormDataPaths {
param([string]$formXmlPath) param([string]$formXmlPath)
@@ -1164,6 +1275,16 @@ function Collect-FormDataPaths {
} }
} }
# Also scan <AdditionalColumns table="Объект.X"> — доп. колонки табличной части, объявленные в
# самой форме (напр. Объект.Товары.Артикул). Такая ТЧ может больше нигде на форме не встречаться,
# и без её заимствования платформа отвергает форму: «Неверный путь к данным».
$acMatches = [regex]::Matches($content, '<AdditionalColumns table="Объект\.(\w+)"')
foreach ($m in $acMatches) {
$seg0 = $m.Groups[1].Value
if ($script:standardFields -contains $seg0) { continue }
$firstLevel[$seg0] = $true
}
# Deduplicate deep paths # Deduplicate deep paths
$seen = @{} $seen = @{}
$uniqueDeep = @() $uniqueDeep = @()
@@ -1276,8 +1397,16 @@ function Resolve-SourceAttributes {
$extraProps = [ordered]@{} $extraProps = [ordered]@{}
$propsNode = $srcEl.SelectSingleNode("md:Properties", $srcNs) $propsNode = $srcEl.SelectSingleNode("md:Properties", $srcNs)
if ($propsNode) { if ($propsNode) {
$propsToExtract = @("Hierarchical","FoldersOnTop","CodeLength","DescriptionLength","CodeType","CodeAllowedLength", # NumberPeriodicity сюда НЕ входит: платформа считает его модификацией настроек нумерации и
"NumberType","NumberLength","NumberAllowedLength","NumberPeriodicity") # тогда требует объявить ещё и <Numerator/>, иначе /UpdateDBCfg падает — «отключать
# контролируемость свойства "Нумератор" недопустимо». Конфигуратор его не переносит
# (эталон заимствования документа: NumberType/NumberLength/NumberAllowedLength и всё).
# Загрузку это не ломает, ошибка вылезает только на обновлении конфигурации БД.
# FoldersOnTop сюда НЕ входит: платформа его у заимствованной оболочки не хранит — при
# загрузке молча выбрасывает (проверено раундтрипом: записали, выгрузили обратно, свойства
# нет). Конфигуратор его тоже не переносит. Остальные из списка сохраняются.
$propsToExtract = @("Hierarchical","CodeLength","DescriptionLength","CodeType","CodeAllowedLength",
"NumberType","NumberLength","NumberAllowedLength")
foreach ($pName in $propsToExtract) { foreach ($pName in $propsToExtract) {
$pNode = $propsNode.SelectSingleNode("md:${pName}", $srcNs) $pNode = $propsNode.SelectSingleNode("md:${pName}", $srcNs)
if ($pNode) { $extraProps[$pName] = $pNode.InnerText } if ($pNode) { $extraProps[$pName] = $pNode.InnerText }
@@ -1580,6 +1709,20 @@ function Borrow-MainAttribute {
foreach ($ts in $srcTS) { foreach ($ts in $srcTS) {
foreach ($tsa in $ts.Attributes) { $allTypeXmls += $tsa.TypeXml } foreach ($tsa in $ts.Attributes) { $allTypeXmls += $tsa.TypeXml }
} }
# Типы из <Columns> основного реквизита формы: колонку мы переносим (Borrow-Form), значит и её
# тип должен быть заимствован — иначе колонка ссылается на DefinedType/справочник, которого в
# расширении нет. Конфигуратор поступает так же (эталон: DefinedTypes/Артикул при заимствовании
# формы заказа поставщику).
$srcFormForCols = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $cfgDir $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
if (Test-Path $srcFormForCols) {
$colsDoc = New-Object System.Xml.XmlDocument
$colsDoc.PreserveWhitespace = $true
$colsDoc.Load($srcFormForCols)
foreach ($extraXml in (Get-MainAttributeExtraXml $colsDoc.DocumentElement '\s+xmlns(?::\w+)?="[^"]*"')) {
if ($extraXml -like "<Columns*") { $allTypeXmls += $extraXml }
}
}
$refTypes = Collect-ReferenceTypes $allTypeXmls $refTypes = Collect-ReferenceTypes $allTypeXmls
Info " Reference types to borrow: $($refTypes.Count)" Info " Reference types to borrow: $($refTypes.Count)"
+165 -6
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cfe-borrow v1.20 — Borrow objects from configuration into extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) # cfe-borrow v1.25 — Borrow objects from configuration into extension (CFE) (не переносить FoldersOnTop — платформа его не хранит)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -58,6 +58,33 @@ def strip_form_bindings(xml, keep_objekt):
return xml return xml
def rewrite_choice_parameter_links(xml, attr_uuids):
"""Ссылки параметров выбора (<ChoiceParameterLinks>/<xr:Link>) — привязка особого рода: путь лежит
в <xr:DataPath> и обычным стриппингом не снимается. Когда основной реквизит не заимствован,
текстовый «Объект.X» в расширении не разрешается («Неверный путь к полю»), поэтому Конфигуратор
переписывает его в непрозрачную форму «1/0:<uuid реквизита объекта>» — ссылка остаётся рабочей.
Реквизит, которого в источнике нет, недоступен и по uuid: такую связь вырезаем целиком.
Путь всегда односегментный (корпусная проверка: 285 из 285 у УТ), глубже — тоже вырезаем."""
if '<ChoiceParameterLinks>' not in xml:
return xml
def repl(m):
link = m.group(0)
dp = re.search(r'<xr:DataPath[^>]*>Объект\.([^<]+)</xr:DataPath>', link)
if not dp:
return link
attr_name = dp.group(1)
if attr_name in attr_uuids:
return re.sub(r'(<xr:DataPath[^>]*>)Объект\.[^<]+(</xr:DataPath>)',
lambda mm: f"{mm.group(1)}1/0:{attr_uuids[attr_name]}{mm.group(2)}", link)
return ''
xml = re.sub(r'\s*<xr:Link>.*?</xr:Link>', repl, xml, flags=re.DOTALL)
# Опустевший контейнер платформе не нужен
xml = re.sub(r'\s*<ChoiceParameterLinks>\s*</ChoiceParameterLinks>', '', xml, flags=re.DOTALL)
return xml
def decode_numeric_entities(s): def decode_numeric_entities(s):
"""lxml emits numeric character refs (&#xNNNN;) for non-ASCII in some self-closed """lxml emits numeric character refs (&#xNNNN;) for non-ASCII in some self-closed
elements where the PowerShell port writes literal characters. Normalize numeric refs elements where the PowerShell port writes literal characters. Normalize numeric refs
@@ -202,6 +229,7 @@ GENERATED_TYPES = {
], ],
"AccountingRegister": [ "AccountingRegister": [
{"prefix": "AccountingRegisterRecord", "category": "Record"}, {"prefix": "AccountingRegisterRecord", "category": "Record"},
{"prefix": "AccountingRegisterExtDimensions", "category": "ExtDimensions"},
{"prefix": "AccountingRegisterManager", "category": "Manager"}, {"prefix": "AccountingRegisterManager", "category": "Manager"},
{"prefix": "AccountingRegisterSelection", "category": "Selection"}, {"prefix": "AccountingRegisterSelection", "category": "Selection"},
{"prefix": "AccountingRegisterList", "category": "List"}, {"prefix": "AccountingRegisterList", "category": "List"},
@@ -215,6 +243,7 @@ GENERATED_TYPES = {
{"prefix": "CalculationRegisterList", "category": "List"}, {"prefix": "CalculationRegisterList", "category": "List"},
{"prefix": "CalculationRegisterRecordSet", "category": "RecordSet"}, {"prefix": "CalculationRegisterRecordSet", "category": "RecordSet"},
{"prefix": "CalculationRegisterRecordKey", "category": "RecordKey"}, {"prefix": "CalculationRegisterRecordKey", "category": "RecordKey"},
{"prefix": "RecalculationsManager", "category": "Recalcs"},
], ],
"ChartOfAccounts": [ "ChartOfAccounts": [
{"prefix": "ChartOfAccountsObject", "category": "Object"}, {"prefix": "ChartOfAccountsObject", "category": "Object"},
@@ -222,12 +251,15 @@ GENERATED_TYPES = {
{"prefix": "ChartOfAccountsSelection", "category": "Selection"}, {"prefix": "ChartOfAccountsSelection", "category": "Selection"},
{"prefix": "ChartOfAccountsList", "category": "List"}, {"prefix": "ChartOfAccountsList", "category": "List"},
{"prefix": "ChartOfAccountsManager", "category": "Manager"}, {"prefix": "ChartOfAccountsManager", "category": "Manager"},
{"prefix": "ChartOfAccountsExtDimensionTypes", "category": "ExtDimensionTypes"},
{"prefix": "ChartOfAccountsExtDimensionTypesRow", "category": "ExtDimensionTypesRow"},
], ],
"ChartOfCharacteristicTypes": [ "ChartOfCharacteristicTypes": [
{"prefix": "ChartOfCharacteristicTypesObject", "category": "Object"}, {"prefix": "ChartOfCharacteristicTypesObject", "category": "Object"},
{"prefix": "ChartOfCharacteristicTypesRef", "category": "Ref"}, {"prefix": "ChartOfCharacteristicTypesRef", "category": "Ref"},
{"prefix": "ChartOfCharacteristicTypesSelection", "category": "Selection"}, {"prefix": "ChartOfCharacteristicTypesSelection", "category": "Selection"},
{"prefix": "ChartOfCharacteristicTypesList", "category": "List"}, {"prefix": "ChartOfCharacteristicTypesList", "category": "List"},
{"prefix": "Characteristic", "category": "Characteristic"},
{"prefix": "ChartOfCharacteristicTypesManager", "category": "Manager"}, {"prefix": "ChartOfCharacteristicTypesManager", "category": "Manager"},
], ],
"ChartOfCalculationTypes": [ "ChartOfCalculationTypes": [
@@ -237,8 +269,11 @@ GENERATED_TYPES = {
{"prefix": "ChartOfCalculationTypesList", "category": "List"}, {"prefix": "ChartOfCalculationTypesList", "category": "List"},
{"prefix": "ChartOfCalculationTypesManager", "category": "Manager"}, {"prefix": "ChartOfCalculationTypesManager", "category": "Manager"},
{"prefix": "DisplacingCalculationTypes", "category": "DisplacingCalculationTypes"}, {"prefix": "DisplacingCalculationTypes", "category": "DisplacingCalculationTypes"},
{"prefix": "DisplacingCalculationTypesRow", "category": "DisplacingCalculationTypesRow"},
{"prefix": "BaseCalculationTypes", "category": "BaseCalculationTypes"}, {"prefix": "BaseCalculationTypes", "category": "BaseCalculationTypes"},
{"prefix": "BaseCalculationTypesRow", "category": "BaseCalculationTypesRow"},
{"prefix": "LeadingCalculationTypes", "category": "LeadingCalculationTypes"}, {"prefix": "LeadingCalculationTypes", "category": "LeadingCalculationTypes"},
{"prefix": "LeadingCalculationTypesRow", "category": "LeadingCalculationTypesRow"},
], ],
"BusinessProcess": [ "BusinessProcess": [
{"prefix": "BusinessProcessObject", "category": "Object"}, {"prefix": "BusinessProcessObject", "category": "Object"},
@@ -246,6 +281,7 @@ GENERATED_TYPES = {
{"prefix": "BusinessProcessSelection", "category": "Selection"}, {"prefix": "BusinessProcessSelection", "category": "Selection"},
{"prefix": "BusinessProcessList", "category": "List"}, {"prefix": "BusinessProcessList", "category": "List"},
{"prefix": "BusinessProcessManager", "category": "Manager"}, {"prefix": "BusinessProcessManager", "category": "Manager"},
{"prefix": "BusinessProcessRoutePointRef", "category": "RoutePointRef"},
], ],
"Task": [ "Task": [
{"prefix": "TaskObject", "category": "Object"}, {"prefix": "TaskObject", "category": "Object"},
@@ -582,6 +618,45 @@ def main():
borrowed_files = [] borrowed_files = []
# --- Helper functions --- # --- Helper functions ---
def get_source_attribute_uuids(type_name, obj_name):
"""Имена реквизитов исходного объекта → uuid. Нужны для непрозрачной формы пути в ссылках
параметров выбора (см. rewrite_choice_parameter_links)."""
result = {}
dir_name = CHILD_TYPE_DIR_MAP.get(type_name)
if not dir_name:
return result
src_file = os.path.join(cfg_dir, dir_name, f"{obj_name}.xml")
if not os.path.isfile(src_file):
return result
tree = etree.parse(src_file, etree.XMLParser(remove_blank_text=True))
obj_el = None
for c in tree.getroot():
if isinstance(c.tag, str):
obj_el = c
break
if obj_el is None:
return result
for child in obj_el:
if not isinstance(child.tag, str) or localname(child) != "ChildObjects":
continue
for sub in child:
if not isinstance(sub.tag, str) or localname(sub) not in ("Attribute", "TabularSection"):
continue
uuid_val = sub.get("uuid")
name_val = None
for props in sub:
if isinstance(props.tag, str) and localname(props) == "Properties":
for prop in props:
if isinstance(prop.tag, str) and localname(prop) == "Name":
name_val = (prop.text or "").strip()
break
break
if uuid_val and name_val:
result[name_val] = uuid_val
break
return result
def read_source_object(type_name, obj_name): def read_source_object(type_name, obj_name):
dir_name = CHILD_TYPE_DIR_MAP.get(type_name) dir_name = CHILD_TYPE_DIR_MAP.get(type_name)
if not dir_name: if not dir_name:
@@ -806,6 +881,39 @@ def main():
save_xml_bom(obj_tree, obj_file) save_xml_bom(obj_tree, obj_file)
info(f" Registered form in: {obj_file}") info(f" Registered form in: {obj_file}")
# --- 11b1. Секции основного реквизита исходной формы, кроме Type/MainAttribute/SavedData ---
# Это <UseAlways> и <Columns> (доп. колонки табличных частей, объявленные прямо в форме). Их
# переносит Конфигуратор, и без них платформа отвергает форму: «Неверный путь к данным» на
# колонке, которой у объекта нет (Объект.Товары.Артикул). Возвращает список XML без xmlns.
def get_main_attribute_extra_xml(form_el, ns_strip_pattern):
result = []
main_attr = None
for child in form_el:
if not isinstance(child.tag, str) or localname(child) != "Attributes":
continue
for attr in child:
if not isinstance(attr.tag, str) or localname(attr) != "Attribute":
continue
for sub in attr:
if isinstance(sub.tag, str) and localname(sub) == "MainAttribute" and (sub.text or "").strip() == "true":
main_attr = attr
break
if main_attr is not None:
break
break
if main_attr is None:
return result
for sub in main_attr:
if not isinstance(sub.tag, str):
continue
if localname(sub) in ("Type", "MainAttribute", "SavedData"):
continue
# with_tail=False: хвостовой пробельный узел — часть родителя, а не секции; иначе в
# вывод попадают пустые строки, которых нет у PS (OuterXml хвост не включает).
xml = decode_numeric_entities(etree.tostring(sub, encoding="unicode", with_tail=False))
result.append(ns_strip_pattern.sub("", xml))
return result
# --- 11b. Collect DataPath references from source Form.xml --- # --- 11b. Collect DataPath references from source Form.xml ---
def collect_form_data_paths(form_xml_path): def collect_form_data_paths(form_xml_path):
with open(form_xml_path, "r", encoding="utf-8-sig") as fh: with open(form_xml_path, "r", encoding="utf-8-sig") as fh:
@@ -847,6 +955,15 @@ def main():
seg2 = segments[2] if len(segments) >= 3 else None seg2 = segments[2] if len(segments) >= 3 else None
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2}) deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2})
# Also scan <AdditionalColumns table="Объект.X"> — доп. колонки табличной части, объявленные в
# самой форме (напр. Объект.Товары.Артикул). Такая ТЧ может больше нигде на форме не встречаться,
# и без её заимствования платформа отвергает форму: «Неверный путь к данным».
for m in re.finditer(r'<AdditionalColumns table="Объект\.(\w+)"', content):
seg0 = m.group(1)
if seg0 in STANDARD_FIELDS:
continue
first_level[seg0] = True
# Deduplicate deep paths # Deduplicate deep paths
seen = set() seen = set()
unique_deep = [] unique_deep = []
@@ -969,10 +1086,18 @@ def main():
extra_props = {} extra_props = {}
props_node = src_el.find(f"{{{MD_NS}}}Properties") props_node = src_el.find(f"{{{MD_NS}}}Properties")
if props_node is not None: if props_node is not None:
# NumberPeriodicity сюда НЕ входит: платформа считает его модификацией настроек нумерации и
# тогда требует объявить ещё и <Numerator/>, иначе /UpdateDBCfg падает — «отключать
# контролируемость свойства "Нумератор" недопустимо». Конфигуратор его не переносит
# (эталон заимствования документа: NumberType/NumberLength/NumberAllowedLength и всё).
# Загрузку это не ломает, ошибка вылезает только на обновлении конфигурации БД.
# FoldersOnTop сюда НЕ входит: платформа его у заимствованной оболочки не хранит — при
# загрузке молча выбрасывает (проверено раундтрипом: записали, выгрузили обратно, свойства
# нет). Конфигуратор его тоже не переносит. Остальные из списка сохраняются.
props_to_extract = [ props_to_extract = [
"Hierarchical", "FoldersOnTop", "CodeLength", "DescriptionLength", "Hierarchical", "CodeLength", "DescriptionLength",
"CodeType", "CodeAllowedLength", "NumberType", "NumberLength", "CodeType", "CodeAllowedLength", "NumberType", "NumberLength",
"NumberAllowedLength", "NumberPeriodicity", "NumberAllowedLength",
] ]
for p_name in props_to_extract: for p_name in props_to_extract:
p_node = props_node.find(f"{{{MD_NS}}}{p_name}") p_node = props_node.find(f"{{{MD_NS}}}{p_name}")
@@ -1185,6 +1310,18 @@ def main():
for ts in src_ts: for ts in src_ts:
for tsa in ts["Attributes"]: for tsa in ts["Attributes"]:
all_type_xmls.append(tsa["TypeXml"]) all_type_xmls.append(tsa["TypeXml"])
# Типы из <Columns> основного реквизита формы: колонку мы переносим (borrow_form), значит и её
# тип должен быть заимствован — иначе колонка ссылается на DefinedType/справочник, которого в
# расширении нет. Конфигуратор поступает так же (эталон: DefinedTypes/Артикул при заимствовании
# формы заказа поставщику).
src_form_for_cols = os.path.join(cfg_dir, dir_name, obj_name, "Forms", form_name, "Ext", "Form.xml")
if os.path.isfile(src_form_for_cols):
cols_tree = etree.parse(src_form_for_cols)
cols_ns_strip = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
for extra_xml in get_main_attribute_extra_xml(cols_tree.getroot(), cols_ns_strip):
if extra_xml.startswith("<Columns"):
all_type_xmls.append(extra_xml)
ref_types = collect_reference_types(all_type_xmls) ref_types = collect_reference_types(all_type_xmls)
info(f" Reference types to borrow: {len(ref_types)}") info(f" Reference types to borrow: {len(ref_types)}")
@@ -1379,14 +1516,20 @@ def main():
continue continue
if not reached_visual: if not reached_visual:
# Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.) # Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.)
form_props.append(decode_numeric_entities(etree.tostring(fc, encoding="unicode"))) # with_tail=False — хвостовой пробел принадлежит родителю; с ним в вывод попадали
# пустые строки, которых нет у PS-порта (OuterXml хвост не включает).
form_props.append(decode_numeric_entities(etree.tostring(fc, encoding="unicode", with_tail=False)))
ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"') ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
# uuid реквизитов объекта — только для формы без заимствованного основного реквизита:
# там ссылки параметров выбора переводятся на непрозрачную форму пути
src_attr_uuids = {} if borrow_main_attr else get_source_attribute_uuids(type_name, obj_name)
# AutoCommandBar: keep ChildItems (buttons with CommandName->0), Autofill->false # AutoCommandBar: keep ChildItems (buttons with CommandName->0), Autofill->false
auto_cmd_xml = "" auto_cmd_xml = ""
if src_auto_cmd is not None: if src_auto_cmd is not None:
auto_cmd_xml = decode_numeric_entities(etree.tostring(src_auto_cmd, encoding="unicode")) auto_cmd_xml = decode_numeric_entities(etree.tostring(src_auto_cmd, encoding="unicode", with_tail=False))
auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml) auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml)
auto_cmd_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', auto_cmd_xml) auto_cmd_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', auto_cmd_xml)
auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>') auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>')
@@ -1394,6 +1537,8 @@ def main():
auto_cmd_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', auto_cmd_xml) auto_cmd_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', auto_cmd_xml)
# Strip data-binding tags whose root attribute isn't borrowed # Strip data-binding tags whose root attribute isn't borrowed
auto_cmd_xml = strip_form_bindings(auto_cmd_xml, borrow_main_attr) auto_cmd_xml = strip_form_bindings(auto_cmd_xml, borrow_main_attr)
if not borrow_main_attr:
auto_cmd_xml = rewrite_choice_parameter_links(auto_cmd_xml, src_attr_uuids)
# ChildItems: copy full tree, clean up base-config references # ChildItems: copy full tree, clean up base-config references
child_items_xml = "" child_items_xml = ""
@@ -1404,12 +1549,14 @@ def main():
break break
if src_child_items is not None: if src_child_items is not None:
child_items_xml = decode_numeric_entities(etree.tostring(src_child_items, encoding="unicode")) child_items_xml = decode_numeric_entities(etree.tostring(src_child_items, encoding="unicode", with_tail=False))
child_items_xml = ns_strip_pattern.sub("", child_items_xml) child_items_xml = ns_strip_pattern.sub("", child_items_xml)
# Replace all CommandName values with 0 # Replace all CommandName values with 0
child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml) child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml)
# Strip data-binding tags whose root attribute isn't borrowed # Strip data-binding tags whose root attribute isn't borrowed
child_items_xml = strip_form_bindings(child_items_xml, borrow_main_attr) child_items_xml = strip_form_bindings(child_items_xml, borrow_main_attr)
if not borrow_main_attr:
child_items_xml = rewrite_choice_parameter_links(child_items_xml, src_attr_uuids)
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension) # Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
child_items_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', child_items_xml) child_items_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', child_items_xml)
# Strip TypeLink blocks with human-readable DataPath (Items.XXX) # Strip TypeLink blocks with human-readable DataPath (Items.XXX)
@@ -1593,6 +1740,11 @@ def main():
if child_items_xml: if child_items_xml:
parts.append(f"\t{child_items_xml}\r\n") parts.append(f"\t{child_items_xml}\r\n")
# Секции основного реквизита исходной формы (<UseAlways>, <Columns>) — их нельзя терять
main_attr_extra = []
if borrow_main_attr:
main_attr_extra = get_main_attribute_extra_xml(src_form_el, ns_strip_pattern)
# Attributes: empty or with MainAttribute when borrow_main_attr # Attributes: empty or with MainAttribute when borrow_main_attr
if borrow_main_attr: if borrow_main_attr:
obj_type_prefix = "" obj_type_prefix = ""
@@ -1607,6 +1759,8 @@ def main():
parts.append(f"\t\t\t<Type><v8:Type>{main_attr_type}</v8:Type></Type>\r\n") parts.append(f"\t\t\t<Type><v8:Type>{main_attr_type}</v8:Type></Type>\r\n")
parts.append("\t\t\t<MainAttribute>true</MainAttribute>\r\n") parts.append("\t\t\t<MainAttribute>true</MainAttribute>\r\n")
parts.append("\t\t\t<SavedData>true</SavedData>\r\n") parts.append("\t\t\t<SavedData>true</SavedData>\r\n")
for extra_xml in main_attr_extra:
parts.append(f"\t\t\t{extra_xml}\r\n")
parts.append("\t\t</Attribute>\r\n") parts.append("\t\t</Attribute>\r\n")
parts.append("\t</Attributes>") parts.append("\t</Attributes>")
else: else:
@@ -1643,6 +1797,11 @@ def main():
parts.append(f"\t\t\t\t<Type><v8:Type>{main_attr_type}</v8:Type></Type>\r\n") parts.append(f"\t\t\t\t<Type><v8:Type>{main_attr_type}</v8:Type></Type>\r\n")
parts.append("\t\t\t\t<MainAttribute>true</MainAttribute>\r\n") parts.append("\t\t\t\t<MainAttribute>true</MainAttribute>\r\n")
parts.append("\t\t\t\t<SavedData>true</SavedData>\r\n") parts.append("\t\t\t\t<SavedData>true</SavedData>\r\n")
for extra_xml in main_attr_extra:
# В BaseForm та же секция на уровень глубже — приём переиндентации тот же, что у ChildItems
for li, line in enumerate(extra_xml.split("\n")):
parts.append(f"\t\t\t\t{line}" if li == 0 else f"\t{line}")
parts.append("\r\n")
parts.append("\t\t\t</Attribute>\r\n") parts.append("\t\t\t</Attribute>\r\n")
parts.append("\t\t</Attributes>") parts.append("\t\t</Attributes>")
else: else:
+3 -3
View File
@@ -23,7 +23,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A python "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.py" -ExtensionPath src\cfe\extname -ConfigPath src\cf -Mode A
``` ```
## Mode A — обзор расширения ## Mode A — обзор расширения
@@ -50,8 +50,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -Exte
```powershell ```powershell
# Обзор — что изменено в расширении # Обзор — что изменено в расширении
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Mode A
# Проверка переноса — все ли #Вставка перенесены # Проверка переноса — все ли #Вставка перенесены
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode B ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Mode B
``` ```
+8 -8
View File
@@ -33,39 +33,39 @@ allowed-tools:
| `Name` | Имя расширения (обязат.) | — | | `Name` | Имя расширения (обязат.) | — |
| `Synonym` | Синоним | = Name | | `Synonym` | Синоним | = Name |
| `NamePrefix` | Префикс собственных объектов | = Name + "_" | | `NamePrefix` | Префикс собственных объектов | = Name + "_" |
| `OutputDir` | Каталог для создания | `src` | | `OutputDir` | Каталог для создания; клади расширение в свой подкаталог, названный по имени: `src\cfe\<Name>` | `src` |
| `Purpose` | `Patch` (исправление) / `Customization` (доработка) / `AddOn` (дополнение) | `Customization` | | `Purpose` | `Patch` (исправление) / `Customization` (доработка) / `AddOn` (дополнение) | `Customization` |
| `Version` | Версия расширения | — | | `Version` | Версия расширения | — |
| `Vendor` | Поставщик | — | | `Vendor` | Поставщик | — |
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` | | `CompatibilityMode` | Режим совместимости; при заданном `ConfigPath` определяется по базовой конфигурации и этот параметр не нужен | `Version8_3_24` |
| `ConfigPath` | Путь к выгрузке базовой конфигурации (авто-определяет CompatibilityMode и Language UUID) | — | | `ConfigPath` | Путь к выгрузке базовой конфигурации (авто-определяет CompatibilityMode и Language UUID) | — |
| `NoRole` | Без основной роли | false | | `NoRole` | Без основной роли | false |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-init.ps1" -Name "МоёРасширение" python "${CLAUDE_SKILL_DIR}/scripts/cfe-init.py" -Name "МоёРасширение" -OutputDir "src\cfe\МоёРасширение" -ConfigPath "src\cf"
``` ```
## Примеры ## Примеры
```powershell ```powershell
# Расширение для ERP с авто-определением совместимости из базовой конфигурации # Расширение для ERP с авто-определением совместимости из базовой конфигурации
... -Name Расш1 -ConfigPath C:\WS\tasks\cfsrc\erp_8.3.24 -OutputDir src ... -Name Расш1 -ConfigPath src\cf -OutputDir src\cfe\Расш1
# Расширение-исправление с явным режимом совместимости # Расширение-исправление с явным режимом совместимости
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src ... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src\cfe\Расш1
# Расширение-доработка с версией # Расширение-доработка с версией
... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src ... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src\cfe\МоёРасширение
# Без роли, с явным префиксом # Без роли, с явным префиксом
... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src ... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src\cfe\ИсправлениеБага
``` ```
## Верификация ## Верификация
``` ```
/cfe-validate <OutputDir> /cfe-validate <OutputDir> -ConfigPath <ConfigPath>
``` ```
+9 -9
View File
@@ -110,36 +110,36 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.ps1" -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before python "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.py" -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
``` ```
## Примеры ## Примеры
```powershell ```powershell
# Код перед записью # Код перед записью
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
# Перехват После на форме # Перехват После на форме
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After
# Замена функции (ПродолжитьВызов) # Замена функции (ПродолжитьВызов)
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType Instead ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType Instead
# ИзменениеИКонтроль — копия тела для правки маркерами # ИзменениеИКонтроль — копия тела для правки маркерами
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "Document.РеализацияТоваров.ObjectModule" -MethodName "ОбработкаПроведения" -InterceptorType ModificationAndControl ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Document.РеализацияТоваров.ObjectModule" -MethodName "ОбработкаПроведения" -InterceptorType ModificationAndControl
# ModulePath как путь к файлу модуля-источника (без -ConfigPath) # ModulePath как путь к файлу модуля-источника (без -ConfigPath)
... -ExtensionPath src\cfe\ИмяРасширения -ModulePath "src\cf\CommonModules\ОбщийМодуль\Ext\Module.bsl" -MethodName "ПолучитьДанные" -InterceptorType Instead ... -ExtensionPath src\cfe\extname -ModulePath "src\cf\CommonModules\ОбщийМодуль\Ext\Module.bsl" -MethodName "ПолучитьДанные" -InterceptorType Instead
# Проверить все контролируемые методы расширения на дрейф # Проверить все контролируемые методы расширения на дрейф
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -Check ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Check
# Актуализировать дрейфнувшие контролируемые методы пачкой # Актуализировать дрейфнувшие контролируемые методы пачкой
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -Actualize ... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Actualize
``` ```
## Верификация ## Верификация
``` ```
/cfe-validate <ExtensionPath> /cfe-validate <ExtensionPath> -ConfigPath <ConfigPath>
``` ```
+14 -3
View File
@@ -1,7 +1,7 @@
--- ---
name: cfe-validate name: cfe-validate
description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности
argument-hint: <ExtensionPath> [-Detailed] [-MaxErrors 30] argument-hint: <ExtensionPath> [-ConfigPath <ConfigDir>] [-Detailed] [-MaxErrors 30]
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -17,13 +17,24 @@ allowed-tools:
| Параметр | Обяз. | Умолч. | Описание | | Параметр | Обяз. | Умолч. | Описание |
|---------------|:-----:|---------|-------------------------------------------------| |---------------|:-----:|---------|-------------------------------------------------|
| ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения | | ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения |
| ConfigPath | нет | — | Каталог конфигурации, из которой заимствованы объекты |
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) | | Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
| MaxErrors | нет | 30 | Остановиться после N ошибок | | MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл | | OutFile | нет | — | Записать результат в файл |
### ConfigPath
Указывай всегда, когда конфигурация-источник доступна: без неё часть ошибок заимствованных форм не ловится, и расширение может пройти валидацию, а потом быть отвергнутым платформой при загрузке.
Если пользователь не указал путь — определи сам:
1. Прочитай `.v8-project.json` из корня проекта
2. Разреши целевую базу (по имени, ветке или `default`)
3. Возьми её поле `configSrc`
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src" python "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.py" -ExtensionPath "src\cfe\extname"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src/Configuration.xml" python "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.py" -ExtensionPath "src\cfe\extname\Configuration.xml"
python "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.py" -ExtensionPath "src\cfe\extname" -ConfigPath "src\cf"
``` ```
@@ -1,4 +1,4 @@
# cfe-validate v1.7 — Validate 1C configuration extension structure (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) # cfe-validate v1.8 — Validate 1C configuration extension structure (CFE) (полнота GeneratedType, ТЧ из AdditionalColumns, сверка путей с -ConfigPath)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -9,7 +9,11 @@ param(
[int]$MaxErrors = 30, [int]$MaxErrors = 30,
[string]$OutFile [string]$OutFile,
# Конфигурация-источник. Без неё проверки, требующие сравнения с основной конфигурацией,
# пропускаются (о чём сказано в отчёте), остальные работают как раньше.
[string]$ConfigPath
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -144,6 +148,46 @@ $childTypeDirMap = @{
"IntegrationService"="IntegrationServices" "IntegrationService"="IntegrationServices"
} }
# Наборы GeneratedType по типу объекта (эталон — таблица §2.5 спецификации конфигурации).
# Неполный набор в заимствованной оболочке платформа отвергает при загрузке: «отсутствует один
# или более типов объекта <Тип>». Типы, у которых GeneratedType нет вовсе (общие модули,
# подписки, регламентные задания и т.п.), в карте отсутствуют — для них проверка не выполняется.
$generatedTypeCategories = @{
"Catalog" = @("Object","Ref","Selection","List","Manager")
"Document" = @("Object","Ref","Selection","List","Manager")
"Enum" = @("Ref","Manager","List")
"Constant" = @("Manager","ValueManager","ValueKey")
"Report" = @("Object","Manager")
"DataProcessor" = @("Object","Manager")
"ExchangePlan" = @("Object","Ref","Selection","List","Manager")
"Task" = @("Object","Ref","Selection","List","Manager")
"BusinessProcess" = @("Object","Ref","Selection","List","Manager","RoutePointRef")
"ChartOfCharacteristicTypes" = @("Object","Ref","Selection","List","Manager","Characteristic")
"ChartOfAccounts" = @("Object","Ref","Selection","List","Manager","ExtDimensionTypes","ExtDimensionTypesRow")
"ChartOfCalculationTypes" = @("Object","Ref","Selection","List","Manager","DisplacingCalculationTypes","DisplacingCalculationTypesRow","BaseCalculationTypes","BaseCalculationTypesRow","LeadingCalculationTypes","LeadingCalculationTypesRow")
"InformationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","RecordManager")
"AccumulationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey")
"AccountingRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","ExtDimensions")
"CalculationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","Recalcs")
"DocumentJournal" = @("Selection","List","Manager")
"Sequence" = @("Record","Manager","RecordSet")
"FilterCriterion" = @("Manager","List")
"SettingsStorage" = @("Manager")
"IntegrationService" = @("Manager")
"WSReference" = @("Manager")
"DefinedType" = @("DefinedType")
}
# Стандартные реквизиты объектов: в ChildObjects их нет, но пути Объект.<Стандартный> законны.
# Имена зависят от варианта встроенного языка, поэтому держим оба написания.
$script:standardObjectFields = @(
"Code","Description","Ref","Parent","Owner","DeletionMark","Predefined","IsFolder","LineNumber",
"Number","Date","Posted","PredefinedDataName","RegisterRecords","DataVersion","RowsCount",
"Код","Наименование","Ссылка","Родитель","Владелец","ПометкаУдаления","Предопределенный",
"ЭтоГруппа","НомерСтроки","Номер","Дата","Проведен","ИмяПредопределенныхДанных",
"Движения","ВерсияДанных","КоличествоСтрок"
)
# Valid enum values for extension properties # Valid enum values for extension properties
$validEnumValues = @{ $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","Version8_5_1") "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","Version8_5_1")
@@ -537,6 +581,7 @@ if ($script:stopped) { & $finalize; exit 1 }
# --- Check 9: Borrowed objects validation + Check 10: Sub-items --- # --- Check 9: Borrowed objects validation + Check 10: Sub-items ---
$script:enumValuesIndex = @{} $script:enumValuesIndex = @{}
$script:borrowedTSIndex = @{}
$script:formList = @() $script:formList = @()
# Helper: check if sub-item has explicit borrowed metadata # Helper: check if sub-item has explicit borrowed metadata
@@ -640,6 +685,25 @@ if ($childObjNode) {
} else { } else {
$borrowedOk++ $borrowedOk++
} }
# Полнота набора GeneratedType: платформа отвергает оболочку с неполным набором
# («отсутствует один или более типов объекта ChartOfCharacteristicTypes»)
$expectedCats = $generatedTypeCategories[$typeName]
if ($expectedCats) {
$objInfo = $objEl.SelectSingleNode("md:InternalInfo", $objNs)
$foundCats = @{}
if ($objInfo) {
foreach ($gt in $objInfo.SelectNodes("xr:GeneratedType", $objNs)) {
$cat = $gt.GetAttribute("category")
if ($cat) { $foundCats[$cat] = $true }
}
}
$missingCats = @($expectedCats | Where-Object { -not $foundCats.ContainsKey($_) })
if ($missingCats.Count -gt 0) {
Report-Error "9. Borrowed ${typeName}.${childName}: missing GeneratedType categor$(if ($missingCats.Count -eq 1) { 'y' } else { 'ies' }) $($missingCats -join ', ')"
$check9Ok = $false
}
}
} }
# --- Check 10: Sub-items (Attribute, TabularSection, EnumValue, Form) --- # --- Check 10: Sub-items (Attribute, TabularSection, EnumValue, Form) ---
@@ -667,6 +731,12 @@ if ($childObjNode) {
$tsInfo = $subItem.SelectSingleNode("md:InternalInfo", $objNs) $tsInfo = $subItem.SelectSingleNode("md:InternalInfo", $objNs)
$tsName = $subItem.SelectSingleNode("md:Properties/md:Name", $objNs) $tsName = $subItem.SelectSingleNode("md:Properties/md:Name", $objNs)
$tsLabel = if ($tsName) { $tsName.InnerText } else { "?" } $tsLabel = if ($tsName) { $tsName.InnerText } else { "?" }
# Индекс заимствованных ТЧ — по нему Check 12 сверяет <AdditionalColumns table="Объект.X">
if ($tsName) {
$tsKey = "${typeName}.${childName}"
if (-not $script:borrowedTSIndex.ContainsKey($tsKey)) { $script:borrowedTSIndex[$tsKey] = @{} }
$script:borrowedTSIndex[$tsKey][$tsName.InnerText] = $true
}
if (-not $tsInfo) { if (-not $tsInfo) {
Report-Error "10. ${ctx}: TabularSection.${tsLabel} missing InternalInfo" Report-Error "10. ${ctx}: TabularSection.${tsLabel} missing InternalInfo"
$check10Ok = $false $check10Ok = $false
@@ -896,6 +966,28 @@ foreach ($bf in $script:borrowedFormsWithTree) {
} }
} }
# <AdditionalColumns table="Объект.X"> — доп. колонки табличной части, объявленные в самой форме.
# Колонки есть, а самой ТЧ в расширении нет → платформа отвергает загрузку: «Неверный путь к
# данным» плюс «Колонки не могут быть добавлены к реквизиту».
$acTables = @{}
foreach ($m in [regex]::Matches($raw, '<AdditionalColumns table="Объект\.(\w+)"')) {
$acTables[$m.Groups[1].Value] = $true
}
# Соседние проверки этого блока эвристичны (имя стиля добывается регуляркой), поэтому там
# предупреждение. Здесь сигнал точный — имя ТЧ берётся из атрибута, — а последствие жёсткое,
# поэтому ошибка.
if ($acTables.Count -gt 0) {
$ownerKey = ($ctx -split '\.Form\.')[0]
$ownerTS = $script:borrowedTSIndex[$ownerKey]
foreach ($tblName in $acTables.Keys) {
$depCheckCount++
if (-not $ownerTS -or -not $ownerTS.ContainsKey($tblName)) {
Report-Error "12. ${ctx}: <AdditionalColumns table=`"Объект.${tblName}`"> — TabularSection.${tblName} not borrowed in extension"
$check12Ok = $false
}
}
}
foreach ($mi in $missingItems) { foreach ($mi in $missingItems) {
Report-Warn "12. ${ctx}: references ${mi} not borrowed in extension" Report-Warn "12. ${ctx}: references ${mi} not borrowed in extension"
$check12Ok = $false $check12Ok = $false
@@ -931,6 +1023,114 @@ if ($script:borrowedFormsWithTree.Count -eq 0) {
Report-OK "13. TypeLink: clean" Report-OK "13. TypeLink: clean"
} }
# --- Check 14: пути Объект.* заимствованных форм против конфигурации-источника ---
# Требует -ConfigPath: отличить живой путь от висячего можно только по исходному объекту.
# «Объект.Партнер» валиден и без заимствования реквизита (наследуется от базы), а «Объект.Товары.Артикул»
# не разрешится нигде, если Артикул — не реквизит объекта и не колонка из <Columns> самой формы.
# Такой путь платформа отвергает на загрузке: «Неверный путь к данным».
if (-not $script:stopped -and $script:borrowedFormsWithTree.Count -gt 0) {
if (-not $ConfigPath) {
Out-Line "[INFO] 14. Пути Объект.* против конфигурации-источника не проверялись: не задан -ConfigPath"
} else {
$cfgRoot = $ConfigPath
if (-not [System.IO.Path]::IsPathRooted($cfgRoot)) { $cfgRoot = Join-Path (Get-Location).Path $cfgRoot }
if ((Test-Path $cfgRoot) -and -not (Test-Path $cfgRoot -PathType Container)) { $cfgRoot = Split-Path $cfgRoot -Parent }
if (-not (Test-Path (Join-Path $cfgRoot "Configuration.xml"))) {
Report-Warn "14. -ConfigPath '$ConfigPath': Configuration.xml не найден — проверка путей пропущена"
} else {
$check14Ok = $true
$pathCheckCount = 0
foreach ($bf in $script:borrowedFormsWithTree) {
$raw = $bf.RawText
$ctx = $bf.Context
$ownerKey = ($ctx -split '\.Form\.')[0]
$ownerParts = $ownerKey -split '\.', 2
if ($ownerParts.Count -lt 2) { continue }
$ownerType = $ownerParts[0]; $ownerName = $ownerParts[1]
$ownerDir = $childTypeDirMap[$ownerType]
if (-not $ownerDir) { continue }
$srcObjFile = Join-Path (Join-Path $cfgRoot $ownerDir) "${ownerName}.xml"
if (-not (Test-Path $srcObjFile)) {
Report-Warn "14. ${ctx}: объект-источник не найден в конфигурации ($ownerDir/${ownerName}.xml)"
continue
}
# Имена, доступные первым сегментом пути: реквизиты и ТЧ объекта-источника.
# Плюс для каждой ТЧ — её колонки: второй сегмент проверяем по ним (именно там
# и жил дефект — Объект.Товары.Артикул при живой ТЧ Товары).
$srcNames = @{}
$srcTSColumns = @{}
$srcDoc = New-Object System.Xml.XmlDocument
$srcDoc.PreserveWhitespace = $false
$srcDoc.Load($srcObjFile)
$srcObjEl = $null
foreach ($c in $srcDoc.DocumentElement.ChildNodes) {
if ($c.NodeType -eq 'Element') { $srcObjEl = $c; break }
}
$srcChildObjects = if ($srcObjEl) { $srcObjEl.SelectSingleNode("*[local-name()='ChildObjects']") } else { $null }
if ($srcChildObjects) {
foreach ($sub in $srcChildObjects.ChildNodes) {
if ($sub.NodeType -ne 'Element') { continue }
if ($sub.LocalName -notin @('Attribute','TabularSection')) { continue }
$nameNode = $sub.SelectSingleNode("*[local-name()='Properties']/*[local-name()='Name']")
if (-not $nameNode) { continue }
$subName = $nameNode.InnerText.Trim()
$srcNames[$subName] = $true
if ($sub.LocalName -ne 'TabularSection') { continue }
$cols = @{}
foreach ($colName in $sub.SelectNodes("*[local-name()='ChildObjects']/*[local-name()='Attribute']/*[local-name()='Properties']/*[local-name()='Name']")) {
$cols[$colName.InnerText.Trim()] = $true
}
$srcTSColumns[$subName] = $cols
}
}
# Плюс колонки, объявленные в самой форме через <Columns>/<AdditionalColumns table="Объект.X">
foreach ($acm in [regex]::Matches($raw, '(?s)<AdditionalColumns table="Объект\.(\w+)">(.*?)</AdditionalColumns>')) {
$tbl = $acm.Groups[1].Value
if (-not $srcTSColumns.ContainsKey($tbl)) { $srcTSColumns[$tbl] = @{} }
foreach ($cm in [regex]::Matches($acm.Groups[2].Value, '<Column name="(\w+)"')) {
$srcTSColumns[$tbl][$cm.Groups[1].Value] = $true
}
}
$badPaths = @{}
foreach ($m in [regex]::Matches($raw, '<(?:\w+:)?\w*DataPath[^>]*>Объект\.([^<]+)</(?:\w+:)?\w*DataPath>')) {
$segments = $m.Groups[1].Value -split '\.'
$seg0 = $segments[0]
$pathCheckCount++
if ($script:standardObjectFields -contains $seg0) { continue }
if (-not $srcNames.ContainsKey($seg0)) {
$badPaths["Объект.${seg0}"] = "у ${ownerKey} нет такого реквизита или табличной части"
continue
}
# Второй сегмент проверяем только для табличных частей: у ссылочного реквизита
# он ведёт в чужой объект, и это уже другая проверка.
if ($segments.Count -lt 2 -or -not $srcTSColumns.ContainsKey($seg0)) { continue }
$seg1 = $segments[1]
if ($script:standardObjectFields -contains $seg1) { continue }
# Итог колонки — псевдополе платформы: Total<Колонка> при живой колонке законен
if ($seg1 -like "Total*" -and $srcTSColumns[$seg0].ContainsKey($seg1.Substring(5))) { continue }
if (-not $srcTSColumns[$seg0].ContainsKey($seg1)) {
$badPaths["Объект.${seg0}.${seg1}"] = "у табличной части ${seg0} нет колонки ${seg1}, и <Columns> формы её не объявляет"
}
}
foreach ($bad in ($badPaths.Keys | Sort-Object)) {
Report-Error "14. ${ctx}: путь '${bad}' — $($badPaths[$bad])"
$check14Ok = $false
}
}
if ($check14Ok) {
Report-OK "14. Object paths vs source config: $pathCheckCount checked"
}
}
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here --- # --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
$extRootDir = Split-Path $resolvedPath -Parent $extRootDir = Split-Path $resolvedPath -Parent
$ctrlCount = 0 $ctrlCount = 0
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cfe-validate v1.7 — Validate 1C configuration extension XML structure (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) # cfe-validate v1.8 — Validate 1C configuration extension XML structure (CFE) (полнота GeneratedType, ТЧ из AdditionalColumns, сверка путей с -ConfigPath)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects.""" """Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
import sys, os, argparse, re import sys, os, argparse, re
@@ -96,6 +96,46 @@ CHILD_TYPE_DIR_MAP = {
'IntegrationService': 'IntegrationServices', 'IntegrationService': 'IntegrationServices',
} }
# Наборы GeneratedType по типу объекта (эталон — таблица §2.5 спецификации конфигурации).
# Неполный набор в заимствованной оболочке платформа отвергает при загрузке: «отсутствует один
# или более типов объекта <Тип>». Типы, у которых GeneratedType нет вовсе (общие модули,
# подписки, регламентные задания и т.п.), в карте отсутствуют — для них проверка не выполняется.
GENERATED_TYPE_CATEGORIES = {
'Catalog': ['Object', 'Ref', 'Selection', 'List', 'Manager'],
'Document': ['Object', 'Ref', 'Selection', 'List', 'Manager'],
'Enum': ['Ref', 'Manager', 'List'],
'Constant': ['Manager', 'ValueManager', 'ValueKey'],
'Report': ['Object', 'Manager'],
'DataProcessor': ['Object', 'Manager'],
'ExchangePlan': ['Object', 'Ref', 'Selection', 'List', 'Manager'],
'Task': ['Object', 'Ref', 'Selection', 'List', 'Manager'],
'BusinessProcess': ['Object', 'Ref', 'Selection', 'List', 'Manager', 'RoutePointRef'],
'ChartOfCharacteristicTypes': ['Object', 'Ref', 'Selection', 'List', 'Manager', 'Characteristic'],
'ChartOfAccounts': ['Object', 'Ref', 'Selection', 'List', 'Manager', 'ExtDimensionTypes', 'ExtDimensionTypesRow'],
'ChartOfCalculationTypes': ['Object', 'Ref', 'Selection', 'List', 'Manager', 'DisplacingCalculationTypes', 'DisplacingCalculationTypesRow', 'BaseCalculationTypes', 'BaseCalculationTypesRow', 'LeadingCalculationTypes', 'LeadingCalculationTypesRow'],
'InformationRegister': ['Record', 'Manager', 'Selection', 'List', 'RecordSet', 'RecordKey', 'RecordManager'],
'AccumulationRegister': ['Record', 'Manager', 'Selection', 'List', 'RecordSet', 'RecordKey'],
'AccountingRegister': ['Record', 'Manager', 'Selection', 'List', 'RecordSet', 'RecordKey', 'ExtDimensions'],
'CalculationRegister': ['Record', 'Manager', 'Selection', 'List', 'RecordSet', 'RecordKey', 'Recalcs'],
'DocumentJournal': ['Selection', 'List', 'Manager'],
'Sequence': ['Record', 'Manager', 'RecordSet'],
'FilterCriterion': ['Manager', 'List'],
'SettingsStorage': ['Manager'],
'IntegrationService': ['Manager'],
'WSReference': ['Manager'],
'DefinedType': ['DefinedType'],
}
# Стандартные реквизиты объектов: в ChildObjects их нет, но пути Объект.<Стандартный> законны.
# Имена зависят от варианта встроенного языка, поэтому держим оба написания.
STANDARD_OBJECT_FIELDS = {
'Code', 'Description', 'Ref', 'Parent', 'Owner', 'DeletionMark', 'Predefined', 'IsFolder', 'LineNumber',
'Number', 'Date', 'Posted', 'PredefinedDataName', 'RegisterRecords', 'DataVersion', 'RowsCount',
'Код', 'Наименование', 'Ссылка', 'Родитель', 'Владелец', 'ПометкаУдаления', 'Предопределенный',
'ЭтоГруппа', 'НомерСтроки', 'Номер', 'Дата', 'Проведен', 'ИмяПредопределенныхДанных',
'Движения', 'ВерсияДанных', 'КоличествоСтрок',
}
# Valid enum values for extension properties # Valid enum values for extension properties
VALID_ENUM_VALUES = { VALID_ENUM_VALUES = {
'ConfigurationExtensionCompatibilityMode': [ 'ConfigurationExtensionCompatibilityMode': [
@@ -177,11 +217,15 @@ def main():
parser.add_argument('-Detailed', action='store_true') parser.add_argument('-Detailed', action='store_true')
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30) parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
parser.add_argument('-OutFile', dest='OutFile', default='') parser.add_argument('-OutFile', dest='OutFile', default='')
# Конфигурация-источник. Без неё проверки, требующие сравнения с основной конфигурацией,
# пропускаются (о чём сказано в отчёте), остальные работают как раньше.
parser.add_argument('-ConfigPath', dest='ConfigPath', default='')
args = ci_parse_args(parser) args = ci_parse_args(parser)
extension_path = args.ExtensionPath extension_path = args.ExtensionPath
max_errors = args.MaxErrors max_errors = args.MaxErrors
out_file = args.OutFile out_file = args.OutFile
config_path_arg = args.ConfigPath
# --- Resolve path --- # --- Resolve path ---
if not os.path.isabs(extension_path): if not os.path.isabs(extension_path):
@@ -560,6 +604,7 @@ def main():
MD = NS['md'] MD = NS['md']
XR = NS['xr'] XR = NS['xr']
enum_values_index = {} enum_values_index = {}
borrowed_ts_index = {}
form_list = [] form_list = []
def is_borrowed_sub_item(sub_item): def is_borrowed_sub_item(sub_item):
@@ -659,6 +704,23 @@ def main():
else: else:
borrowed_ok_count += 1 borrowed_ok_count += 1
# Полнота набора GeneratedType: платформа отвергает оболочку с неполным набором
# («отсутствует один или более типов объекта ChartOfCharacteristicTypes»)
expected_cats = GENERATED_TYPE_CATEGORIES.get(type_name)
if expected_cats:
obj_info = obj_el.find(f'{{{MD}}}InternalInfo')
found_cats = set()
if obj_info is not None:
for gt in obj_info.findall(f'{{{XR}}}GeneratedType'):
cat = gt.get('category')
if cat:
found_cats.add(cat)
missing_cats = [c for c in expected_cats if c not in found_cats]
if missing_cats:
word = 'category' if len(missing_cats) == 1 else 'categories'
r.error(f"9. Borrowed {type_name}.{child_name}: missing GeneratedType {word} {', '.join(missing_cats)}")
check9_ok = False
# --- Check 10: Sub-items (Attribute, TabularSection, EnumValue, Form) --- # --- Check 10: Sub-items (Attribute, TabularSection, EnumValue, Form) ---
obj_child_objects = obj_el.find(f'{{{MD}}}ChildObjects') obj_child_objects = obj_el.find(f'{{{MD}}}ChildObjects')
if obj_child_objects is not None: if obj_child_objects is not None:
@@ -686,6 +748,9 @@ def main():
ts_info = sub_item.find(f'{{{MD}}}InternalInfo') ts_info = sub_item.find(f'{{{MD}}}InternalInfo')
ts_name_el = sub_item.find(f'{{{MD}}}Properties/{{{MD}}}Name') ts_name_el = sub_item.find(f'{{{MD}}}Properties/{{{MD}}}Name')
ts_label = (ts_name_el.text or '?') if ts_name_el is not None else '?' ts_label = (ts_name_el.text or '?') if ts_name_el is not None else '?'
# Индекс заимствованных ТЧ — по нему Check 12 сверяет <AdditionalColumns table="Объект.X">
if ts_name_el is not None and ts_name_el.text:
borrowed_ts_index.setdefault(f'{type_name}.{child_name}', {})[ts_name_el.text.strip()] = True
if ts_info is None: if ts_info is None:
r.error(f'10. {ctx}: TabularSection.{ts_label} missing InternalInfo') r.error(f'10. {ctx}: TabularSection.{ts_label} missing InternalInfo')
check10_ok = False check10_ok = False
@@ -878,6 +943,22 @@ def main():
elif entry['Enum'] not in enum_values_index or entry['Value'] not in enum_values_index.get(entry['Enum'], {}): elif entry['Enum'] not in enum_values_index or entry['Value'] not in enum_values_index.get(entry['Enum'], {}):
missing_items.append(f"Enum.{entry['Enum']}.EnumValue.{entry['Value']}") missing_items.append(f"Enum.{entry['Enum']}.EnumValue.{entry['Value']}")
# <AdditionalColumns table="Объект.X"> — доп. колонки табличной части, объявленные в самой форме.
# Колонки есть, а самой ТЧ в расширении нет → платформа отвергает загрузку: «Неверный путь к
# данным» плюс «Колонки не могут быть добавлены к реквизиту».
# Соседние проверки этого блока эвристичны (имя стиля добывается регуляркой), поэтому там
# предупреждение. Здесь сигнал точный — имя ТЧ берётся из атрибута, — а последствие жёсткое,
# поэтому ошибка.
ac_tables = set(re.findall(r'<AdditionalColumns table="Объект\.(\w+)"', raw))
if ac_tables:
owner_key = ctx.split('.Form.')[0]
owner_ts = borrowed_ts_index.get(owner_key, {})
for tbl_name in sorted(ac_tables):
dep_check_count += 1
if tbl_name not in owner_ts:
r.error(f'12. {ctx}: <AdditionalColumns table="Объект.{tbl_name}"> — TabularSection.{tbl_name} not borrowed in extension')
check12_ok = False
for mi in missing_items: for mi in missing_items:
r.warn(f'12. {ctx}: references {mi} not borrowed in extension') r.warn(f'12. {ctx}: references {mi} not borrowed in extension')
check12_ok = False check12_ok = False
@@ -909,6 +990,115 @@ def main():
elif check13_ok: elif check13_ok:
r.ok('13. TypeLink: clean') r.ok('13. TypeLink: clean')
# --- Check 14: пути Объект.* заимствованных форм против конфигурации-источника ---
# Требует -ConfigPath: отличить живой путь от висячего можно только по исходному объекту.
# «Объект.Партнер» валиден и без заимствования реквизита (наследуется от базы), а «Объект.Товары.Артикул»
# не разрешится нигде, если Артикул — не колонка ТЧ и не колонка из <Columns> самой формы.
# Такой путь платформа отвергает на загрузке: «Неверный путь к данным».
if not r.stopped and borrowed_forms_with_tree:
if not config_path_arg:
r.out('[INFO] 14. Пути Объект.* против конфигурации-источника не проверялись: не задан -ConfigPath')
else:
cfg_root = config_path_arg
if not os.path.isabs(cfg_root):
cfg_root = os.path.join(os.getcwd(), cfg_root)
if os.path.exists(cfg_root) and not os.path.isdir(cfg_root):
cfg_root = os.path.dirname(cfg_root)
if not os.path.isfile(os.path.join(cfg_root, 'Configuration.xml')):
r.warn(f"14. -ConfigPath '{config_path_arg}': Configuration.xml не найден — проверка путей пропущена")
else:
check14_ok = True
path_check_count = 0
for bf in borrowed_forms_with_tree:
raw = bf['RawText']
ctx = bf['Context']
owner_key = ctx.split('.Form.')[0]
owner_parts = owner_key.split('.', 1)
if len(owner_parts) < 2:
continue
owner_type, owner_name = owner_parts
owner_dir = CHILD_TYPE_DIR_MAP.get(owner_type)
if not owner_dir:
continue
src_obj_file = os.path.join(cfg_root, owner_dir, f'{owner_name}.xml')
if not os.path.isfile(src_obj_file):
r.warn(f'14. {ctx}: объект-источник не найден в конфигурации ({owner_dir}/{owner_name}.xml)')
continue
# Имена, доступные первым сегментом пути: реквизиты и ТЧ объекта-источника.
# Плюс для каждой ТЧ — её колонки: второй сегмент проверяем по ним (именно там
# и жил дефект — Объект.Товары.Артикул при живой ТЧ Товары).
src_names = set()
src_ts_columns = {}
src_tree = etree.parse(src_obj_file, etree.XMLParser(remove_blank_text=True))
src_obj_el = None
for c in src_tree.getroot():
if isinstance(c.tag, str):
src_obj_el = c
break
src_child_objects = src_obj_el.find(f'{{{MD}}}ChildObjects') if src_obj_el is not None else None
if src_child_objects is not None:
for sub in src_child_objects:
if not isinstance(sub.tag, str):
continue
sub_ln = etree.QName(sub.tag).localname
if sub_ln not in ('Attribute', 'TabularSection'):
continue
name_el = sub.find(f'{{{MD}}}Properties/{{{MD}}}Name')
if name_el is None or not name_el.text:
continue
sub_name = name_el.text.strip()
src_names.add(sub_name)
if sub_ln != 'TabularSection':
continue
cols = set()
for col_name in sub.findall(f'{{{MD}}}ChildObjects/{{{MD}}}Attribute/{{{MD}}}Properties/{{{MD}}}Name'):
if col_name.text:
cols.add(col_name.text.strip())
src_ts_columns[sub_name] = cols
# Плюс колонки, объявленные в самой форме через <Columns>/<AdditionalColumns table="Объект.X">
for acm in re.finditer(r'<AdditionalColumns table="Объект\.(\w+)">(.*?)</AdditionalColumns>', raw, re.DOTALL):
tbl = acm.group(1)
cols = src_ts_columns.setdefault(tbl, set())
for cm in re.finditer(r'<Column name="(\w+)"', acm.group(2)):
cols.add(cm.group(1))
bad_paths = {}
for m in re.finditer(r'<(?:\w+:)?\w*DataPath[^>]*>Объект\.([^<]+)</(?:\w+:)?\w*DataPath>', raw):
segments = m.group(1).split('.')
seg0 = segments[0]
path_check_count += 1
if seg0 in STANDARD_OBJECT_FIELDS:
continue
if seg0 not in src_names:
bad_paths[f'Объект.{seg0}'] = f'у {owner_key} нет такого реквизита или табличной части'
continue
# Второй сегмент проверяем только для табличных частей: у ссылочного реквизита
# он ведёт в чужой объект, и это уже другая проверка.
if len(segments) < 2 or seg0 not in src_ts_columns:
continue
seg1 = segments[1]
if seg1 in STANDARD_OBJECT_FIELDS:
continue
# Итог колонки — псевдополе платформы: Total<Колонка> при живой колонке законен
if seg1.startswith('Total') and seg1[5:] in src_ts_columns[seg0]:
continue
if seg1 not in src_ts_columns[seg0]:
bad_paths[f'Объект.{seg0}.{seg1}'] = f'у табличной части {seg0} нет колонки {seg1}, и <Columns> формы её не объявляет'
for bad in sorted(bad_paths):
r.error(f"14. {ctx}: путь '{bad}'{bad_paths[bad]}")
check14_ok = False
if check14_ok:
r.ok(f'14. Object paths vs source config: {path_check_count} checked')
if r.stopped:
r.finalize(out_file)
sys.exit(1)
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here --- # --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
ctrl_count = 0 ctrl_count = 0
for dp, _dn, files in os.walk(config_dir): for dp, _dn, files in os.walk(config_dir):
+5 -5
View File
@@ -31,7 +31,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-create.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -59,14 +59,14 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" <п
```powershell ```powershell
# Создать файловую базу # Создать файловую базу
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" python "${CLAUDE_SKILL_DIR}/scripts/db-create.py" -InfoBasePath "C:\Bases\NewDB"
# Создать серверную базу # Создать серверную базу
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" python "${CLAUDE_SKILL_DIR}/scripts/db-create.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
# Создать из шаблона CF # Создать из шаблона CF
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf" python "${CLAUDE_SKILL_DIR}/scripts/db-create.py" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf"
# Создать и добавить в список баз # Создать и добавить в список баз
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база" python "${CLAUDE_SKILL_DIR}/scripts/db-create.py" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база"
``` ```
+4 -4
View File
@@ -35,7 +35,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -60,11 +60,11 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" <п
```powershell ```powershell
# Выгрузка конфигурации (файловая база) # Выгрузка конфигурации (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf" python "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf" python "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf"
# Выгрузка расширения # Выгрузка расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение" python "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение"
``` ```
+3 -3
View File
@@ -38,7 +38,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -61,10 +61,10 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" <п
```powershell ```powershell
# Выгрузка ИБ (файловая база) # Выгрузка ИБ (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\base.dt" python "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\base.dt"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "base.dt" python "${CLAUDE_SKILL_DIR}/scripts/db-dump-dt.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "base.dt"
``` ```
## Связанные навыки ## Связанные навыки
+6 -6
View File
@@ -37,7 +37,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -76,17 +76,17 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" <
```powershell ```powershell
# Полная выгрузка (файловая база) # Полная выгрузка (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full python "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.py" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
# Инкрементальная выгрузка # Инкрементальная выгрузка
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes python "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes
# Частичная выгрузка # Частичная выгрузка
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ" python "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full python "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full
# Выгрузка расширения # Выгрузка расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение" python "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
``` ```
+4 -4
View File
@@ -36,7 +36,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -65,11 +65,11 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <п
```powershell ```powershell
# Файловая база # Файловая база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf" python "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf" python "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf"
# Загрузка расширения # Загрузка расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение" python "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение"
``` ```
+3 -3
View File
@@ -52,7 +52,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -82,10 +82,10 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" <п
```powershell ```powershell
# Файловая база # Файловая база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\base.dt" python "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\base.dt"
# Серверная база с ускорением загрузки # Серверная база с ускорением загрузки
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "base.dt" -JobsCount 4 python "${CLAUDE_SKILL_DIR}/scripts/db-load-dt.py" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "base.dt" -JobsCount 4
``` ```
## Связанные навыки ## Связанные навыки
+3 -3
View File
@@ -38,7 +38,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-load-git.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -72,8 +72,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <
```powershell ```powershell
# Все незафиксированные изменения # Все незафиксированные изменения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB python "${CLAUDE_SKILL_DIR}/scripts/db-load-git.py" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB
# Из диапазона коммитов # Из диапазона коммитов
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD" python "${CLAUDE_SKILL_DIR}/scripts/db-load-git.py" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD"
``` ```
+5 -5
View File
@@ -38,7 +38,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -90,14 +90,14 @@ Documents/Заказ/Forms/ФормаДокумента.xml
```powershell ```powershell
# Полная загрузка # Полная загрузка
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full python "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.py" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
# Частичная загрузка конкретных файлов # Частичная загрузка конкретных файлов
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl" python "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
# Загрузка расширения # Загрузка расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение" python "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
# Загрузка + обновление БД в одном запуске # Загрузка + обновление БД в одном запуске
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB python "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB
``` ```
+5 -5
View File
@@ -36,7 +36,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-run.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -64,14 +64,14 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" <пар
```powershell ```powershell
# Простой запуск # Простой запуск
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" python "${CLAUDE_SKILL_DIR}/scripts/db-run.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
# Запуск с обработкой # Запуск с обработкой
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf" python "${CLAUDE_SKILL_DIR}/scripts/db-run.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf"
# Открыть по навигационной ссылке # Открыть по навигационной ссылке
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура" python "${CLAUDE_SKILL_DIR}/scripts/db-run.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура"
# Серверная база с параметром запуска # Серверная база с параметром запуска
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление" python "${CLAUDE_SKILL_DIR}/scripts/db-run.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление"
``` ```
+4 -4
View File
@@ -35,7 +35,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/db-update.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -78,11 +78,11 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <п
```powershell ```powershell
# Обычное обновление (файловая база) # Обычное обновление (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" python "${CLAUDE_SKILL_DIR}/scripts/db-update.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
# Динамическое обновление (серверная база) # Динамическое обновление (серверная база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic "+" python "${CLAUDE_SKILL_DIR}/scripts/db-update.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic "+"
# Обновление расширения # Обновление расширения
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение" python "${CLAUDE_SKILL_DIR}/scripts/db-update.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение"
``` ```
+3 -3
View File
@@ -40,7 +40,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/epf-build.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -64,8 +64,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <п
```powershell ```powershell
# Сборка обработки (файловая база) # Сборка обработки (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf" python "${CLAUDE_SKILL_DIR}/scripts/epf-build.py" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf" python "${CLAUDE_SKILL_DIR}/scripts/epf-build.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
``` ```
+3 -3
View File
@@ -39,7 +39,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/epf-dump.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -64,8 +64,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" <па
```powershell ```powershell
# Разборка обработки (файловая база) # Разборка обработки (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src" python "${CLAUDE_SKILL_DIR}/scripts/epf-dump.py" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src" python "${CLAUDE_SKILL_DIR}/scripts/epf-dump.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src"
``` ```
+1 -1
View File
@@ -31,7 +31,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"] python "${CLAUDE_SKILL_DIR}/scripts/init.py" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"]
``` ```
## Дальнейшие шаги ## Дальнейшие шаги
+2 -2
View File
@@ -24,7 +24,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка" python "${CLAUDE_SKILL_DIR}/scripts/epf-validate.py" -ObjectPath "src/МояОбработка"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка/МояОбработка.xml" python "${CLAUDE_SKILL_DIR}/scripts/epf-validate.py" -ObjectPath "src/МояОбработка/МояОбработка.xml"
``` ```
+3 -3
View File
@@ -42,7 +42,7 @@ allowed-tools:
Используй общий скрипт из epf-build: Используй общий скрипт из epf-build:
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -66,8 +66,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-bu
```powershell ```powershell
# Сборка отчёта (файловая база) # Сборка отчёта (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf" python "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.py" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf" python "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
``` ```
+3 -3
View File
@@ -41,7 +41,7 @@ allowed-tools:
Используй общий скрипт из epf-dump: Используй общий скрипт из epf-dump:
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -66,8 +66,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dum
```powershell ```powershell
# Разборка отчёта (файловая база) # Разборка отчёта (файловая база)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src" python "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.py" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src" python "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
``` ```
+1 -1
View File
@@ -32,7 +32,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"] [-WithSKD] python "${CLAUDE_SKILL_DIR}/scripts/init.py" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"] [-WithSKD]
``` ```
## Дальнейшие шаги ## Дальнейшие шаги
+2 -2
View File
@@ -26,7 +26,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МойОтчёт" python "${CLAUDE_SKILL_DIR}/../epf-validate/scripts/epf-validate.py" -ObjectPath "src/МойОтчёт"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МойОтчёт/МойОтчёт.xml" python "${CLAUDE_SKILL_DIR}/../epf-validate/scripts/epf-validate.py" -ObjectPath "src/МойОтчёт/МойОтчёт.xml"
``` ```
+1 -1
View File
@@ -32,7 +32,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-add.ps1" -ObjectPath "<ObjectPath>" -FormName "<FormName>" [-Purpose "<Purpose>"] [-Synonym "<Synonym>"] [-SetDefault] python "${CLAUDE_SKILL_DIR}/scripts/form-add.py" -ObjectPath "<ObjectPath>" -FormName "<FormName>" [-Purpose "<Purpose>"] [-Synonym "<Synonym>"] [-SetDefault]
``` ```
## Purpose — назначение формы ## Purpose — назначение формы
+2 -2
View File
@@ -29,10 +29,10 @@ allowed-tools:
```powershell ```powershell
# Режим JSON DSL # Режим JSON DSL
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-compile.ps1" -JsonPath "<json>" -OutputPath "<Form.xml>" python "${CLAUDE_SKILL_DIR}/scripts/form-compile.py" -JsonPath "<json>" -OutputPath "<Form.xml>"
# Режим from-object (объект и purpose выводятся из OutputPath; Document и Catalog) # Режим from-object (объект и purpose выводятся из OutputPath; Document и Catalog)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-compile.ps1" -FromObject -OutputPath "<.../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml>" python "${CLAUDE_SKILL_DIR}/scripts/form-compile.py" -FromObject -OutputPath "<.../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml>"
``` ```
## JSON DSL — справка ## JSON DSL — справка
@@ -1,4 +1,4 @@
# form-compile v1.191 — Compile 1C managed form from JSON or object metadata (+write_xml_file/write_utf8_bom: общий эталон записи) # form-compile v1.192 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$JsonPath, [string]$JsonPath,
@@ -5926,9 +5926,17 @@ function Emit-Attributes {
} }
if ($hasAddCols) { if ($hasAddCols) {
foreach ($ac in @($attr.additionalColumns)) { foreach ($ac in @($attr.additionalColumns)) {
# Пустой список колонок задаётся ЯВНО (`"columns": []`) — это законная форма,
# платформа так пишет таблицу, у которой доп. колонок нет. А вот отсутствие ключа
# — недосказанность автора: «доп. колонки есть», а какие, не указано. Раньше на
# этом PS падал с «Не удается индексировать в массив NULL» (@($null).Count = 1).
if ($null -eq $ac.PSObject.Properties['columns'] -or $null -eq $ac.columns) {
Write-Error "additionalColumns group for table '$($ac.table)': key 'columns' is missing — list the columns, or pass an empty array for a table without extra columns"
exit 1
}
$acCols = @($ac.columns) $acCols = @($ac.columns)
if ($acCols.Count -eq 0) { if ($acCols.Count -eq 0) {
# Пустая группа доп.колонок (table-ref без колонок) → self-closing (как платформа) # Явно пустая группа → self-closing (как платформа)
X "$inner`t<AdditionalColumns table=`"$($ac.table)`"/>" X "$inner`t<AdditionalColumns table=`"$($ac.table)`"/>"
continue continue
} }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# form-compile v1.191 — Compile 1C managed form from JSON or object metadata (+write_xml_file/write_utf8_bom: общий эталон записи) # form-compile v1.192 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import copy import copy
@@ -5773,9 +5773,18 @@ def emit_attributes(lines, attrs, indent, conditional_appearance=None):
emit_attr_column(lines, col, f'{inner}\t') emit_attr_column(lines, col, f'{inner}\t')
if has_add_cols: if has_add_cols:
for ac in attr['additionalColumns']: for ac in attr['additionalColumns']:
ac_cols = ac.get('columns') or [] # Пустой список колонок задаётся ЯВНО (`"columns": []`) — это законная форма,
# платформа так пишет таблицу, у которой доп. колонок нет. А вот отсутствие ключа
# — недосказанность автора: «доп. колонки есть», а какие, не указано. PS-порт на
# этом падал с «Не удается индексировать в массив NULL» (@($null).Count = 1).
if ac.get('columns') is None:
print(f"additionalColumns group for table '{ac['table']}': key 'columns' is missing "
"— list the columns, or pass an empty array for a table without extra columns",
file=sys.stderr)
sys.exit(1)
ac_cols = ac['columns']
if not ac_cols: if not ac_cols:
# Пустая группа доп.колонок (table-ref без колонок) → self-closing (как платформа) # Явно пустая группа → self-closing (как платформа)
lines.append(f'{inner}\t<AdditionalColumns table="{ac["table"]}"/>') lines.append(f'{inner}\t<AdditionalColumns table="{ac["table"]}"/>')
continue continue
lines.append(f'{inner}\t<AdditionalColumns table="{ac["table"]}">') lines.append(f'{inner}\t<AdditionalColumns table="{ac["table"]}">')
+1 -1
View File
@@ -31,7 +31,7 @@ allowed-tools:
| `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout | | `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-decompile.ps1" -FormPath "<Form.xml>" -OutputPath "<out.json>" python "${CLAUDE_SKILL_DIR}/scripts/form-decompile.py" -FormPath "<Form.xml>" -OutputPath "<out.json>"
``` ```
## Что получаешь ## Что получаешь
@@ -1,4 +1,4 @@
# form-decompile v0.148 — Decompile 1C managed Form.xml to JSON DSL (draft) # form-decompile v0.149 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param( param(
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# form-decompile v0.148 — Decompile 1C managed Form.xml to JSON DSL (draft) # form-decompile v0.149 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
# #
@@ -103,29 +103,29 @@ def _attr(node, name, ns_uri=None):
def convert_string_to_json_literal(s): def convert_string_to_json_literal(s):
if s is None: if s is None:
return 'null' return 'null'
sb = ['"'] out = ['"']
for ch in s: for ch in s:
code = ord(ch) code = ord(ch)
if code == 0x22: if code == 0x22:
sb.append('\\"') out.append('\\"')
elif code == 0x5C: elif code == 0x5C:
sb.append('\\\\') out.append('\\\\')
elif code == 0x08: elif code == 0x08:
sb.append('\\b') out.append('\\b')
elif code == 0x09: elif code == 0x09:
sb.append('\\t') out.append('\\t')
elif code == 0x0A: elif code == 0x0A:
sb.append('\\n') out.append('\\n')
elif code == 0x0C: elif code == 0x0C:
sb.append('\\f') out.append('\\f')
elif code == 0x0D: elif code == 0x0D:
sb.append('\\r') out.append('\\r')
elif code < 0x20: elif code < 0x20:
sb.append('\\u%04x' % code) out.append('\\u%04x' % code)
else: else:
sb.append(ch) out.append(ch)
sb.append('"') out.append('"')
return ''.join(sb) return ''.join(out)
def _num_to_str(obj): def _num_to_str(obj):
+1 -1
View File
@@ -29,7 +29,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-edit.ps1" -FormPath "<путь>" -JsonPath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/form-edit.py" -FormPath "<путь>" -JsonPath "<путь>"
``` ```
## JSON формат ## JSON формат
+1 -1
View File
@@ -15,7 +15,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-info.ps1" -FormPath "<путь к Form.xml>" python "${CLAUDE_SKILL_DIR}/scripts/form-info.py" -FormPath "<путь к Form.xml>"
``` ```
## Параметры ## Параметры
+1 -1
View File
@@ -31,7 +31,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/remove-form.ps1" -ObjectName "<ObjectName>" -FormName "<FormName>" [-SrcDir "<SrcDir>"] python "${CLAUDE_SKILL_DIR}/scripts/remove-form.py" -ObjectName "<ObjectName>" -FormName "<FormName>" [-SrcDir "<SrcDir>"]
``` ```
## Что удаляется ## Что удаляется
+2 -2
View File
@@ -23,7 +23,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-validate.ps1" -FormPath "Catalogs/Номенклатура/Forms/ФормаЭлемента" python "${CLAUDE_SKILL_DIR}/scripts/form-validate.py" -FormPath "Catalogs/Номенклатура/Forms/ФормаЭлемента"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/form-validate.ps1" -FormPath "src/МояОбработка/Forms/Форма/Ext/Form.xml" python "${CLAUDE_SKILL_DIR}/scripts/form-validate.py" -FormPath "src/МояОбработка/Forms/Форма/Ext/Form.xml"
``` ```
@@ -1,4 +1,4 @@
# form-validate v1.10 — Validate 1C managed form # form-validate v1.15 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -56,9 +56,23 @@ try {
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable) $nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$nsMgr.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform") $nsMgr.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
$nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core") $nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$nsMgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$root = $xmlDoc.DocumentElement $root = $xmlDoc.DocumentElement
# Корень автономной внешней обработки/отчёта. Копия общего эталона (семья
# support-guard: is_external_root, авторитет — cf-edit).
function Test-ExternalObjectRoot([string]$xmlPath) {
if (-not (Test-Path $xmlPath)) { return $false }
try {
[xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8
$el = $mx.DocumentElement.FirstChild
while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling }
if ($el) { return @('ExternalDataProcessor','ExternalReport') -contains $el.LocalName }
} catch {}
return $false
}
# --- Detect context: config vs EPF/ERF --- # --- Detect context: config vs EPF/ERF ---
# Walk up from FormPath looking for Configuration.xml → config context # Walk up from FormPath looking for Configuration.xml → config context
# No Configuration.xml → external data processor / report (EPF/ERF) # No Configuration.xml → external data processor / report (EPF/ERF)
@@ -66,13 +80,56 @@ $script:isConfigContext = $false
$walkDir = Split-Path (Resolve-Path $FormPath) -Parent $walkDir = Split-Path (Resolve-Path $FormPath) -Parent
for ($i = 0; $i -lt 15; $i++) { for ($i = 0; $i -lt 15; $i++) {
if (-not $walkDir -or $walkDir -eq (Split-Path $walkDir)) { break } if (-not $walkDir -or $walkDir -eq (Split-Path $walkDir)) { break }
# Порядок проверок тот же, что у Detect-FormatVersion: сначала корень автономной обработки,
# потом Configuration.xml — иначе форма внутри EPF, лежащей в дереве конфигурации, взяла бы
# версию конфигурации.
$extRoot = "$walkDir.xml"
if (-not $script:versionAnchor) {
if (Test-ExternalObjectRoot $extRoot) {
# Ближайший якорь побеждает: автономная обработка остаётся автономной, даже если её
# исходники лежат внутри дерева с Configuration.xml (типовая раскладка проекта:
# src/cf рядом с src/epf). Иначе её собственные External*-типы считались бы ошибкой.
$script:versionAnchor = $extRoot
break
}
}
if (Test-Path (Join-Path $walkDir "Configuration.xml")) { if (Test-Path (Join-Path $walkDir "Configuration.xml")) {
$script:isConfigContext = $true $script:isConfigContext = $true
$script:configXmlPath = Join-Path $walkDir "Configuration.xml"
if (-not $script:versionAnchor) { $script:versionAnchor = $script:configXmlPath }
break break
} }
$walkDir = Split-Path $walkDir $walkDir = Split-Path $walkDir
} }
# Версия формата выгрузки. Копия общего эталона (семья detect_format_version, авторитет —
# form-compile): та же ветка для автономной EPF/ERF, где версию несёт корень обработки.
function Detect-FormatVersion([string]$dir) {
$d = $dir
while ($d) {
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
$extPath = "$d.xml"
if (Test-Path $extPath) {
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
}
$cfgPath = Join-Path $d "Configuration.xml"
if (Test-Path $cfgPath) {
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
# Длину среза берём по СТРОКЕ, а не по размеру файла: размер в БАЙТАХ, Substring считает
# СИМВОЛЫ, и на кириллице байт больше — короткий Configuration.xml ронял навык исключением.
$head = $cfgText.Substring(0, [Math]::Min(2000, $cfgText.Length))
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
}
$parent = Split-Path $d -Parent
if ($parent -eq $d) { break }
$d = $parent
}
return "2.17"
}
# --- Counters --- # --- Counters ---
$errors = 0 $errors = 0
@@ -144,10 +201,15 @@ if (-not $stopped) {
if ($acb) { if ($acb) {
$acbName = $acb.GetAttribute("name") $acbName = $acb.GetAttribute("name")
$acbId = $acb.GetAttribute("id") $acbId = $acb.GetAttribute("id")
# id=-1 — соглашение, а не требование: в корпусе УТ/БП/ERP так у 21 094 форм из 21 097,
# но три формы платформа выгружает с обычным id и грузит их без нареканий. Поэтому
# предупреждение; ошибка — только если id вовсе не число.
if ($acbId -eq "-1") { if ($acbId -eq "-1") {
Report-OK "AutoCommandBar: name='$acbName', id=$acbId" Report-OK "AutoCommandBar: name='$acbName', id=$acbId"
} elseif ($acbId -match '^-?\d+$') {
Report-Warn "AutoCommandBar id='$acbId', usually '-1'"
} else { } else {
Report-Error "AutoCommandBar id='$acbId', expected '-1'" Report-Error "AutoCommandBar id='$acbId' is not a number"
} }
} else { } else {
Report-Error "AutoCommandBar element missing" Report-Error "AutoCommandBar element missing"
@@ -427,11 +489,19 @@ if (-not $stopped) {
$segments = $cleanPath -split '\.' $segments = $cleanPath -split '\.'
$rootAttr = $segments[0] $rootAttr = $segments[0]
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute # Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute.
if ($rootAttr -eq 'Items') { # Разрешаем ЦЕПОЧКОЙ: таблица во вложенной таблице сама привязана через Items.*, и один
# шаг оставлял корнем литерал «Items» — форма платформы объявлялась битой (типовые
# НастройкаПравилОбработкиЗаявокСотрудников в БП и ERP).
$itemsHops = 0
$itemsBroken = $false
while ($rootAttr -eq 'Items') {
$itemsHops++
if ($itemsHops -gt 10) { $itemsBroken = $true; break } # страховка от кольца ссылок
if ($segments.Count -lt 3 -or $segments[2] -ne 'CurrentData') { if ($segments.Count -lt 3 -or $segments[2] -ne 'CurrentData') {
Report-Warn "[$tag] '$elName': $bTag='$dataPath' — unknown Items.* shape, expected Items.<Table>.CurrentData.*" Report-Warn "[$tag] '$elName': $bTag='$dataPath' — unknown Items.* shape, expected Items.<Table>.CurrentData.*"
continue $itemsBroken = $true
break
} }
$tableName = $segments[1] $tableName = $segments[1]
$tableEl = $null $tableEl = $null
@@ -444,17 +514,21 @@ if (-not $stopped) {
if (-not $tableEl) { if (-not $tableEl) {
Report-Error "[$tag] '$elName': $bTag='$dataPath' — table element '$tableName' not found" Report-Error "[$tag] '$elName': $bTag='$dataPath' — table element '$tableName' not found"
$pathErrors++ $pathErrors++
continue $itemsBroken = $true
break
} }
$tableDpNode = $tableEl.Node.SelectSingleNode("f:DataPath", $nsMgr) $tableDpNode = $tableEl.Node.SelectSingleNode("f:DataPath", $nsMgr)
if (-not $tableDpNode -or -not $tableDpNode.InnerText.Trim()) { if (-not $tableDpNode -or -not $tableDpNode.InnerText.Trim()) {
# Table without DataPath — can't resolve further, accept silently # Table without DataPath — can't resolve further, accept silently
continue $itemsBroken = $true
break
} }
$tableDp = $tableDpNode.InnerText.Trim() -replace '\[\d+\]', '' $tableDp = $tableDpNode.InnerText.Trim() -replace '\[\d+\]', ''
if ($tableDp.StartsWith('~')) { $tableDp = $tableDp.Substring(1) } if ($tableDp.StartsWith('~')) { $tableDp = $tableDp.Substring(1) }
$rootAttr = ($tableDp -split '\.')[0] $segments = $tableDp -split '\.'
$rootAttr = $segments[0]
} }
if ($itemsBroken) { continue }
if (-not $attrMap.ContainsKey($rootAttr)) { if (-not $attrMap.ContainsKey($rootAttr)) {
Report-Error "[$tag] '$elName': $bTag='$dataPath' — attribute '$rootAttr' not found" Report-Error "[$tag] '$elName': $bTag='$dataPath' — attribute '$rootAttr' not found"
@@ -569,13 +643,18 @@ if (-not $stopped) {
$actionErrors = 0 $actionErrors = 0
$actionChecked = 0 $actionChecked = 0
# Предупреждение, а не ошибка: <Action> может назначаться в рантайме
# (`Команда.Действие = "Подключаемый_…"` в ПриСозданииНаСервере) — приём типовых конфигураций
# там, где обработчик существует не во всякой сборке. Назначать может и чужой модуль
# (переопределяемый слой, подключаемые команды), так что по одному Form.xml не решить.
# Корпус УТ/БП/ERP: 406 таких команд на 275 формах, произведённых платформой.
foreach ($cmd in $cmdNodes) { foreach ($cmd in $cmdNodes) {
if ($stopped) { break } if ($stopped) { break }
$cmdName = $cmd.GetAttribute("name") $cmdName = $cmd.GetAttribute("name")
$actionNode = $cmd.SelectSingleNode("f:Action", $nsMgr) $actionNode = $cmd.SelectSingleNode("f:Action", $nsMgr)
$actionChecked++ $actionChecked++
if (-not $actionNode -or -not $actionNode.InnerText.Trim()) { if (-not $actionNode -or -not $actionNode.InnerText.Trim()) {
Report-Error "Command '$cmdName': missing or empty Action" Report-Warn "Command '$cmdName': no Action — handler must be assigned at runtime, otherwise the command does nothing"
$actionErrors++ $actionErrors++
} }
} }
@@ -742,6 +821,36 @@ if (-not $stopped -and $isExtension) {
Report-OK "Extension ID ranges: $extAttrCount attr(s), $extCmdCount cmd(s) — all >= 1000000" Report-OK "Extension ID ranges: $extAttrCount attr(s), $extCmdCount cmd(s) — all >= 1000000"
} }
} }
# 11d. Пути на основной реквизит, которого форма не объявляет.
# Check 5 такое пропускает: у заимствованной формы он не проверяет базовые элементы (id < 1000000),
# а привязки в <xr:Link> вообще вне его списка тегов. Между тем это ровно тот случай, на котором
# платформа отвергает загрузку: «Неверный путь к полю - Объект.X». Правило: если основной реквизит
# не объявлен в <Attributes> формы, любой путь с корнем «Объект» не разрешится.
$mainAttrDeclared = $false
foreach ($attr in $attrNodes) {
$maNode = $attr.SelectSingleNode("f:MainAttribute", $nsMgr)
if ($maNode -and $maNode.InnerText.Trim() -eq "true") { $mainAttrDeclared = $true; break }
}
if (-not $mainAttrDeclared) {
# Значения привязок ищем текстом: интересуют и обычные теги, и <xr:DataPath> внутри
# <ChoiceParameterLinks>, а те живут в чужом пространстве имён.
$rawForm = [System.IO.File]::ReadAllText($FormPath, [System.Text.Encoding]::UTF8)
$mainBase = $baseFormNode.SelectSingleNode("f:Attributes/f:Attribute[f:MainAttribute='true']", $bfNs)
$danglingPaths = @{}
foreach ($m in [regex]::Matches($rawForm, '<(?:\w+:)?\w*DataPath[^>]*>(Объект\.[^<]+)</(?:\w+:)?\w*DataPath>')) {
$danglingPaths[$m.Groups[1].Value] = $true
}
if ($danglingPaths.Count -gt 0) {
$shown = @($danglingPaths.Keys | Sort-Object)
$sample = ($shown | Select-Object -First 3) -join ", "
$suffix = if ($shown.Count -gt 3) { " (и ещё $($shown.Count - 3))" } else { "" }
Report-Error "Path(s) rooted at 'Объект' but the form declares no MainAttribute: $sample$suffix"
} elseif ($mainBase) {
Report-OK "Object paths: none dangling (MainAttribute not declared)"
}
}
} }
# Check callType without BaseForm (structural warning) # Check callType without BaseForm (structural warning)
@@ -844,6 +953,71 @@ if (-not $stopped) {
} }
} }
# --- Check 13: префиксы в значениях объявлены в самом файле ---
# `cfg:DataProcessorObject.X` в <v8:Type> при незадекларированном xmlns:cfg — валидный XML, который
# платформа не читает вовсе: «Исключение XDTO произошло при чтении файла». Ошибка типична для
# рукописного XML: префикс скопирован из чужой формы, а объявление в корне забыто. Область видимости
# считаем по узлу (GetNamespaceOfPrefix), а не по корню: локальная xmlns на элементе законна.
if (-not $stopped) {
$prefixErrors = 0
$prefixChecked = 0
$prefixPattern = '^([A-Za-z_][A-Za-z0-9_.-]*):.+$'
# Значения, где префикс обязан резолвиться: тип реквизита/колонки и xsi:type
# Только листовые узлы: под local-name()='Type' подходит и обёртка <Type>, и вложенный <v8:Type>,
# а InnerText обёртки — то же значение, иначе одна ошибка сообщалась бы дважды.
foreach ($node in $xmlDoc.SelectNodes("//*[local-name()='Type' or local-name()='TypeSet']", $nsMgr)) {
if ($node.SelectSingleNode("*")) { continue }
$val = $node.InnerText.Trim()
if (-not $val) { continue }
$m = [regex]::Match($val, $prefixPattern)
if (-not $m.Success) { continue }
$prefixChecked++
$pfx = $m.Groups[1].Value
if (-not $node.GetNamespaceOfPrefix($pfx)) {
Report-Error "13. Type '$val': namespace prefix '${pfx}:' is not declared — the platform cannot read the file (XDTO)"
$prefixErrors++
}
}
foreach ($node in $xmlDoc.SelectNodes("//*[@xsi:type]", $nsMgr)) {
$val = $node.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
$m = [regex]::Match($val, $prefixPattern)
if (-not $m.Success) { continue }
$prefixChecked++
$pfx = $m.Groups[1].Value
if (-not $node.GetNamespaceOfPrefix($pfx)) {
Report-Error "13. xsi:type='$val': namespace prefix '${pfx}:' is not declared — the platform cannot read the file (XDTO)"
$prefixErrors++
}
}
if ($prefixChecked -eq 0) {
Report-OK "13. Namespace prefixes: nothing to check"
} elseif ($prefixErrors -eq 0) {
Report-OK "13. Namespace prefixes: $prefixChecked values, all declared"
}
}
# --- Check 14: версия формата формы совпадает с версией выгрузки ---
# Версию задаёт платформа, которой выгружали, и в пределах одной выгрузки она едина. Форма из
# другой версии — «Неизвестная версия формата N загружаемого файла»: платформа не читает файл,
# который новее её самой. Источник версии ищем общим helper-ом: он же покрывает автономную
# внешнюю обработку/отчёт, где Configuration.xml нет и версию несёт корень самой обработки.
if (-not $stopped -and $script:versionAnchor) {
$formVer = $root.GetAttribute("version")
$dumpVer = Detect-FormatVersion (Split-Path (Resolve-Path $FormPath) -Parent)
if (-not $formVer) {
Report-OK "14. Format version: not comparable"
} elseif ($formVer -ne $dumpVer) {
Report-Error "14. Format version $formVer differs from the dump ($dumpVer) — a dump carries one version, the platform refuses a file it cannot read"
} else {
Report-OK "14. Format version: $formVer, matches the dump"
}
}
# --- Summary --- # --- Summary ---
$checks = $script:okCount + $errors + $warnings $checks = $script:okCount + $errors + $warnings
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# form-validate v1.10 — Validate 1C managed form # form-validate v1.15 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -71,6 +71,49 @@ VALID_CFG_PREFIXES = {
} }
# Корень автономной внешней обработки/отчёта. Копия общего эталона (семья
# support-guard: is_external_root, авторитет — cf-edit).
def _sg_is_external_root(xml_path):
if not os.path.isfile(xml_path):
return False
try:
mx = etree.parse(xml_path).getroot()
for child in mx:
if isinstance(child.tag, str):
return child.tag.split("}")[-1] in ("ExternalDataProcessor", "ExternalReport")
except Exception:
return False
return False
# Версия формата выгрузки. Копия общего эталона (семья detect_format_version, авторитет —
# form-compile): та же ветка для автономной EPF/ERF, где версию несёт корень обработки.
def detect_format_version(d):
while d:
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
ext_path = d + ".xml"
if os.path.isfile(ext_path):
with open(ext_path, "r", encoding="utf-8-sig") as f:
ext_head = f.read(2000)
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
if m:
return m.group(1)
cfg_path = os.path.join(d, "Configuration.xml")
if os.path.isfile(cfg_path):
with open(cfg_path, "r", encoding="utf-8-sig") as f:
head = f.read(2000)
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
if m:
return m.group(1)
parent = os.path.dirname(d)
if parent == d:
break
d = parent
return "2.17"
def localname(el): def localname(el):
return etree.QName(el.tag).localname return etree.QName(el.tag).localname
@@ -128,13 +171,29 @@ def main():
# Detect context: config vs EPF/ERF # Detect context: config vs EPF/ERF
is_config_context = False is_config_context = False
config_xml_path = ''
version_anchor = ''
walk_dir = os.path.dirname(os.path.abspath(form_path)) walk_dir = os.path.dirname(os.path.abspath(form_path))
for _ in range(15): for _ in range(15):
parent = os.path.dirname(walk_dir) parent = os.path.dirname(walk_dir)
if parent == walk_dir: if parent == walk_dir:
break break
# Порядок проверок тот же, что у detect_format_version: сначала корень автономной обработки,
# потом Configuration.xml — иначе форма внутри EPF, лежащей в дереве конфигурации, взяла бы
# версию конфигурации.
ext_root = walk_dir + '.xml'
if not version_anchor:
if _sg_is_external_root(ext_root):
# Ближайший якорь побеждает: автономная обработка остаётся автономной, даже если её
# исходники лежат внутри дерева с Configuration.xml (типовая раскладка проекта:
# src/cf рядом с src/epf). Иначе её собственные External*-типы считались бы ошибкой.
version_anchor = ext_root
break
if os.path.isfile(os.path.join(walk_dir, 'Configuration.xml')): if os.path.isfile(os.path.join(walk_dir, 'Configuration.xml')):
is_config_context = True is_config_context = True
config_xml_path = os.path.join(walk_dir, 'Configuration.xml')
if not version_anchor:
version_anchor = config_xml_path
break break
walk_dir = parent walk_dir = parent
@@ -197,10 +256,15 @@ def main():
if acb is not None: if acb is not None:
acb_name = acb.get("name", "") acb_name = acb.get("name", "")
acb_id = acb.get("id", "") acb_id = acb.get("id", "")
# id=-1 — соглашение, а не требование: в корпусе УТ/БП/ERP так у 21 094 форм из 21 097,
# но три формы платформа выгружает с обычным id и грузит их без нареканий. Поэтому
# предупреждение; ошибка — только если id вовсе не число.
if acb_id == "-1": if acb_id == "-1":
report_ok(f"AutoCommandBar: name='{acb_name}', id={acb_id}") report_ok(f"AutoCommandBar: name='{acb_name}', id={acb_id}")
elif re.match(r'^-?\d+$', acb_id):
report_warn(f"AutoCommandBar id='{acb_id}', usually '-1'")
else: else:
report_error(f"AutoCommandBar id='{acb_id}', expected '-1'") report_error(f"AutoCommandBar id='{acb_id}' is not a number")
else: else:
report_error("AutoCommandBar element missing") report_error("AutoCommandBar element missing")
@@ -452,11 +516,21 @@ def main():
segments = clean_path.split(".") segments = clean_path.split(".")
root_attr = segments[0] root_attr = segments[0]
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute # Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute.
if root_attr == 'Items': # Разрешаем ЦЕПОЧКОЙ: таблица во вложенной таблице сама привязана через Items.*, и один
# шаг оставлял корнем литерал «Items» — форма платформы объявлялась битой (типовые
# НастройкаПравилОбработкиЗаявокСотрудников в БП и ERP).
items_hops = 0
items_broken = False
while root_attr == 'Items':
items_hops += 1
if items_hops > 10: # страховка от кольца ссылок
items_broken = True
break
if len(segments) < 3 or segments[2] != 'CurrentData': if len(segments) < 3 or segments[2] != 'CurrentData':
report_warn(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — unknown Items.* shape, expected Items.<Table>.CurrentData.*") report_warn(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — unknown Items.* shape, expected Items.<Table>.CurrentData.*")
continue items_broken = True
break
table_name = segments[1] table_name = segments[1]
table_el = None table_el = None
for candidate in all_elements: for candidate in all_elements:
@@ -466,14 +540,19 @@ def main():
if table_el is None: if table_el is None:
report_error(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — table element '{table_name}' not found") report_error(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — table element '{table_name}' not found")
path_errors += 1 path_errors += 1
continue items_broken = True
break
table_dp_node = table_el["Node"].find(f"{{{F_NS}}}DataPath") table_dp_node = table_el["Node"].find(f"{{{F_NS}}}DataPath")
if table_dp_node is None or not (table_dp_node.text or "").strip(): if table_dp_node is None or not (table_dp_node.text or "").strip():
continue items_broken = True
break
table_dp = re.sub(r'\[\d+\]', '', (table_dp_node.text or "").strip()) table_dp = re.sub(r'\[\d+\]', '', (table_dp_node.text or "").strip())
if table_dp.startswith('~'): if table_dp.startswith('~'):
table_dp = table_dp[1:] table_dp = table_dp[1:]
root_attr = table_dp.split(".")[0] segments = table_dp.split(".")
root_attr = segments[0]
if items_broken:
continue
if root_attr not in attr_map: if root_attr not in attr_map:
report_error(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — attribute '{root_attr}' not found") report_error(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — attribute '{root_attr}' not found")
@@ -487,6 +566,8 @@ def main():
path_msg = f"{path_msg}, {skip_note}" if path_msg else skip_note path_msg = f"{path_msg}, {skip_note}" if path_msg else skip_note
if path_errors == 0 and path_msg: if path_errors == 0 and path_msg:
report_ok(f"Data bindings: {path_msg}") report_ok(f"Data bindings: {path_msg}")
elif path_errors == 0:
report_ok("Data bindings: none")
# --- Check 6: Button command references --- # --- Check 6: Button command references ---
if not stopped: if not stopped:
@@ -521,6 +602,8 @@ def main():
if cmd_errors == 0 and cmd_checked > 0: if cmd_errors == 0 and cmd_checked > 0:
report_ok(f"Command references: {cmd_checked} buttons checked") report_ok(f"Command references: {cmd_checked} buttons checked")
elif cmd_checked == 0:
report_ok("Command references: none")
# --- Check 7: Events have handler names --- # --- Check 7: Events have handler names ---
if not stopped: if not stopped:
@@ -560,12 +643,19 @@ def main():
if event_errors == 0 and event_checked > 0: if event_errors == 0 and event_checked > 0:
report_ok(f"Event handlers: {event_checked} events checked") report_ok(f"Event handlers: {event_checked} events checked")
elif event_checked == 0:
report_ok("Event handlers: none")
# --- Check 8: Command actions --- # --- Check 8: Command actions ---
if not stopped: if not stopped:
action_errors = 0 action_errors = 0
action_checked = 0 action_checked = 0
# Предупреждение, а не ошибка: <Action> может назначаться в рантайме
# (`Команда.Действие = "Подключаемый_…"` в ПриСозданииНаСервере) — приём типовых конфигураций
# там, где обработчик существует не во всякой сборке. Назначать может и чужой модуль
# (переопределяемый слой, подключаемые команды), так что по одному Form.xml не решить.
# Корпус УТ/БП/ERP: 406 таких команд на 275 формах, произведённых платформой.
for cmd in cmd_nodes: for cmd in cmd_nodes:
if stopped: if stopped:
break break
@@ -573,11 +663,13 @@ def main():
action_node = cmd.find(f"{{{F_NS}}}Action") action_node = cmd.find(f"{{{F_NS}}}Action")
action_checked += 1 action_checked += 1
if action_node is None or not (action_node.text or "").strip(): if action_node is None or not (action_node.text or "").strip():
report_error(f"Command '{cmd_name}': missing or empty Action") report_warn(f"Command '{cmd_name}': no Action — handler must be assigned at runtime, otherwise the command does nothing")
action_errors += 1 action_errors += 1
if action_errors == 0 and action_checked > 0: if action_errors == 0 and action_checked > 0:
report_ok(f"Command actions: {action_checked} commands checked") report_ok(f"Command actions: {action_checked} commands checked")
elif action_checked == 0:
report_ok("Command actions: none")
# --- Check 9: MainAttribute count --- # --- Check 9: MainAttribute count ---
if not stopped: if not stopped:
@@ -708,6 +800,33 @@ def main():
if (ext_attr_count + ext_cmd_count) > 0: if (ext_attr_count + ext_cmd_count) > 0:
report_ok(f"Extension ID ranges: {ext_attr_count} attr(s), {ext_cmd_count} cmd(s) \u2014 all >= 1000000") report_ok(f"Extension ID ranges: {ext_attr_count} attr(s), {ext_cmd_count} cmd(s) \u2014 all >= 1000000")
# 11d. \u041f\u0443\u0442\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442, \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u0442.
# Check 5 \u0442\u0430\u043a\u043e\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442: \u0443 \u0437\u0430\u0438\u043c\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u043e\u043d \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b (id < 1000000),
# \u0430 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u0432 <xr:Link> \u0432\u043e\u043e\u0431\u0449\u0435 \u0432\u043d\u0435 \u0435\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u0442\u0435\u0433\u043e\u0432. \u041c\u0435\u0436\u0434\u0443 \u0442\u0435\u043c \u044d\u0442\u043e \u0440\u043e\u0432\u043d\u043e \u0442\u043e\u0442 \u0441\u043b\u0443\u0447\u0430\u0439, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c
# \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u043e\u0442\u0432\u0435\u0440\u0433\u0430\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443: \u00ab\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043a \u043f\u043e\u043b\u044e - \u041e\u0431\u044a\u0435\u043a\u0442.X\u00bb. \u041f\u0440\u0430\u0432\u0438\u043b\u043e: \u0435\u0441\u043b\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442
# \u043d\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d \u0432 <Attributes> \u0444\u043e\u0440\u043c\u044b, \u043b\u044e\u0431\u043e\u0439 \u043f\u0443\u0442\u044c \u0441 \u043a\u043e\u0440\u043d\u0435\u043c \u00ab\u041e\u0431\u044a\u0435\u043a\u0442\u00bb \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0441\u044f.
main_attr_declared = False
for attr in attr_nodes:
ma_node = attr.find(f"{{{F_NS}}}MainAttribute")
if ma_node is not None and (ma_node.text or "").strip() == "true":
main_attr_declared = True
break
if not main_attr_declared:
# \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043e\u043a \u0438\u0449\u0435\u043c \u0442\u0435\u043a\u0441\u0442\u043e\u043c: \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 \u0438 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0442\u0435\u0433\u0438, \u0438 <xr:DataPath> \u0432\u043d\u0443\u0442\u0440\u0438
# <ChoiceParameterLinks>, \u0430 \u0442\u0435 \u0436\u0438\u0432\u0443\u0442 \u0432 \u0447\u0443\u0436\u043e\u043c \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435 \u0438\u043c\u0451\u043d.
with open(form_path, "r", encoding="utf-8-sig") as fh:
raw_form = fh.read()
main_base = base_form_node.find(f"{{{F_NS}}}Attributes/{{{F_NS}}}Attribute[{{{F_NS}}}MainAttribute='true']")
dangling_paths = set(re.findall(r'<(?:\w+:)?\w*DataPath[^>]*>(\u041e\u0431\u044a\u0435\u043a\u0442\.[^<]+)</(?:\w+:)?\w*DataPath>', raw_form))
if dangling_paths:
shown = sorted(dangling_paths)
sample = ", ".join(shown[:3])
suffix = f" (\u0438 \u0435\u0449\u0451 {len(shown) - 3})" if len(shown) > 3 else ""
report_error(f"Path(s) rooted at '\u041e\u0431\u044a\u0435\u043a\u0442' but the form declares no MainAttribute: {sample}{suffix}")
elif main_base is not None:
report_ok("Object paths: none dangling (MainAttribute not declared)")
# Check callType without BaseForm # Check callType without BaseForm
if not stopped and not is_extension: if not stopped and not is_extension:
call_type_without_base = False call_type_without_base = False
@@ -770,6 +889,62 @@ def main():
else: else:
report_ok('12. Types: no type values to check') report_ok('12. Types: no type values to check')
# --- Check 13: префиксы в значениях объявлены в самом файле ---
# `cfg:DataProcessorObject.X` в <v8:Type> при незадекларированном xmlns:cfg — валидный XML, который
# платформа не читает вовсе: «Исключение XDTO произошло при чтении файла». Ошибка типична для
# рукописного XML: префикс скопирован из чужой формы, а объявление в корне забыто. Область видимости
# считаем по узлу (nsmap элемента), а не по корню: локальная xmlns на элементе законна.
if not stopped:
prefix_errors = 0
prefix_checked = 0
prefix_re = re.compile(r'^([A-Za-z_][A-Za-z0-9_.-]*):.+$')
for node in root.iter():
if not isinstance(node.tag, str):
continue
ln = localname(node)
values = []
if ln in ('Type', 'TypeSet'):
values.append((node.text or '').strip())
xsi_type = node.get(f'{{{"http://www.w3.org/2001/XMLSchema-instance"}}}type')
if xsi_type:
values.append(xsi_type.strip())
for val in values:
if not val:
continue
m = prefix_re.match(val)
if not m:
continue
prefix_checked += 1
pfx = m.group(1)
if pfx not in node.nsmap:
kind = "xsi:type" if val == xsi_type else "Type"
report_error(f"13. {kind} '{val}': namespace prefix '{pfx}:' is not declared "
"— the platform cannot read the file (XDTO)")
prefix_errors += 1
if prefix_checked == 0:
report_ok('13. Namespace prefixes: nothing to check')
elif prefix_errors == 0:
report_ok(f'13. Namespace prefixes: {prefix_checked} values, all declared')
# --- Check 14: версия формата формы совпадает с версией выгрузки ---
# Версию задаёт платформа, которой выгружали, и в пределах одной выгрузки она едина. Форма из
# другой версии — «Неизвестная версия формата N загружаемого файла»: платформа не читает файл,
# который новее её самой. Источник версии ищем общим helper-ом: он же покрывает автономную
# внешнюю обработку/отчёт, где Configuration.xml нет и версию несёт корень самой обработки.
if not stopped and version_anchor:
form_ver = root.get('version', '')
dump_ver = detect_format_version(os.path.dirname(os.path.abspath(form_path)))
if not form_ver:
report_ok('14. Format version: not comparable')
elif form_ver != dump_ver:
report_error(f'14. Format version {form_ver} differs from the dump ({dump_ver}) '
'— a dump carries one version, the platform refuses a file it cannot read')
else:
report_ok(f'14. Format version: {form_ver}, matches the dump')
# --- Finalize --- # --- Finalize ---
checks = ok_count + errors + warnings checks = ok_count + errors + warnings
if errors == 0 and warnings == 0 and not detailed: if errors == 0 and warnings == 0 and not detailed:
+1 -1
View File
@@ -30,7 +30,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-help.ps1" -ObjectName "<ObjectName>" [-Lang "<Lang>"] [-SrcDir "<SrcDir>"] python "${CLAUDE_SKILL_DIR}/scripts/add-help.py" -ObjectName "<ObjectName>" [-Lang "<Lang>"] [-SrcDir "<SrcDir>"]
``` ```
## Что делает скрипт ## Что делает скрипт
+2 -2
View File
@@ -29,13 +29,13 @@ allowed-tools:
### Inline mode ### Inline mode
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/interface-edit.ps1" -CIPath '<path>' -Operation hide -Value '<cmd>' python "${CLAUDE_SKILL_DIR}/scripts/interface-edit.py" -CIPath '<path>' -Operation hide -Value '<cmd>'
``` ```
### JSON mode ### JSON mode
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/interface-edit.ps1" -CIPath '<path>' -DefinitionFile '<json>' python "${CLAUDE_SKILL_DIR}/scripts/interface-edit.py" -CIPath '<path>' -DefinitionFile '<json>'
``` ```
## Операции ## Операции
+2 -2
View File
@@ -24,6 +24,6 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/interface-validate.ps1" -CIPath "Subsystems/Продажи" python "${CLAUDE_SKILL_DIR}/scripts/interface-validate.py" -CIPath "Subsystems/Продажи"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/interface-validate.ps1" -CIPath "Subsystems/Продажи/Ext/CommandInterface.xml" python "${CLAUDE_SKILL_DIR}/scripts/interface-validate.py" -CIPath "Subsystems/Продажи/Ext/CommandInterface.xml"
``` ```
+1 -1
View File
@@ -24,7 +24,7 @@ allowed-tools:
3. Изменить созданный объект — `/meta-edit`; проверить — `/meta-validate`. 3. Изменить созданный объект — `/meta-edit`; проверить — `/meta-validate`.
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-compile.ps1" -JsonPath "<json>" -OutputDir "<ConfigDir>" python "${CLAUDE_SKILL_DIR}/scripts/meta-compile.py" -JsonPath "<json>" -OutputDir "<ConfigDir>"
``` ```
| Параметр | Описание | | Параметр | Описание |
+1 -1
View File
@@ -38,7 +38,7 @@ allowed-tools:
| `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout | | `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-decompile.ps1" -ObjectPath "<Объект.xml>" -OutputPath "<out.json>" python "${CLAUDE_SKILL_DIR}/scripts/meta-decompile.py" -ObjectPath "<Объект.xml>" -OutputPath "<out.json>"
``` ```
Неподдерживаемый тип объекта или не-`MetaDataObject` root → ненулевой код выхода и сообщение в stderr. Неподдерживаемый тип объекта или не-`MetaDataObject` root → ненулевой код выхода и сообщение в stderr.
+2 -2
View File
@@ -18,13 +18,13 @@ allowed-tools:
### Inline mode (простые операции) ### Inline mode (простые операции)
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-edit.ps1" -ObjectPath "<path>" -Operation <op> -Value "<val>" python "${CLAUDE_SKILL_DIR}/scripts/meta-edit.py" -ObjectPath "<path>" -Operation <op> -Value "<val>"
``` ```
### JSON mode (сложные/комбинированные) ### JSON mode (сложные/комбинированные)
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-edit.ps1" -DefinitionFile "<json>" -ObjectPath "<path>" python "${CLAUDE_SKILL_DIR}/scripts/meta-edit.py" -DefinitionFile "<json>" -ObjectPath "<path>"
``` ```
| Параметр | Описание | | Параметр | Описание |
+1 -1
View File
@@ -3,7 +3,7 @@
Для сложных и комбинированных операций используйте JSON-файл вместо inline-режима. Для сложных и комбинированных операций используйте JSON-файл вместо inline-режима.
```powershell ```powershell
powershell.exe -NoProfile -File .claude/skills/meta-edit/scripts/meta-edit.ps1 -DefinitionFile "<json>" -ObjectPath "<path>" python .claude/skills/meta-edit/scripts/meta-edit.py -DefinitionFile "<json>" -ObjectPath "<path>"
``` ```
## add — добавить элементы ## add — добавить элементы
+1 -1
View File
@@ -23,7 +23,7 @@ allowed-tools:
| `OutFile` | Записать результат в файл (UTF-8 BOM) | | `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-info.ps1" -ObjectPath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/meta-info.py" -ObjectPath "<путь>"
``` ```
## Три режима ## Три режима
+1 -1
View File
@@ -32,7 +32,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-remove.ps1" -ConfigDir "<путь>" -Object "Catalog.Товары" python "${CLAUDE_SKILL_DIR}/scripts/meta-remove.py" -ConfigDir "<путь>" -Object "Catalog.Товары"
``` ```
## Поддерживаемые типы ## Поддерживаемые типы
+2 -2
View File
@@ -24,6 +24,6 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-validate.ps1" -ObjectPath "Catalogs/Номенклатура/Номенклатура.xml" python "${CLAUDE_SKILL_DIR}/scripts/meta-validate.py" -ObjectPath "Catalogs/Номенклатура/Номенклатура.xml"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-validate.ps1" -ObjectPath "Catalogs/Банки|Documents/Заказ" python "${CLAUDE_SKILL_DIR}/scripts/meta-validate.py" -ObjectPath "Catalogs/Банки|Documents/Заказ"
``` ```
+36 -11
View File
@@ -29,7 +29,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -JsonPath "<путь>.json" -OutputPath "<путь>/Template.xml" python "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.py" -JsonPath "<путь>.json" -OutputPath "<путь>/Template.xml"
``` ```
## Рабочий процесс ## Рабочий процесс
@@ -43,24 +43,49 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -J
## JSON-схема DSL ## JSON-схема DSL
Ниже — компактная структура и ключевые правила, достаточные для типового макета. Полные таблицы полей (все свойства шрифтов, стилей, ячеек), развёрнутый пример и ограничения формата — в **`reference/dsl-spec.md`**; нужны не всегда, читать по необходимости. Ниже — компактная структура и ключевые правила, достаточные для типового макета. Подробности читать по необходимости:
| Что нужно | Файл |
|---|---|
| Полные таблицы полей, развёрнутый пример, ограничения формата | `reference/dsl-spec.md` |
| Шрифты, стили, цвета, рамки, колоночные раскладки и стили колонок | `reference/styles.md` |
| Полный перечень свойств стиля — все 44 | `reference/format-properties.md` |
Краткая структура: Краткая структура:
``` ```
{ columns, page, defaultWidth, columnWidths, { columns, page, defaultWidth, columnWidths, columnStyles,
fonts: { name: { face, size, bold, italic, underline, strikeout } }, fonts: { name: { face, size, bold, italic, underline, strikeout } | { ref } },
styles: { name: { font, align, valign, border, borderWidth, wrap, format } }, styles: { name: { font, horizontalAlignment, verticalAlignment, textPlacement,
areas: [{ name, rows: [{ height, rowStyle, cells: [ backColor, textColor, border, borderColor, format, hidden } },
areas: [{ name, columnSet, rows: [{ height, hidden, rowStyle, cells: [
{ col, span, rowspan, style, param, detail, text, template } { col, span, rowspan, style, param, detail, text, template }
]}]}] ]}]}],
namedAreas: [{ name, rows, cols }],
columnSets: { name: { columns, columnWidths, columnStyles } }
} }
``` ```
Ключевые правила: Ключевые правила:
- `page` — формат страницы (`"A4-landscape"`, `"A4-portrait"` или число). Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"` - `page` — формат страницы (`"A4-landscape"`, `"A4-portrait"` или число). Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"`
- `col` — 1-based позиция колонки - `name` у области в `areas` необязателен: область без имени — просто кусок сетки, именованной она не станет
- `rowStyle` — автозаполнение пустот стилем (рамки по всей ширине) - `namedAreas` — области, которые не описываются диапазоном подряд идущих строк: полоса колонок, прямоугольник, ячейка. Тип не указывается, он следует из того, какие оси заданы
- Тип заполнения определяется автоматически: `param` → Parameter, `text` → Text, `template` → Template - `columnSet` у области — ссылка на раскладку из `columnSets`, когда группе строк нужны свои ширины колонок; без него действует документная раскладка
- `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки) - Ключ стиля — имя свойства как в выгрузке; `columnStyles` вешает стиль на колонку так же, как `style` на ячейку
- Рамка — `border` (все стороны) или `leftBorder`/`topBorder`/`rightBorder`/`bottomBorder`; значение `"Solid"` либо `{ style, width }`
- `rowStyle` — стиль строки: ложится и на строку, и на все её колонки, заполняя пустоты (рамки по всей ширине)
- `height` и `hidden` — собственные свойства строки, у ячейки таких нет
- `empty` в строке — шорткат для N подряд пустых строк (`{ "empty": 3 }` = три `{}`) - `empty` в строке — шорткат для N подряд пустых строк (`{ "empty": 3 }` = три `{}`)
- Строку можно писать массивом ячеек — позиция из порядка, `col` не нужен: `"текст"`, `"{Имя}"` — параметр, `">"` — продолжить ячейку слева, `"|"` — сверху, `null` — пропуск колонки
- `col` — 1-based позиция колонки
- `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки)
- Содержимое ячейки задаётся одним из ключей: `param` — параметр заполнения, `text` — статический текст, `template` — текст со вставками `[Параметр]`
Двухуровневая шапка массивами:
```json
"rows": [
["Вид", "Остаток", ">", "Итог"],
["|", "начало", "конец", "|"],
["{Вид}", "{Нач}", "{Кон}", "{Итог}"]
]
```
+131 -50
View File
@@ -2,6 +2,9 @@
Компактный JSON-формат для описания макетов табличных документов 1С (SpreadsheetDocument). Используется навыком `/mxl-compile` (JSON → XML). Компактный JSON-формат для описания макетов табличных документов 1С (SpreadsheetDocument). Используется навыком `/mxl-compile` (JSON → XML).
Оформление — шрифты, стили, цвета, рамки, колоночные раскладки — в `styles.md`;
полный перечень свойств стиля — в `format-properties.md`.
## Пример ## Пример
```json ```json
@@ -18,11 +21,11 @@
"styles": { "styles": {
"default": {}, "default": {},
"header": { "font": "header", "align": "center" }, "header": { "font": "header", "horizontalAlignment": "Center" },
"label": { "font": "bold" }, "label": { "font": "bold" },
"bordered": { "border": "all" }, "bordered": { "border": "Solid" },
"bordered-right": { "border": "all", "align": "right" }, "bordered-right": { "border": "Solid", "horizontalAlignment": "Right" },
"total-right": { "font": "bold", "border": "top", "align": "right" } "total-right": { "font": "bold", "topBorder": "Solid", "horizontalAlignment": "Right" }
}, },
"areas": [ "areas": [
@@ -73,88 +76,166 @@
| Поле | Обяз. | По умолч. | Описание | | Поле | Обяз. | По умолч. | Описание |
|------|:-----:|-----------|----------| |------|:-----:|-----------|----------|
| `columns` | да | — | Количество колонок | | `columns` | да | — | Количество колонок в раскладке по умолчанию. `0` допустимо: значит, все строки живут в раскладках из `columnSets` |
| `page` | нет | — | Формат страницы: `"A4-landscape"` (780), `"A4-portrait"` (540) или число. Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"` | | `page` | нет | — | Формат страницы: `"A4-landscape"` (780), `"A4-portrait"` (540) или число. Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"` |
| `defaultWidth` | нет | 10 | Ширина колонок по умолчанию. Игнорируется если задан `page` и все колонки используют `"Nx"` | | `defaultWidth` | нет | 10 | Ширина колонок по умолчанию. Игнорируется если задан `page` и все колонки используют `"Nx"` |
| `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"`. Значения: число (абсолют) или `"Nx"` (множитель от defaultWidth, напр. `"2x"`, `"0.5x"`) | | `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"`. Значения: число (абсолют) или `"Nx"` (множитель от defaultWidth, напр. `"2x"`, `"0.5x"`) |
| `columnStyles` | нет | — | Оформление колонок: те же ключи, значение — имя стиля (см. `styles.md`) |
| `textLanguages` | нет | `["ru"]` | Языки, на которых пишется текст, заданный строкой (см. ниже) |
| `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) | | `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) |
| `styles` | нет | `{}` | Именованные стили | | `styles` | нет | `{}` | Именованные стили (см. `styles.md`) |
| `areas` | да | — | Массив именованных областей (порядок = порядок в документе) | | `areas` | да | — | Массив областей — диапазонов подряд идущих строк (порядок = порядок в документе); имя необязательно |
| `namedAreas` | нет | — | Именованные области, заданные координатами (см. ниже) |
## Шрифты (`fonts.<name>`) | `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `styles.md`) |
| Поле | По умолч. | Описание |
|------|-----------|----------|
| `face` | `"Arial"` | Имя шрифта |
| `size` | `10` | Размер |
| `bold` | `false` | Жирный |
| `italic` | `false` | Курсив |
| `underline` | `false` | Подчёркнутый |
| `strikeout` | `false` | Зачёркнутый |
Шрифт `"default"` используется когда стиль не указывает шрифт явно. Если не определён, создаётся автоматически (Arial 10).
## Стили (`styles.<name>`)
| Поле | По умолч. | Описание |
|------|-----------|----------|
| `font` | `"default"` | Ссылка на имя шрифта |
| `align` | — | `left`, `center`, `right` |
| `valign` | — | `top`, `center` |
| `border` | — | Стороны рамки: `all`, `top`, `bottom`, `left`, `right`, `none`. Через запятую: `"top,bottom"` |
| `borderWidth` | `"thin"` | Толщина рамки: `thin` (1px) или `thick` (2px) |
| `wrap` | `false` | Перенос текста |
| `format` | — | Формат данных 1С: `"ЧЦ=15; ЧДЦ=2"`, `"ДФ=dd.MM.yyyy"` и т.д. |
## Области (`areas[]`) ## Области (`areas[]`)
| Поле | Обяз. | Описание | | Поле | Обяз. | Описание |
|------|:-----:|----------| |------|:-----:|----------|
| `name` | да | Имя области для `Макет.ПолучитьОбласть("Имя")` | | `name` | нет | Имя области для `Макет.ПолучитьОбласть("Имя")` |
| `columnSet` | нет | Ссылка на раскладку из `columnSets` |
| `rows` | да | Массив строк | | `rows` | да | Массив строк |
Макет собирается из областей — диапазонов подряд идущих строк. Имя делает область именованной: она доступна в коде как `Макет.ПолучитьОбласть("Имя")` и занимает строки своего диапазона. **Область без имени** — просто кусок сетки: так описываются строки, не принадлежащие ни одной именованной области.
## Именованные области координатами (`namedAreas[]`)
Для областей, которые диапазоном подряд идущих строк не описываются: полоса колонок, прямоугольник, ячейка, а также пересекающиеся с другими.
| Поле | Обяз. | Описание |
|------|:-----:|----------|
| `name` | да | Имя области |
| `rows` | \* | Строки: число или диапазон `"N-M"`, 1-based |
| `cols` | \* | Колонки: число или диапазон `"N-M"`, 1-based |
\* Обязательна хотя бы одна из осей.
**Тип области не указывается** — он следует из того, какие оси заданы, как в `ТабличныйДокумент.Область()`: только строки → полоса строк, только колонки → полоса колонок, обе оси → прямоугольник, одиночные значения по обеим осям → одна ячейка.
```json
"namedAreas": [
{ "name": "ОбластьПечатиПоВысоте", "rows": "1-48" },
{ "name": "ОбластьПечатиПоШирине", "cols": "1-35" },
{ "name": "HZY", "rows": 9, "cols": "16-17" }
]
```
Диапазон — та же грамматика, что у `columnWidths`, но **только** число или `"N-M"`: список через запятую запрещён, область непрерывна. Имя обязательно, и хотя бы одна ось должна быть задана; нарушение любого из этих правил → ненулевой код выхода и сообщение в stderr.
## Строки (`rows[]`) ## Строки (`rows[]`)
| Поле | По умолч. | Описание | | Поле | По умолч. | Описание |
|------|-----------|----------| |------|-----------|----------|
| `height` | — | Высота строки (если не задана, используется авто) | | `height` | — | Высота строки (если не задана, используется авто) |
| `rowStyle` | — | Стиль для ВСЕХ колонок (заполняет пустоты рамками) | | `hidden` | `false` | Скрыть строку |
| `rowStyle` | — | Стиль строки: ложится и на саму строку, и на ВСЕ её колонки (заполняет пустоты рамками) |
| `cells` | `[]` | Массив ячеек | | `cells` | `[]` | Массив ячеек |
| `empty` | — | Количество подряд идущих пустых строк (заменяет N отдельных `{}`) | | `empty` | — | Количество подряд идущих пустых строк (заменяет N отдельных `{}`) |
Строка без `cells` и `rowStyle` → пустая строка. `{ "empty": 3 }` эквивалентно трём `{}`. Строка без `cells` и `rowStyle` → пустая строка. `{ "empty": 3 }` эквивалентно трём `{}`.
`height` и `hidden` — собственные свойства строки: у ячейки таких нет, и в её оформление они
не попадают. Всё остальное оформление строки задаётся через `rowStyle`.
### Короткая форма: строка массивом
Вместо объекта строка может быть массивом ячеек — позиция определяется порядком, `col` не указывается.
| Элемент | Значение |
|---------|----------|
| `"текст"` | Статический текст (`text`) |
| `{ "ru": "…", "en": "…" }` | Тот же текст на нескольких языках |
| `"{Имя}"` | Параметр (`param`) |
| `">"` | Продолжение ячейки слева — увеличивает её `span` |
| `"|"` | Продолжение ячейки сверху — увеличивает её `rowspan` |
| `null` | Пустая колонка: позиция занята, ячейка не создаётся |
| `{ ... }` | Обычная ячейка **без** `col`; нужна для `style`, `detail`, `template` |
Объект-элемент трактуется по его ключам: если среди них есть ключ ячейки (`span`, `rowspan`,
`style`, `param`, `detail`, `text`, `template`) — объект описывает свойства ячейки. Иначе он
целиком считается её текстом, а его ключи — идентификаторами языков.
```json
"rows": [
["Вид", "Остаток", ">", "Итог"],
["|", "начало", "конец", "|"],
["{Вид}", "{Нач}", "{Кон}", "{Итог}"]
]
```
Здесь «Вид» и «Итог» объединены по вертикали на две строки, «Остаток» — по горизонтали на две колонки.
Ограничения короткой формы:
- не задать `height` и `rowStyle` — это свойства строки, а не ячейки;
- не выразить текст, совпадающий с `">"`, `"|"` или с шаблоном `"{...}"`.
Маркеру нужно, что продолжать: `">"` требует ячейку слева в той же строке, `"|"` — ячейку сверху. Объектный элемент не должен нести `col`: позиция уже задана порядком. Число элементов не может превышать `columns`. Нарушение любого из этих правил → ненулевой код выхода и сообщение в stderr.
## Ячейки (`cells[]`) ## Ячейки (`cells[]`)
| Поле | Обяз. | По умолч. | Описание | | Поле | Обяз. | По умолч. | Описание |
|------|:-----:|-----------|----------| |------|:-----:|-----------|----------|
| `col` | да | — | Позиция колонки (1-based) | | `col` | да | — | Позиция колонки (1-based). В короткой форме строки не указывается — позиция берётся из порядка |
| `span` | нет | `1` | Объединение по горизонтали (количество колонок) | | `span` | нет | `1` | Объединение по горизонтали (количество колонок) |
| `rowspan` | нет | `1` | Объединение по вертикали (количество строк) | | `rowspan` | нет | `1` | Объединение по вертикали (количество строк) |
| `style` | нет | rowStyle | Стиль ячейки (переопределяет rowStyle) | | `style` | нет | rowStyle | Стиль ячейки (переопределяет rowStyle) |
| `param` | нет | — | Параметр заполнения | | `param` | нет | — | Параметр заполнения |
| `detail` | нет | — | Параметр расшифровки (только с `param`) | | `detail` | нет | — | Параметр расшифровки (только с `param`) |
| `text` | нет | — | Статический текст | | `text` | нет | — | Статический текст. Строка или объект `{ ru, en }` — см. ниже |
| `template` | нет | — | Шаблонный текст с `[Параметр]` | | `template` | нет | — | Шаблонный текст с `[Параметр]`. Строка или объект, как `text` |
### Тип заполнения ### Содержимое ячейки
Определяется автоматически по содержимому ячейки: Задаётся ровно одним из ключей, объявлять способ заполнения отдельно не нужно:
- `param` → fillType=Parameter - `param` — параметр заполнения;
- `template` → fillType=Template - `template` — текст со вставками `[Параметр]`;
- `text` → fillType=Text - `text` — статический текст;
- ничего → без fillType (пустая ячейка или рамка) - ничего — пустая ячейка (нужна, например, ради рамки).
## `rowStyle` — автозаполнение ### Текст на нескольких языках
Когда задан `rowStyle`, компилятор создаёт ячейки для ВСЕХ колонок строки. Позиции без явных ячеек заполняются пустыми ячейками с указанным стилем. Это обеспечивает сплошные рамки в табличных строках. `text` и `template` принимают строку или объект «язык → текст». Объект даёт по надписи на каждый язык, в порядке ключей. Строка означает один и тот же текст на всех языках макета — по умолчанию только русский.
```json
{ "col": 1, "text": "Наименование" }
{ "col": 2, "text": { "ru": "Поставщик", "en": "Supplier" } }
```
Набор языков задаётся документным ключом `textLanguages`:
```json
{ "columns": 3, "textLanguages": ["ru", "en"], "areas": [] }
```
С таким объявлением `"Наименование"` из примера выше даст надпись и под `ru`, и под `en`.
Ключ ни на что в конфигурации не смотрит — это просто список языков, на которые разворачивается строка.
Пустая строка — это текст: ячейка с `"text": ""` даёт пустую надпись, а не ячейку без текста.
## `rowStyle` — оформление строки
Стиль применяется ко ВСЕЙ ширине строки: позиции без явных ячеек получают тот же стиль. Так в табличных строках получаются сплошные рамки. Он же становится оформлением самой строки — именно так платформа хранит строку, оформленную целиком.
Стиль конкретной ячейки (`style`) перекрывает `rowStyle` для этой ячейки.
Если в предыдущих строках той же области есть ячейки с `rowspan`, их колонки при автозаполнении пропускаются. Если в предыдущих строках той же области есть ячейки с `rowspan`, их колонки при автозаполнении пропускаются.
## Ограничения ## Ограничения
Текущая версия не поддерживает: DSL описывает не все конструкции табличного документа. Перечисленное ниже **теряется при
- Множественные наборы колонок (`columnsID`) round-trip** (`/mxl-decompile``/mxl-compile`): в JSON оно не попадает, в сгенерированный
- Области типа Columns / Rectangle XML не возвращается.
- Рисунки (штрихкоды, картинки)
- Фон ячеек - ячейки-поля ввода (`containsValue` / `valueType` / `controlType`);
- объединения, не привязанные к ячейке (по всей высоте или ширине документа);
- рисунки и картинки, в том числе штрихкоды, и примечания к ячейкам;
- группировки строк и колонок;
- колонтитулы, параметры печати, область печати.
Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому
diff после round-trip обычно шире фактической доработки.
Отдельно про побайтовое совпадение. В макетах, которые долго правили в Конфигураторе,
встречаются следы прежних состояний: формат ячейки может нести ширину колонки, которая с тех
пор изменилась. Такие значения не описывают итоговый документ и из него не выводятся, поэтому
собранный XML совпадёт с исходным не всегда — при полностью сохранённом содержании.
@@ -0,0 +1,77 @@
# Полный список свойств стиля
Имя ключа совпадает с именем свойства в выгрузке — исключений нет. Частые свойства
с примерами — в `styles.md`, здесь полный перечень.
Тип значения:
- **число** — целое;
- **да/нет**`true` / `false`;
- **перечисление** — одно из указанных, регистр не важен;
- **цвет**`#RRGGBB`, `style:Имя`, `web:Имя`, `win:Имя`;
- **линия**`"Solid"` либо `{ style, width }`;
- **текст** — строка (разворачивается на языки макета).
## Текст и выравнивание
| Ключ | Тип | Значение |
|------|-----|----------|
| `font` | имя | Ссылка на имя из `fonts` |
| `horizontalAlignment` | перечисление | `Left`, `Center`, `Right`, `Justify`, `Auto` |
| `verticalAlignment` | перечисление | `Top`, `Center`, `Bottom` |
| `textPlacement` | перечисление | `Wrap`, `Cut`, `Block`, `Auto` |
| `textOrientation` | число | Поворот в десятых долях градуса: `900` = 90° |
| `textColor` | цвет | Цвет текста |
| `indent` | число | Отступ текста |
| `autoIndent` | число | Автоматический отступ |
| `format` | текст | Формат данных: `"ЧЦ=15; ЧДЦ=2"` |
| `editFormat` | текст | Формат редактирования |
| `mask` | текст | Маска ввода |
| `markNegatives` | да/нет | Выделять отрицательные |
## Фон и рамка
| Ключ | Тип | Значение |
|------|-----|----------|
| `backColor` | цвет | Цвет фона |
| `pattern` | перечисление | `Solid`, `WithoutPattern`, `Pattern7`, `Pattern10`, `Pattern12`, `Pattern13`, `Pattern14`, `Pattern16` |
| `patternColor` | цвет | Цвет узора |
| `border` | линия | Все четыре стороны |
| `leftBorder`, `topBorder`, `rightBorder`, `bottomBorder` | линия | Отдельная сторона |
| `borderColor` | цвет | Цвет рамки |
## Поведение
| Ключ | Тип | Значение |
|------|-----|----------|
| `hidden` | да/нет | Скрыть |
| `protection` | да/нет | Защита от редактирования |
| `print` | да/нет | Выводить на печать |
| `hyperLink` | да/нет | Гиперссылка |
| `detailsUse` | перечисление | Использование расшифровки: `Cell`, `Row`, `WithoutProcessing` |
| `autoMarkIncomplete` | да/нет | Автоотметка незаполненного |
| `bySelectedColumns` | да/нет | По выделенным колонкам |
| `columnSizeChange` | перечисление | `Normal`, `QuickChange` |
| `autoWidthCalculation` | да/нет | Автоматический расчёт ширины |
| `widthWeightFactor` | число | Весовой коэффициент ширины |
## Картинка в ячейке
| Ключ | Тип | Значение |
|------|-----|----------|
| `picIndex` | число | Номер картинки |
| `pictureSizeMode` | перечисление | `AutoSize`, `Proportionally`, `RealSize` |
| `picHorizontalAlignment` | перечисление | `Auto`, `Center`, `Left`, `Right` |
| `picVerticalAlignment` | перечисление | `Top`, `Center`, `Bottom` |
| `textPosition` | перечисление | Положение текста относительно картинки: `Auto`, `Top`, `Right`, `Bottom` |
| `drawingBorder` | число | Рамка рисунка |
| `drawingHaveLeftBorder`, `drawingHaveTopBorder`, `drawingHaveRightBorder`, `drawingHaveBottomBorder` | да/нет | Наличие стороны рамки рисунка |
## Чего в стиле нет
- `width` — свойство колонки, задаётся через `columnWidths`;
- `height` — свойство строки, задаётся ключом `height` у строки;
- `fillType` — выводится из того, каким ключом задано содержимое ячейки
(`text` / `param` / `template`);
- `containsValue`, `valueType`, `controlType` — свойства конкретной ячейки, а не общего
оформления; сейчас не поддерживаются.
@@ -0,0 +1,145 @@
# Оформление: шрифты, стили, колонки
Оформление в табличном документе — одна сущность на всех: ячейка, строка и колонка ссылаются
на один и тот же именованный стиль. Ячейка — ключом `style`, строка — `rowStyle`, колонка —
через `columnStyles`.
## Шрифты (`fonts.<name>`)
| Поле | По умолч. | Описание |
|------|-----------|----------|
| `face` | `"Arial"` | Имя шрифта |
| `size` | `10` | Размер (бывает дробным: `8.3`) |
| `bold` | `false` | Жирный |
| `italic` | `false` | Курсив |
| `underline` | `false` | Подчёркнутый |
| `strikeout` | `false` | Зачёркнутый |
Шрифт `"default"` используется, когда стиль не указывает шрифт явно. Если не определён,
создаётся автоматически (Arial 10).
Вместо собственного описания шрифт может быть **ссылкой** — на элемент стиля конфигурации
или на системный шрифт. Тогда у него единственное поле:
```json
"fonts": {
"основной": { "ref": "style:TextFont" },
"системный": { "ref": "sys:DefaultGUIFont" }
}
```
Это та же запись, что у шрифта в описании формы.
## Стили (`styles.<name>`)
Ключ стиля — имя свойства так, как оно называется в выгрузке. Ниже частые; полный список
из 44 свойств — в `format-properties.md`.
| Поле | Описание |
|------|----------|
| `font` | Ссылка на имя из `fonts` |
| `horizontalAlignment` | `Left`, `Center`, `Right`, `Justify`, `Auto` |
| `verticalAlignment` | `Top`, `Center`, `Bottom` |
| `textPlacement` | Что делать с длинным текстом: `Wrap` (перенос), `Cut` (обрезать), `Block`, `Auto` |
| `backColor` | Цвет фона (см. «Цвет») |
| `textColor` | Цвет текста |
| `border`, `leftBorder`, `topBorder`, `rightBorder`, `bottomBorder` | Рамка (см. «Рамка») |
| `borderColor` | Цвет рамки |
| `format` | Формат данных 1С: `"ЧЦ=15; ЧДЦ=2"`, `"ДФ=dd.MM.yyyy"` |
| `hidden` | Скрыть |
| `protection` | Защита от редактирования |
| `indent` | Отступ |
| `textOrientation` | Поворот текста, в десятых долях градуса (`900` = 90°) |
Значения перечислений регистр не различают: `"center"` и `"Center"` равнозначны.
```json
"styles": {
"шапка": {
"font": "жирный",
"horizontalAlignment": "Center",
"verticalAlignment": "Center",
"textPlacement": "Wrap",
"backColor": "#EBEBEB"
},
"итог": { "font": "жирный", "topBorder": "Solid", "horizontalAlignment": "Right" }
}
```
## Цвет
Строка в одной из четырёх форм — это нотация самой платформы:
| Форма | Значение |
|-------|----------|
| `#RRGGBB` | RGB-hex, напр. `#FFFFC0` |
| `style:ИмяСтиля` | Элемент стиля конфигурации или платформы, напр. `style:FormBackColor` |
| `web:Имя` | Цвет из web-палитры, напр. `web:Gainsboro`, `web:FireBrick` |
| `win:Имя` | Системный цвет Windows, напр. `win:ButtonText` |
Имя должно существовать в своей палитре — несуществующее платформа отвергнет при загрузке.
## Рамка
Пять ключей: `border` — все четыре стороны сразу, `leftBorder` / `topBorder` / `rightBorder` /
`bottomBorder` — по отдельности. Значение одинаковое у всех:
| Запись | Значение |
|--------|----------|
| `"Solid"` | Стиль линии, ширина 1 |
| `{ "style": "Solid", "width": 2 }` | Стиль и ширина |
Стили линии: `Solid`, `None`, `Dotted`, `ThinDashed`, `LargeDashed`, `ThickDashed`, `Double`.
Задавать стороны по отдельности можно всегда: если все четыре совпали, компилятор сам свернёт
их в один `border` — так это хранит платформа.
```json
"рамка-снизу": { "bottomBorder": "Dotted" },
"рамка-вокруг": { "border": { "style": "Solid", "width": 2 } }
```
## Колоночные раскладки (`columnSets`)
Группа строк может иметь собственные ширины колонок — в 1С это «индивидуальная ширина колонок».
Документные `columns` и `columnWidths` описывают раскладку по умолчанию; дополнительные
объявляются в `columnSets`, а область ссылается на нужную ключом `columnSet` — так же, как
ячейка ссылается на `styles` через `style`.
```json
{
"columns": 52,
"columnWidths": { "1": 8 },
"columnSets": {
"таблица": { "columns": 52, "columnWidths": { "1": 7, "2-52": 24 } }
},
"areas": [
{ "name": "Шапка", "rows": [ ] },
{ "name": "ТабличнаяЧасть", "columnSet": "таблица", "rows": [ ] }
]
}
```
Раскладка описывается той же парой полей, что и документная: `columns` — количество колонок
(у раскладок оно обычно разное), `columnWidths` — ширины. Ключ словаря — имя раскладки;
в макетах, полученных декомпиляцией, это идентификатор из исходного файла, при описании
с нуля — любая строка.
Все строки области получают раскладку области, поэтому одна область не может смешивать
раскладки. Позиции колонок (`col`, `span`) проверяются по ширине раскладки СВОЕЙ области,
а не документной.
Ссылка на необъявленную раскладку → ненулевой код выхода и сообщение в stderr.
## Стиль колонки (`columnStyles`)
Колонка несёт то же оформление, что ячейка и строка. Ключи — та же грамматика диапазонов,
что у `columnWidths`; значение — имя стиля.
```json
"columnWidths": { "1": 30, "2-3": 15 },
"columnStyles": { "1": "по-центру", "4": "скрытая" }
```
Внутри `columnSets` работает тот же ключ. Ширина и стиль независимы: колонка может иметь
только ширину, только стиль или и то, и другое.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -29,7 +29,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-decompile.ps1" -TemplatePath "<путь>/Template.xml" [-OutputPath "<путь>.json"] python "${CLAUDE_SKILL_DIR}/scripts/mxl-decompile.py" -TemplatePath "<путь>/Template.xml" [-OutputPath "<путь>.json"]
``` ```
## Рабочий процесс ## Рабочий процесс
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -38,12 +38,12 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-info.ps1" -TemplatePath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/mxl-info.py" -TemplatePath "<путь>"
``` ```
Или по имени обработки/макета: Или по имени обработки/макета:
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-info.ps1" -ProcessorName "<Имя>" -TemplateName "<Макет>" [-SrcDir "<каталог>"] python "${CLAUDE_SKILL_DIR}/scripts/mxl-info.py" -ProcessorName "<Имя>" -TemplateName "<Макет>" [-SrcDir "<каталог>"]
``` ```
Дополнительные флаги: Дополнительные флаги:
+2 -2
View File
@@ -23,7 +23,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-validate.ps1" -TemplatePath "Catalogs/Номенклатура/Templates/Макет" python "${CLAUDE_SKILL_DIR}/scripts/mxl-validate.py" -TemplatePath "Catalogs/Номенклатура/Templates/Макет"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-validate.ps1" -TemplatePath "src/МояОбработка/Templates/ПечатнаяФорма" python "${CLAUDE_SKILL_DIR}/scripts/mxl-validate.py" -TemplatePath "src/МояОбработка/Templates/ПечатнаяФорма"
``` ```
+1 -1
View File
@@ -21,7 +21,7 @@ allowed-tools:
| `OutputDir` | Корень выгрузки конфигурации (где `Configuration.xml`, `Roles/` и т.д.) | | `OutputDir` | Корень выгрузки конфигурации (где `Configuration.xml`, `Roles/` и т.д.) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -JsonPath "<json>" -OutputDir "<ConfigDir>" python "${CLAUDE_SKILL_DIR}/scripts/role-compile.py" -JsonPath "<json>" -OutputDir "<ConfigDir>"
``` ```
Создаёт `{OutputDir}/Roles/Имя.xml` и `{OutputDir}/Roles/Имя/Ext/Rights.xml`. Регистрирует `<Role>` в `Configuration.xml`. Создаёт `{OutputDir}/Roles/Имя.xml` и `{OutputDir}/Roles/Имя/Ext/Rights.xml`. Регистрирует `<Role>` в `Configuration.xml`.
+1 -1
View File
@@ -22,7 +22,7 @@ allowed-tools:
## Запуск скрипта ## Запуск скрипта
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-info.ps1" -RightsPath <path> -OutFile <output.txt> python "${CLAUDE_SKILL_DIR}/scripts/role-info.py" -RightsPath <path> -OutFile <output.txt>
``` ```
### Параметры ### Параметры
+1 -1
View File
@@ -23,5 +23,5 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-validate.ps1" -RightsPath "Roles/МояРоль" python "${CLAUDE_SKILL_DIR}/scripts/role-validate.py" -RightsPath "Roles/МояРоль"
``` ```
+2 -2
View File
@@ -23,10 +23,10 @@ allowed-tools:
```powershell ```powershell
# Из файла # Из файла
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-compile.ps1" -DefinitionFile "<json>" -OutputPath "<Template.xml>" python "${CLAUDE_SKILL_DIR}/scripts/skd-compile.py" -DefinitionFile "<json>" -OutputPath "<Template.xml>"
# Из строки (без промежуточного файла) # Из строки (без промежуточного файла)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-compile.ps1" -Value '<json-string>' -OutputPath "<Template.xml>" python "${CLAUDE_SKILL_DIR}/scripts/skd-compile.py" -Value '<json-string>' -OutputPath "<Template.xml>"
``` ```
## JSON DSL — краткий справочник ## JSON DSL — краткий справочник
+1 -1
View File
@@ -31,7 +31,7 @@ allowed-tools:
| `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout | | `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-decompile.ps1" -TemplatePath "<Template.xml>" -OutputPath "<out.json>" python "${CLAUDE_SKILL_DIR}/scripts/skd-decompile.py" -TemplatePath "<Template.xml>" -OutputPath "<out.json>"
``` ```
## Что получаешь ## Что получаешь
+1 -1
View File
@@ -25,7 +25,7 @@ allowed-tools:
| `NoSelection` | (опц.) Не добавлять поле в selection варианта | | `NoSelection` | (опц.) Не добавлять поле в selection варианта |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-edit.ps1" -TemplatePath "<path>" -Operation <op> -Value "<value>" python "${CLAUDE_SKILL_DIR}/scripts/skd-edit.py" -TemplatePath "<path>" -Operation <op> -Value "<value>"
``` ```
## Пакетный режим (batch) ## Пакетный режим (batch)
+1 -1
View File
@@ -1,4 +1,4 @@
# skd-edit v1.36 — Atomic 1C DCS editor (+esc_xml/esc_xml_text: разное экранирование атрибута и текста) # skd-edit v1.37 — Atomic 1C DCS editor (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# NB: парный .py собирает выражения автодат вне f-string ради совместимости с python 3.9 (PEP 701). # NB: парный .py собирает выражения автодат вне f-string ради совместимости с python 3.9 (PEP 701).
param( param(
+4 -4
View File
@@ -1,4 +1,4 @@
# skd-edit v1.36 — Atomic 1C DCS editor (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста) # skd-edit v1.37 — Atomic 1C DCS editor (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -2437,7 +2437,7 @@ elif operation == "rename-parameter":
new_name = m_rn.group(2).strip() new_name = m_rn.group(2).strip()
if old_name == new_name: if old_name == new_name:
print('[WARN] rename-parameter: old and new names are equal -- skipped') print('[WARN] rename-parameter: old and new names are equal skipped')
continue continue
# 1. Rename <parameter><name>OldName</name> # 1. Rename <parameter><name>OldName</name>
@@ -2494,7 +2494,7 @@ elif operation == "reorder-parameters":
for val in values: for val in values:
order = [s.strip() for s in val.split(",") if s.strip()] order = [s.strip() for s in val.split(",") if s.strip()]
if not order: if not order:
print('[WARN] reorder-parameters: empty list -- skipped') print('[WARN] reorder-parameters: empty list skipped')
continue continue
all_params = [] all_params = []
@@ -2647,7 +2647,7 @@ elif operation == "add-selection":
break break
if is_dup: if is_dup:
target = f'group "{group_name}"' if group_name else f'variant "{var_name}"' target = f'group "{group_name}"' if group_name else f'variant "{var_name}"'
print(f'[WARN] SelectedItemAuto already exists in {target} -- skipped') print(f'[WARN] SelectedItemAuto already exists in {target} skipped')
continue continue
sel_indent = get_container_child_indent(selection) sel_indent = get_container_child_indent(selection)
+1 -1
View File
@@ -25,7 +25,7 @@ allowed-tools:
| `OutFile` | Записать результат в файл (UTF-8 BOM) | | `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-info.ps1" -TemplatePath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/skd-info.py" -TemplatePath "<путь>"
``` ```
С указанием режима: С указанием режима:
+2 -2
View File
@@ -24,6 +24,6 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-validate.ps1" -TemplatePath "src/МойОтчёт/Templates/ОсновнаяСхема" python "${CLAUDE_SKILL_DIR}/scripts/skd-validate.py" -TemplatePath "src/МойОтчёт/Templates/ОсновнаяСхема"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-validate.ps1" -TemplatePath "Catalogs/Номенклатура/Templates/СКД/Ext/Template.xml" python "${CLAUDE_SKILL_DIR}/scripts/skd-validate.py" -TemplatePath "Catalogs/Номенклатура/Templates/СКД/Ext/Template.xml"
``` ```
+1 -1
View File
@@ -24,7 +24,7 @@ allowed-tools:
| `NoValidate` | Пропустить авто-валидацию | | `NoValidate` | Пропустить авто-валидацию |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/subsystem-compile.ps1" -Value '<json>' -OutputDir '<ConfigDir>' python "${CLAUDE_SKILL_DIR}/scripts/subsystem-compile.py" -Value '<json>' -OutputDir '<ConfigDir>'
``` ```
## JSON-определение ## JSON-определение
+1 -1
View File
@@ -24,7 +24,7 @@ allowed-tools:
| `NoValidate` | Пропустить авто-валидацию | | `NoValidate` | Пропустить авто-валидацию |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/subsystem-edit.ps1" -SubsystemPath '<path>' -Operation add-content -Value 'Catalog.Товары' python "${CLAUDE_SKILL_DIR}/scripts/subsystem-edit.py" -SubsystemPath '<path>' -Operation add-content -Value 'Catalog.Товары'
``` ```
## Операции ## Операции
+1 -1
View File
@@ -23,7 +23,7 @@ allowed-tools:
| `OutFile` | Записать результат в файл (UTF-8 BOM) | | `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/subsystem-info.ps1" -SubsystemPath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/subsystem-info.py" -SubsystemPath "<путь>"
``` ```
## Пять режимов ## Пять режимов
+2 -2
View File
@@ -24,6 +24,6 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/subsystem-validate.ps1" -SubsystemPath "Subsystems/Продажи" python "${CLAUDE_SKILL_DIR}/scripts/subsystem-validate.py" -SubsystemPath "Subsystems/Продажи"
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/subsystem-validate.ps1" -SubsystemPath "Subsystems/Продажи.xml" python "${CLAUDE_SKILL_DIR}/scripts/subsystem-validate.py" -SubsystemPath "Subsystems/Продажи.xml"
``` ```
+1 -1
View File
@@ -28,7 +28,7 @@ allowed-tools:
`-Path` — тот же путь, который отклонил `support-guard` (объект, форма, макет или каталог дампа). `-Path` — тот же путь, который отклонил `support-guard` (объект, форма, макет или каталог дампа).
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/support-edit.ps1" -Path "Catalogs/Контрагенты.xml" -Set editable python "${CLAUDE_SKILL_DIR}/scripts/support-edit.py" -Path "Catalogs/Контрагенты.xml" -Set editable
``` ```
## editable или off-support? ## editable или off-support?
+2 -2
View File
@@ -33,7 +33,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-template.ps1" -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" -TemplateType "<TemplateType>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-SetMainSKD] python "${CLAUDE_SKILL_DIR}/scripts/add-template.py" -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" -TemplateType "<TemplateType>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-SetMainSKD]
``` ```
## Пример ## Пример
@@ -41,7 +41,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-template.ps1" -
Добавить основную СКД к отчёту в расширении: Добавить основную СКД к отчёту в расширении:
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-template.ps1" -ObjectName "ОтчётПродажи" -TemplateName "ОсновнаяСхемаКомпоновкиДанных" -TemplateType "DataCompositionSchema" -SrcDir "src/cfe/МоёРасширение/Reports" python "${CLAUDE_SKILL_DIR}/scripts/add-template.py" -ObjectName "ОтчётПродажи" -TemplateName "ОсновнаяСхемаКомпоновкиДанных" -TemplateType "DataCompositionSchema" -SrcDir "src/cfe/МоёРасширение/Reports"
``` ```
## Маппинг типов ## Маппинг типов
+1 -1
View File
@@ -31,7 +31,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/remove-template.ps1" -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" [-SrcDir "<SrcDir>"] python "${CLAUDE_SKILL_DIR}/scripts/remove-template.py" -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" [-SrcDir "<SrcDir>"]
``` ```
## Что удаляется ## Что удаляется
+3 -3
View File
@@ -26,7 +26,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-info.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/web-info.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -62,8 +62,8 @@ Apache, но не публикаций. Убедиться в конкретно
```powershell ```powershell
# Статус по умолчанию # Статус по умолчанию
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-info.ps1" python "${CLAUDE_SKILL_DIR}/scripts/web-info.py"
# Указать путь к Apache # Указать путь к Apache
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-info.ps1" -ApachePath "C:\tools\apache24" python "${CLAUDE_SKILL_DIR}/scripts/web-info.py" -ApachePath "C:\tools\apache24"
``` ```
+5 -5
View File
@@ -41,7 +41,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-publish.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/web-publish.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -92,14 +92,14 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-publish.ps1" <
```powershell ```powershell
# Файловая база # Файловая база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-publish.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" python "${CLAUDE_SKILL_DIR}/scripts/web-publish.py" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
# С явным именем публикации и портом # С явным именем публикации и портом
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-publish.ps1" -InfoBasePath "C:\Bases\MyDB" -AppName "mydb" -Port 9090 python "${CLAUDE_SKILL_DIR}/scripts/web-publish.py" -InfoBasePath "C:\Bases\MyDB" -AppName "mydb" -Port 9090
# Серверная база # Серверная база
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-publish.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" python "${CLAUDE_SKILL_DIR}/scripts/web-publish.py" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret"
# Ручной режим (только инструкция) # Ручной режим (только инструкция)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-publish.ps1" -InfoBasePath "C:\Bases\MyDB" -Manual python "${CLAUDE_SKILL_DIR}/scripts/web-publish.py" -InfoBasePath "C:\Bases\MyDB" -Manual
``` ```
+3 -3
View File
@@ -26,7 +26,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-stop.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/web-stop.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -45,8 +45,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-stop.ps1" <па
```powershell ```powershell
# Остановить Apache # Остановить Apache
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-stop.ps1" python "${CLAUDE_SKILL_DIR}/scripts/web-stop.py"
# С указанием пути # С указанием пути
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-stop.ps1" -ApachePath "C:\tools\apache24" python "${CLAUDE_SKILL_DIR}/scripts/web-stop.py" -ApachePath "C:\tools\apache24"
``` ```
+4 -4
View File
@@ -35,7 +35,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.ps1" <параметры> python "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.py" <параметры>
``` ```
### Параметры скрипта ### Параметры скрипта
@@ -52,11 +52,11 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.ps1"
```powershell ```powershell
# Удалить одну публикацию # Удалить одну публикацию
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.ps1" -AppName "bpdemo" python "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.py" -AppName "bpdemo"
# Удалить все публикации # Удалить все публикации
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.ps1" -All python "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.py" -All
# С указанием пути # С указанием пути
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.ps1" -AppName "mydb" -ApachePath "C:\tools\apache24" python "${CLAUDE_SKILL_DIR}/scripts/web-unpublish.py" -AppName "mydb" -ApachePath "C:\tools\apache24"
``` ```
+1 -1
View File
@@ -28,7 +28,7 @@ allowed-tools:
| `Force` | нет | Перезаписать существующий пакет. Без него навык откажется затирать уже собранный пакет | | `Force` | нет | Перезаписать существующий пакет. Без него навык откажется затирать уже собранный пакет |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-compile.ps1" -XsdPath "<схема.xsd>" -OutputDir "<каталог-исходников>" python "${CLAUDE_SKILL_DIR}/scripts/xdto-compile.py" -XsdPath "<схема.xsd>" -OutputDir "<каталог-исходников>"
``` ```
Примеры: Примеры:
+1 -1
View File
@@ -21,7 +21,7 @@ allowed-tools:
| `OutFile` | нет | Записать схему в файл (UTF-8 с BOM). Без него — вывод в stdout | | `OutFile` | нет | Записать схему в файл (UTF-8 с BOM). Без него — вывод в stdout |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-decompile.ps1" -PackagePath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/xdto-decompile.py" -PackagePath "<путь>"
``` ```
Примеры: Примеры:
+1 -1
View File
@@ -27,7 +27,7 @@ allowed-tools:
| `NoValidate` | нет | Не запускать `xdto-validate` после правки | | `NoValidate` | нет | Не запускать `xdto-validate` после правки |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-edit.ps1" -PackagePath "<путь>" -Operation <op> -Target "<адрес>" -Value "<значение>" python "${CLAUDE_SKILL_DIR}/scripts/xdto-edit.py" -PackagePath "<путь>" -Operation <op> -Target "<адрес>" -Value "<значение>"
``` ```
## Операции ## Операции
+1 -1
View File
@@ -29,7 +29,7 @@ allowed-tools:
| `OutFile` | нет | Записать результат в файл (UTF-8 BOM) | | `OutFile` | нет | Записать результат в файл (UTF-8 BOM) |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-info.ps1" -PackagePath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/xdto-info.py" -PackagePath "<путь>"
``` ```
## Что показывает ## Что показывает
+1 -1
View File
@@ -24,7 +24,7 @@ allowed-tools:
| `OutFile` | нет | Записать отчёт в файл | | `OutFile` | нет | Записать отчёт в файл |
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-validate.ps1" -PackagePath "<путь>" python "${CLAUDE_SKILL_DIR}/scripts/xdto-validate.py" -PackagePath "<путь>"
``` ```
`[ERROR]` — платформа такой пакет не примет либо примет неправильно. `[ERROR]` — платформа такой пакет не примет либо примет неправильно.
-7
View File
@@ -1,7 +0,0 @@
# Коммиты, которые git blame должен «проскакивать» (механические правки —
# не меняют авторство содержимого). Включить локально:
# git config blame.ignoreRevsFile .git-blame-ignore-revs
# GitHub/GitLab уважают этот файл в blame-UI автоматически.
# chore(repo): нормализация EOL к LF + .gitattributes
26888a07d58351755fb8e487727c00d5b611eb95
-28
View File
@@ -1,28 +0,0 @@
# EOL policy
# ─────────────────────────────────────────────────────────────────────────────
# Авторский контент нормализуем к LF: инструмент правки (Edit) всегда пишет LF,
# поэтому единый LF убирает EOL-шум в диффах и ловушку «не правь CRLF-файл».
# git с eol=lf конвертит ТОЛЬКО CR<->LF и не трогает BOM (BOM — байты контента),
# поэтому BOM на .ps1 сохраняется.
*.ps1 text eol=lf
*.psm1 text eol=lf
*.py text eol=lf
*.mjs text eol=lf
*.md text eol=lf
*.json text eol=lf
.gitignore text eol=lf
# .bsl уже целиком LF — пин фиксирует статус-кво от будущего дрейфа.
*.bsl text eol=lf
# Данные 1С НЕ трогаем. *.xml — реальные выгрузки 1С (EOL местами значим,
# правим не мы, а навыки): оставляем как есть, под управление не берём.
# autocrlf=false и отсутствие text-атрибута => git хранит их байты как есть.
# Бинарники 1С
*.bin binary
# Package.bin пакетов XDTO — текстовый XML, несмотря на расширение. Оставляем
# под правилом *.bin binary (байты не нормализуются), но включаем текстовый diff,
# иначе изменение модели пакета в истории выглядит как «Binary files differ».
XDTOPackages/**/Package.bin diff
-26
View File
@@ -1,26 +0,0 @@
# 1C Skills for {{PLATFORM_LABEL}} ({{RUNTIME_LABEL}})
Автоматическая сборка из [main]({{MAIN_REPO_URL}}) — навыки 1С:Предприятие 8.3 для AI-агента **{{PLATFORM_LABEL}}** с рантаймом **{{RUNTIME_LABEL}}**.
> Эта ветка генерируется CI на каждый push в main. **Не редактируйте напрямую** — все правки идут в [main]({{MAIN_REPO_URL}}).
## Установка
1. Скачайте ZIP этой ветки: **Code → Download ZIP** (или `git archive`).
2. Распакуйте в корень своего проекта — должна появиться папка `{{PLATFORM_DIR}}/`.
3. Запустите {{PLATFORM_LABEL}} из этого проекта — навыки станут доступны.
## Требования
{{RUNTIME_REQUIREMENTS}}
- **1С:Предприятие 8.3** — для сборки/разборки EPF/ERF и работы с базами.
- **Node.js 18+** — для `/web-test`.
## Документация
Полные гайды, спецификации и описание навыков — в [main]({{MAIN_REPO_URL}}).
---
Source: {{MAIN_REPO_URL}}
Build commit: `{{COMMIT_SHA}}`
-31
View File
@@ -1,31 +0,0 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "{{PLUGIN_NAME}}",
"description": "[Python] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент. Linux/Mac или когда PowerShell недоступен.",
"author": {
"name": "Nikolay Shirokov"
},
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"license": "MIT",
"keywords": [
"1c",
"1c-dev",
"cf",
"cfe",
"epf",
"erf",
"metadata",
"configuration",
"extension",
"form",
"report",
"skd",
"data-processor",
"mxl",
"web-client",
"testing",
"test-automation"
],
"skills": "./.claude/skills/"
}
-36
View File
@@ -1,36 +0,0 @@
{
"name": "{{PLUGIN_NAME}}",
"version": "{{VERSION}}",
"description": "[{{RUNTIME_LABEL}}] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
"author": {
"name": "Nikolay Shirokov"
},
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
"license": "MIT",
"keywords": [
"1c",
"1c-dev",
"cf",
"cfe",
"epf",
"erf",
"metadata",
"configuration",
"extension",
"form",
"report",
"skd",
"data-processor",
"mxl",
"web-client",
"testing",
"test-automation"
],
"skills": "./.codex/skills/",
"interface": {
"displayName": "1C Skills ({{RUNTIME_LABEL}})",
"shortDescription": "{{SHORT_DESCRIPTION}}",
"category": "Development"
}
}
-246
View File
@@ -1,246 +0,0 @@
name: Build port branches
on:
push:
branches: [main]
paths:
- '.claude/skills/**'
- 'scripts/switch.py'
- 'requirements.txt'
- '.github/templates/README.port.md.tmpl'
- '.github/templates/codex-plugin.json.tmpl'
- '.github/templates/claude-plugin.json.tmpl'
- '.github/workflows/build-ports.yml'
- 'LICENSE'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- platform: claude-code
runtime: python
branch: port-claude-code-py
label: Claude Code
target_dir: .claude/skills
- platform: cursor
runtime: powershell
branch: port-cursor
label: Cursor
target_dir: .cursor/skills
- platform: cursor
runtime: python
branch: port-cursor-py
label: Cursor
target_dir: .cursor/skills
- platform: codex
runtime: powershell
branch: port-codex
label: Codex
target_dir: .codex/skills
- platform: codex
runtime: python
branch: port-codex-py
label: Codex
target_dir: .codex/skills
- platform: copilot
runtime: powershell
branch: port-copilot
label: GitHub Copilot
target_dir: .github/skills
- platform: copilot
runtime: python
branch: port-copilot-py
label: GitHub Copilot
target_dir: .github/skills
- platform: augment
runtime: powershell
branch: port-augment
label: Augment
target_dir: .augment/skills
- platform: augment
runtime: python
branch: port-augment-py
label: Augment
target_dir: .augment/skills
- platform: cline
runtime: powershell
branch: port-cline
label: Cline
target_dir: .cline/skills
- platform: cline
runtime: python
branch: port-cline-py
label: Cline
target_dir: .cline/skills
- platform: kilo
runtime: powershell
branch: port-kilo
label: Kilo Code
target_dir: .kilocode/skills
- platform: kilo
runtime: python
branch: port-kilo-py
label: Kilo Code
target_dir: .kilocode/skills
- platform: kiro
runtime: powershell
branch: port-kiro
label: Kiro
target_dir: .kiro/skills
- platform: kiro
runtime: python
branch: port-kiro-py
label: Kiro
target_dir: .kiro/skills
- platform: gemini
runtime: powershell
branch: port-gemini
label: Gemini CLI
target_dir: .gemini/skills
- platform: gemini
runtime: python
branch: port-gemini-py
label: Gemini CLI
target_dir: .gemini/skills
- platform: opencode
runtime: powershell
branch: port-opencode
label: OpenCode
target_dir: .opencode/skills
- platform: opencode
runtime: python
branch: port-opencode-py
label: OpenCode
target_dir: .opencode/skills
- platform: roo
runtime: powershell
branch: port-roo
label: Roo Code
target_dir: .roo/skills
- platform: roo
runtime: python
branch: port-roo-py
label: Roo Code
target_dir: .roo/skills
- platform: windsurf
runtime: powershell
branch: port-windsurf
label: Windsurf
target_dir: .windsurf/skills
- platform: windsurf
runtime: python
branch: port-windsurf-py
label: Windsurf
target_dir: .windsurf/skills
- platform: codeassistant
runtime: powershell
branch: port-codeassistant
label: Yandex Code Assistant
target_dir: .codeassistant/skills
- platform: codeassistant
runtime: python
branch: port-codeassistant-py
label: Yandex Code Assistant
target_dir: .codeassistant/skills
- platform: agents
runtime: powershell
branch: port-agents
label: Agent Skills
target_dir: .agents/skills
- platform: agents
runtime: python
branch: port-agents-py
label: Agent Skills
target_dir: .agents/skills
steps:
- name: Checkout main
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Build skills tree for ${{ matrix.platform }} (${{ matrix.runtime }})
run: |
python scripts/switch.py "${{ matrix.platform }}" \
--project-dir build \
--runtime "${{ matrix.runtime }}"
- name: Render port README
env:
PLATFORM_LABEL: ${{ matrix.label }}
PLATFORM_DIR: ${{ matrix.target_dir }}
RUNTIME: ${{ matrix.runtime }}
RUNTIME_LABEL: ${{ matrix.runtime == 'powershell' && 'PowerShell' || 'Python' }}
COMMIT_SHA: ${{ github.sha }}
MAIN_REPO_URL: https://github.com/${{ github.repository }}
run: |
if [ "$RUNTIME" = "powershell" ]; then
RUNTIME_REQUIREMENTS='- **Windows** с PowerShell 5.1+ (входит в Windows).'
else
RUNTIME_REQUIREMENTS='- **Python 3.9+**. Установка зависимостей: `pip install -r requirements.txt` (lxml, Pillow, psutil).'
fi
sed \
-e "s|{{PLATFORM_LABEL}}|${PLATFORM_LABEL}|g" \
-e "s|{{PLATFORM_DIR}}|${PLATFORM_DIR}|g" \
-e "s|{{RUNTIME_LABEL}}|${RUNTIME_LABEL}|g" \
-e "s|{{RUNTIME_REQUIREMENTS}}|${RUNTIME_REQUIREMENTS}|g" \
-e "s|{{COMMIT_SHA}}|${COMMIT_SHA}|g" \
-e "s|{{MAIN_REPO_URL}}|${MAIN_REPO_URL}|g" \
.github/templates/README.port.md.tmpl > build/README.md
- name: Render Codex plugin manifest
if: matrix.platform == 'codex'
env:
PLUGIN_NAME: ${{ matrix.runtime == 'python' && '1c-skills-py' || '1c-skills' }}
RUNTIME_LABEL: ${{ matrix.runtime == 'powershell' && 'PowerShell' || 'Python' }}
SHORT_DESCRIPTION: ${{ matrix.runtime == 'python' && 'Python runtime (Linux/Mac/Windows)' || 'PowerShell runtime (Windows-first)' }}
COMMIT_SHA: ${{ github.sha }}
run: |
VERSION="$(date -u +%Y.%-m.%-d)+${COMMIT_SHA::7}"
mkdir -p build/.codex-plugin
sed \
-e "s|{{PLUGIN_NAME}}|${PLUGIN_NAME}|g" \
-e "s|{{VERSION}}|${VERSION}|g" \
-e "s|{{RUNTIME_LABEL}}|${RUNTIME_LABEL}|g" \
-e "s|{{SHORT_DESCRIPTION}}|${SHORT_DESCRIPTION}|g" \
.github/templates/codex-plugin.json.tmpl > build/.codex-plugin/plugin.json
- name: Render Claude plugin manifest (Py variant)
if: matrix.platform == 'claude-code' && matrix.runtime == 'python'
env:
PLUGIN_NAME: 1c-skills-py
run: |
mkdir -p build/.claude-plugin
sed -e "s|{{PLUGIN_NAME}}|${PLUGIN_NAME}|g" \
.github/templates/claude-plugin.json.tmpl > build/.claude-plugin/plugin.json
- name: Copy LICENSE
run: cp LICENSE build/LICENSE
- name: Copy requirements.txt (Python builds only)
if: matrix.runtime == 'python'
run: cp requirements.txt build/requirements.txt
- name: Force-push orphan snapshot to ${{ matrix.branch }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd build
git init -q -b master
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -q -m "Auto-build: ${{ matrix.platform }} (${{ matrix.runtime }}) from ${GITHUB_SHA::7}"
git push --force \
"https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" \
"master:${{ matrix.branch }}"

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