mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-13 15:03:20 +03:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d5dad7b05 |
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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 недоступен."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Операции
|
## Операции
|
||||||
|
|||||||
@@ -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 "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Три режима
|
## Три режима
|
||||||
|
|||||||
@@ -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 "МояКонфигурация"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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 "Новая база"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 "МоёРасширение"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Связанные навыки
|
## Связанные навыки
|
||||||
|
|||||||
@@ -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 "МоёРасширение"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 "МоёРасширение"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Связанные навыки
|
## Связанные навыки
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 "ЗапуститьОбновление"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 "МоёРасширение"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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>"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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 — назначение формы
|
||||||
|
|||||||
@@ -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"]}">')
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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 формат
|
||||||
|
|||||||
@@ -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>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|||||||
@@ -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>"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Что удаляется
|
## Что удаляется
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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>"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Что делает скрипт
|
## Что делает скрипт
|
||||||
|
|||||||
@@ -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>'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Операции
|
## Операции
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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>"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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>"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
|
|||||||
@@ -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 — добавить элементы
|
||||||
|
|||||||
@@ -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 "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Три режима
|
## Три режима
|
||||||
|
|||||||
@@ -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.Товары"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Поддерживаемые типы
|
## Поддерживаемые типы
|
||||||
|
|||||||
@@ -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/Заказ"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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": [
|
||||||
|
["Вид", "Остаток", ">", "Итог"],
|
||||||
|
["|", "начало", "конец", "|"],
|
||||||
|
["{Вид}", "{Нач}", "{Кон}", "{Итог}"]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 "<каталог>"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Дополнительные флаги:
|
Дополнительные флаги:
|
||||||
|
|||||||
@@ -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/ПечатнаяФорма"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
@@ -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>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры
|
### Параметры
|
||||||
|
|||||||
@@ -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/МояРоль"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 — краткий справочник
|
||||||
|
|||||||
@@ -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>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Что получаешь
|
## Что получаешь
|
||||||
|
|||||||
@@ -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,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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
С указанием режима:
|
С указанием режима:
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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-определение
|
||||||
|
|||||||
@@ -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.Товары'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Операции
|
## Операции
|
||||||
|
|||||||
@@ -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 "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Пять режимов
|
## Пять режимов
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Маппинг типов
|
## Маппинг типов
|
||||||
|
|||||||
@@ -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>"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Что удаляется
|
## Что удаляется
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 "<каталог-исходников>"
|
||||||
```
|
```
|
||||||
|
|
||||||
Примеры:
|
Примеры:
|
||||||
|
|||||||
@@ -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 "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
Примеры:
|
Примеры:
|
||||||
|
|||||||
@@ -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 "<значение>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Операции
|
## Операции
|
||||||
|
|||||||
@@ -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 "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Что показывает
|
## Что показывает
|
||||||
|
|||||||
@@ -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]` — платформа такой пакет не примет либо примет неправильно.
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# Коммиты, которые git blame должен «проскакивать» (механические правки —
|
|
||||||
# не меняют авторство содержимого). Включить локально:
|
|
||||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
|
||||||
# GitHub/GitLab уважают этот файл в blame-UI автоматически.
|
|
||||||
|
|
||||||
# chore(repo): нормализация EOL к LF + .gitattributes
|
|
||||||
26888a07d58351755fb8e487727c00d5b611eb95
|
|
||||||
@@ -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
|
|
||||||
@@ -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}}`
|
|
||||||
@@ -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/"
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
Reference in New Issue
Block a user