mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-26 06:54:38 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e45de42c1 |
@@ -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,31 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
||||||
"name": "1c-skills",
|
|
||||||
"description": "[PowerShell] Навыки для разработки на 1С:Предприятие 8.3 — абстракции над XML-форматами и CLI конфигуратора, плюс глаза и руки для тестирования через веб-клиент.",
|
|
||||||
"author": {
|
|
||||||
"name": "Nikolay Shirokov"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"repository": "https://github.com/Nikolay-Shirokov/cc-1c-skills",
|
|
||||||
"license": "MIT",
|
|
||||||
"keywords": [
|
|
||||||
"1c",
|
|
||||||
"1c-dev",
|
|
||||||
"cf",
|
|
||||||
"cfe",
|
|
||||||
"epf",
|
|
||||||
"erf",
|
|
||||||
"metadata",
|
|
||||||
"configuration",
|
|
||||||
"extension",
|
|
||||||
"form",
|
|
||||||
"report",
|
|
||||||
"skd",
|
|
||||||
"data-processor",
|
|
||||||
"mxl",
|
|
||||||
"web-client",
|
|
||||||
"testing",
|
|
||||||
"test-automation"
|
|
||||||
],
|
|
||||||
"skills": "./.claude/skills/"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,138 +0,0 @@
|
|||||||
# help-add v1.4 — Add built-in help to 1C object
|
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
|
||||||
param(
|
|
||||||
[Parameter(Mandatory)]
|
|
||||||
[string]$ObjectName,
|
|
||||||
|
|
||||||
[string]$Lang = "ru",
|
|
||||||
|
|
||||||
[string]$SrcDir = "src"
|
|
||||||
)
|
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
||||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
||||||
|
|
||||||
# --- Detect format version ---
|
|
||||||
|
|
||||||
function Detect-FormatVersion([string]$dir) {
|
|
||||||
$d = $dir
|
|
||||||
while ($d) {
|
|
||||||
$cfgPath = Join-Path $d "Configuration.xml"
|
|
||||||
if (Test-Path $cfgPath) {
|
|
||||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
|
||||||
}
|
|
||||||
|
|
||||||
$formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path
|
|
||||||
|
|
||||||
# --- Проверки ---
|
|
||||||
|
|
||||||
$objectDir = Join-Path $SrcDir $ObjectName
|
|
||||||
$extDir = Join-Path $objectDir "Ext"
|
|
||||||
|
|
||||||
if (-not (Test-Path $extDir)) {
|
|
||||||
Write-Error "Каталог объекта не найден: $extDir. Проверьте путь ObjectName (например Catalogs/МойСправочник)."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
$helpXmlPath = Join-Path $extDir "Help.xml"
|
|
||||||
if (Test-Path $helpXmlPath) {
|
|
||||||
Write-Error "Справка уже существует: $helpXmlPath"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Кодировка ---
|
|
||||||
|
|
||||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
|
||||||
|
|
||||||
# --- 1. Help.xml ---
|
|
||||||
|
|
||||||
$helpXml = @"
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
|
||||||
<Page>$Lang</Page>
|
|
||||||
</Help>
|
|
||||||
"@
|
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($helpXmlPath, $helpXml, $encBom)
|
|
||||||
|
|
||||||
# --- 2. Help/<lang>.html ---
|
|
||||||
|
|
||||||
$helpDir = Join-Path $extDir "Help"
|
|
||||||
New-Item -ItemType Directory -Path $helpDir -Force | Out-Null
|
|
||||||
|
|
||||||
$helpHtmlPath = Join-Path $helpDir "$Lang.html"
|
|
||||||
|
|
||||||
$helpHtml = @"
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="v8help://service_book/service_style"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>$ObjectName</h1>
|
|
||||||
<p>Описание.</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"@
|
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($helpHtmlPath, $helpHtml, $encBom)
|
|
||||||
|
|
||||||
# --- 3. Проверка IncludeHelpInContents в метаданных форм ---
|
|
||||||
|
|
||||||
$formsDir = Join-Path $objectDir "Forms"
|
|
||||||
if (Test-Path $formsDir) {
|
|
||||||
$formMetaFiles = Get-ChildItem -Path $formsDir -Filter "*.xml" -File
|
|
||||||
foreach ($formMeta in $formMetaFiles) {
|
|
||||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
|
||||||
$xmlDoc.PreserveWhitespace = $true
|
|
||||||
$xmlDoc.Load($formMeta.FullName)
|
|
||||||
|
|
||||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
|
||||||
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
|
||||||
|
|
||||||
$includeHelp = $xmlDoc.SelectSingleNode("//md:IncludeHelpInContents", $nsMgr)
|
|
||||||
if (-not $includeHelp) {
|
|
||||||
# Добавить после <FormType>
|
|
||||||
$formType = $xmlDoc.SelectSingleNode("//md:FormType", $nsMgr)
|
|
||||||
if ($formType) {
|
|
||||||
$newElem = $xmlDoc.CreateElement("IncludeHelpInContents", "http://v8.1c.ru/8.3/MDClasses")
|
|
||||||
$newElem.InnerText = "false"
|
|
||||||
$parent = $formType.ParentNode
|
|
||||||
$nextSibling = $formType.NextSibling
|
|
||||||
# Вставить перенос + табуляцию + элемент
|
|
||||||
$ws = $xmlDoc.CreateWhitespace("`n`t`t`t")
|
|
||||||
if ($nextSibling) {
|
|
||||||
$parent.InsertBefore($ws, $nextSibling) | Out-Null
|
|
||||||
$parent.InsertBefore($newElem, $ws) | Out-Null
|
|
||||||
} else {
|
|
||||||
$parent.AppendChild($ws) | Out-Null
|
|
||||||
$parent.AppendChild($newElem) | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
$settings = New-Object System.Xml.XmlWriterSettings
|
|
||||||
$settings.Encoding = $encBom
|
|
||||||
$settings.Indent = $false
|
|
||||||
$stream = New-Object System.IO.FileStream($formMeta.FullName, [System.IO.FileMode]::Create)
|
|
||||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
|
||||||
$xmlDoc.Save($writer)
|
|
||||||
$writer.Close()
|
|
||||||
$stream.Close()
|
|
||||||
|
|
||||||
Write-Host " IncludeHelpInContents добавлен: $($formMeta.Name)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "[OK] Создана справка: $ObjectName"
|
|
||||||
Write-Host " Метаданные: $helpXmlPath"
|
|
||||||
Write-Host " Страница: $helpHtmlPath"
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# add-help v1.4 — Add built-in help to 1C object
|
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
|
||||||
|
|
||||||
|
|
||||||
def detect_format_version(d):
|
|
||||||
while d:
|
|
||||||
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 save_xml_with_bom(tree, path):
|
|
||||||
"""Save XML tree to file with UTF-8 BOM."""
|
|
||||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
|
||||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
|
||||||
if not xml_bytes.endswith(b"\n"):
|
|
||||||
xml_bytes += b"\n"
|
|
||||||
with open(path, "wb") as f:
|
|
||||||
f.write(b"\xef\xbb\xbf")
|
|
||||||
f.write(xml_bytes)
|
|
||||||
|
|
||||||
|
|
||||||
def write_text_with_bom(path, text):
|
|
||||||
"""Write text to file with UTF-8 BOM."""
|
|
||||||
with open(path, "w", encoding="utf-8-sig") as f:
|
|
||||||
f.write(text)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
|
||||||
parser = argparse.ArgumentParser(description="Add built-in help to 1C object", allow_abbrev=False)
|
|
||||||
parser.add_argument("-ObjectName", required=True)
|
|
||||||
parser.add_argument("-Lang", default="ru")
|
|
||||||
parser.add_argument("-SrcDir", default="src")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
object_name = args.ObjectName
|
|
||||||
lang = args.Lang
|
|
||||||
src_dir = args.SrcDir
|
|
||||||
|
|
||||||
format_version = detect_format_version(os.path.abspath(src_dir))
|
|
||||||
|
|
||||||
# --- Checks ---
|
|
||||||
|
|
||||||
object_dir = os.path.join(src_dir, object_name)
|
|
||||||
ext_dir = os.path.join(object_dir, "Ext")
|
|
||||||
|
|
||||||
if not os.path.isdir(ext_dir):
|
|
||||||
print(f"Каталог объекта не найден: {ext_dir}. Проверьте путь ObjectName (например Catalogs/МойСправочник).", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
help_xml_path = os.path.join(ext_dir, "Help.xml")
|
|
||||||
if os.path.exists(help_xml_path):
|
|
||||||
print(f"Справка уже существует: {help_xml_path}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# --- 1. Help.xml ---
|
|
||||||
|
|
||||||
help_xml = (
|
|
||||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
||||||
'<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops"'
|
|
||||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
|
||||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
|
||||||
f' version="{format_version}">\n'
|
|
||||||
f'\t<Page>{lang}</Page>\n'
|
|
||||||
'</Help>'
|
|
||||||
)
|
|
||||||
|
|
||||||
write_text_with_bom(help_xml_path, help_xml)
|
|
||||||
|
|
||||||
# --- 2. Help/<lang>.html ---
|
|
||||||
|
|
||||||
help_dir = os.path.join(ext_dir, "Help")
|
|
||||||
os.makedirs(help_dir, exist_ok=True)
|
|
||||||
|
|
||||||
help_html_path = os.path.join(help_dir, f"{lang}.html")
|
|
||||||
|
|
||||||
help_html = (
|
|
||||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\n'
|
|
||||||
'<html>\n'
|
|
||||||
'<head>\n'
|
|
||||||
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>\n'
|
|
||||||
' <link rel="stylesheet" type="text/css" href="v8help://service_book/service_style"/>\n'
|
|
||||||
'</head>\n'
|
|
||||||
'<body>\n'
|
|
||||||
f' <h1>{object_name}</h1>\n'
|
|
||||||
' <p>Описание.</p>\n'
|
|
||||||
'</body>\n'
|
|
||||||
'</html>'
|
|
||||||
)
|
|
||||||
|
|
||||||
write_text_with_bom(help_html_path, help_html)
|
|
||||||
|
|
||||||
# --- 3. Check IncludeHelpInContents in form metadata ---
|
|
||||||
|
|
||||||
forms_dir = os.path.join(object_dir, "Forms")
|
|
||||||
if os.path.isdir(forms_dir):
|
|
||||||
for entry in os.listdir(forms_dir):
|
|
||||||
if not entry.endswith(".xml"):
|
|
||||||
continue
|
|
||||||
form_meta_full = os.path.join(forms_dir, entry)
|
|
||||||
if not os.path.isfile(form_meta_full):
|
|
||||||
continue
|
|
||||||
|
|
||||||
parser_xml = etree.XMLParser(remove_blank_text=False)
|
|
||||||
form_tree = etree.parse(form_meta_full, parser_xml)
|
|
||||||
form_root = form_tree.getroot()
|
|
||||||
|
|
||||||
include_help = form_root.find(".//md:IncludeHelpInContents", NSMAP)
|
|
||||||
if include_help is not None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Add after <FormType>
|
|
||||||
form_type = form_root.find(".//md:FormType", NSMAP)
|
|
||||||
if form_type is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
parent = form_type.getparent()
|
|
||||||
ns = "http://v8.1c.ru/8.3/MDClasses"
|
|
||||||
new_elem = etree.SubElement(parent, f"{{{ns}}}IncludeHelpInContents")
|
|
||||||
new_elem.text = "false"
|
|
||||||
# Remove SubElement's auto-placement (it appends to end) and insert after FormType
|
|
||||||
parent.remove(new_elem)
|
|
||||||
|
|
||||||
# Find index of FormType in parent
|
|
||||||
form_type_idx = list(parent).index(form_type)
|
|
||||||
|
|
||||||
# Insert after FormType
|
|
||||||
parent.insert(form_type_idx + 1, new_elem)
|
|
||||||
|
|
||||||
# Whitespace handling: copy FormType's tail as new_elem's tail,
|
|
||||||
# and set FormType's tail to include newline + indent
|
|
||||||
new_elem.tail = form_type.tail
|
|
||||||
form_type.tail = "\n\t\t\t"
|
|
||||||
|
|
||||||
save_xml_with_bom(form_tree, form_meta_full)
|
|
||||||
|
|
||||||
print(f" IncludeHelpInContents добавлен: {entry}")
|
|
||||||
|
|
||||||
print(f"[OK] Создана справка: {object_name}")
|
|
||||||
print(f" Метаданные: {help_xml_path}")
|
|
||||||
print(f" Страница: {help_html_path}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,246 +0,0 @@
|
|||||||
# /skd-info — полная справка по режимам
|
|
||||||
|
|
||||||
Компактное описание — в [SKILL.md](SKILL.md).
|
|
||||||
|
|
||||||
## overview (по умолчанию) — карта схемы
|
|
||||||
|
|
||||||
Компактная навигационная карта (10-25 строк). Показывает структуру и подсказывает следующие шаги:
|
|
||||||
|
|
||||||
```
|
|
||||||
=== DCS: ОсновнаяСхемаКомпоновкиДанных (362 lines) ===
|
|
||||||
|
|
||||||
Sources: ИсточникДанных1 (Local)
|
|
||||||
|
|
||||||
Datasets:
|
|
||||||
[Query] НоменклатураСЦенами 7 fields, query 40 lines
|
|
||||||
Calculated: 1
|
|
||||||
Resources: 1
|
|
||||||
Templates: 1 templates, 1 group bindings
|
|
||||||
Params: (none)
|
|
||||||
|
|
||||||
Variants:
|
|
||||||
[1] НоменклатураИЦены "Номенклатура и цены" Table(detail) 3 filters
|
|
||||||
[2] НоменклатураБезЦен "Номенклатура без цен" Group(detail) 2 filters
|
|
||||||
|
|
||||||
Next:
|
|
||||||
-Mode query query text
|
|
||||||
-Mode fields field tables by dataset
|
|
||||||
-Mode calculated calculated field expressions
|
|
||||||
-Mode resources resource aggregation
|
|
||||||
-Mode variant -Name <N> variant structure (1..2)
|
|
||||||
```
|
|
||||||
|
|
||||||
Для DataSetUnion — дерево наборов + связи:
|
|
||||||
```
|
|
||||||
Datasets:
|
|
||||||
[Union] РасчетНалогаНаИмущество 52 fields
|
|
||||||
├─ [Query] РасчетНалогаНаИмущество 51 fields, query 181 lines
|
|
||||||
├─ [Query] ДанныеПоКадастровой 29 fields, query 40 lines
|
|
||||||
├─ [Query] ДанныеПоСреднегодовой 34 fields, query 41 lines
|
|
||||||
Links: РасчетНалогаНаИмущество -> СостояниеОС (2 fields)
|
|
||||||
```
|
|
||||||
|
|
||||||
Параметры разделяются на видимые/скрытые:
|
|
||||||
```
|
|
||||||
Params: 18 (7 visible, 11 hidden): Период, Ответственный, ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## query — текст запроса
|
|
||||||
|
|
||||||
`-Name <набор>` — имя DataSet (обязателен если наборов > 1).
|
|
||||||
|
|
||||||
Извлекает raw-текст запроса с деэкранированием XML (`&`→`&`, `>`→`>`). Для пакетных запросов — оглавление батчей:
|
|
||||||
|
|
||||||
```
|
|
||||||
=== Query: ДанныеТ13 (334 lines, 13 batches) ===
|
|
||||||
Batch 1: lines 1-8 → ПОМЕСТИТЬ Представления_Периоды
|
|
||||||
Batch 2: lines 9-26 → ПОМЕСТИТЬ Представления_СотрудникиОрганизации
|
|
||||||
...
|
|
||||||
--- Batch 1 ---
|
|
||||||
ВЫБРАТЬ
|
|
||||||
ДАТАВРЕМЯ(1, 1, 1) КАК Период
|
|
||||||
ПОМЕСТИТЬ Представления_Периоды
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Фильтр по номеру батча: `-Batch 3` покажет только 3-й пакет.
|
|
||||||
|
|
||||||
## fields — поля наборов данных
|
|
||||||
|
|
||||||
Без `-Name` — карта: имена полей по наборам:
|
|
||||||
```
|
|
||||||
=== Fields map ===
|
|
||||||
СостояниеОС [Query] (3): Организация, ОсновноеСредство, ДатаСостояния
|
|
||||||
РасчетНалогаНаИмущество [Union] (52): ДоляСтоимостиЧислитель, ...
|
|
||||||
РасчетНалогаНаИмущество [Query] (51): КадастроваяСтоимость, ...
|
|
||||||
```
|
|
||||||
|
|
||||||
С `-Name <поле>` — детали конкретного поля:
|
|
||||||
```
|
|
||||||
=== Field: ДатаСостояния "Дата ввода в эксплуатацию" ===
|
|
||||||
|
|
||||||
Dataset: СостояниеОС [Query]
|
|
||||||
Format: ДФ=dd.MM.yyyy
|
|
||||||
```
|
|
||||||
|
|
||||||
Показывает: dataset, title, type, role, useRestriction, format, presentationExpression.
|
|
||||||
|
|
||||||
## links — связи наборов данных
|
|
||||||
|
|
||||||
```
|
|
||||||
=== Links (4) ===
|
|
||||||
|
|
||||||
РасчетНалогаНаИмущество -> СостояниеОС :
|
|
||||||
Организация -> Организация
|
|
||||||
ОсновноеСредство -> ОсновноеСредство
|
|
||||||
```
|
|
||||||
|
|
||||||
Группирует по парам наборов. Показывает поля связи и параметры.
|
|
||||||
|
|
||||||
## calculated — вычисляемые поля
|
|
||||||
|
|
||||||
Без `-Name` — карта: имена и заголовки:
|
|
||||||
```
|
|
||||||
=== Calculated fields (23) ===
|
|
||||||
ДоляСтоимости "Доля стоимости"
|
|
||||||
КоэффициентКи "Коэффициент Ки"
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
С `-Name <поле>` — полное выражение:
|
|
||||||
```
|
|
||||||
=== Calculated: ДоляСтоимости ===
|
|
||||||
|
|
||||||
Expression:
|
|
||||||
ВЫБОР КОГДА ... ТОГДА "1" ИНАЧЕ ... КОНЕЦ
|
|
||||||
Title: Доля стоимости
|
|
||||||
Restrict: condition
|
|
||||||
```
|
|
||||||
|
|
||||||
## resources — ресурсы (итоги по группировкам)
|
|
||||||
|
|
||||||
Без `-Name` — карта: имена полей, `*` = есть формулы по группировкам:
|
|
||||||
```
|
|
||||||
=== Resources (51) ===
|
|
||||||
НалоговаяБаза
|
|
||||||
КоэффициентКи *
|
|
||||||
...
|
|
||||||
* = has group-level formulas
|
|
||||||
```
|
|
||||||
|
|
||||||
С `-Name <поле>` — формулы агрегации:
|
|
||||||
```
|
|
||||||
=== Resource: ДатаСостояния ===
|
|
||||||
|
|
||||||
[ОсновноеСредство] ЕстьNull(ДатаСостояния, "")
|
|
||||||
```
|
|
||||||
|
|
||||||
## params — параметры схемы
|
|
||||||
|
|
||||||
```
|
|
||||||
=== Parameters (16) ===
|
|
||||||
Name Type Default Visible Expression
|
|
||||||
Период StandardPeriod LastMonth yes -
|
|
||||||
НачалоПериода DateTime - hidden &Период.ДатаНачала
|
|
||||||
Организация CatalogRef.Организации null yes -
|
|
||||||
```
|
|
||||||
|
|
||||||
## variant — варианты отчёта
|
|
||||||
|
|
||||||
Без `-Name` — список вариантов:
|
|
||||||
```
|
|
||||||
=== Variants (2) ===
|
|
||||||
[1] НоменклатураИЦены "Номенклатура и цены" Table(detail) 3 filters
|
|
||||||
[2] НоменклатураБезЦен "Номенклатура без цен" Group(detail) 2 filters
|
|
||||||
```
|
|
||||||
|
|
||||||
С `-Name <N|имя>` — структура конкретного варианта:
|
|
||||||
```
|
|
||||||
=== Variant [1]: НоменклатураИЦены "Номенклатура и цены" ===
|
|
||||||
|
|
||||||
Structure:
|
|
||||||
Table "Таблица"
|
|
||||||
├── Columns: [ТипЦен Items]
|
|
||||||
│ Selection: Auto, Цена
|
|
||||||
└── Rows: [Номенклатура Items]
|
|
||||||
Selection: Номенклатура, УИД, Auto
|
|
||||||
|
|
||||||
Filter:
|
|
||||||
[ ] Номенклатура InHierarchy [user]
|
|
||||||
[ ] ТипЦен Equal
|
|
||||||
[x] ВАрхиве = false "Исключая скрытые товары"
|
|
||||||
|
|
||||||
DataParams: КлючВарианта="НоменклатураИЦены"
|
|
||||||
Output: style=ЧерноБелый groups=Separately totalsH=None totalsV=None
|
|
||||||
```
|
|
||||||
|
|
||||||
## templates — привязки шаблонов вывода
|
|
||||||
|
|
||||||
Три типа привязок: `fieldTemplate` (к полю), `groupTemplate` (к группировке, Header/Footer), `groupHeaderTemplate` (заголовок группы).
|
|
||||||
|
|
||||||
Без `-Name` — карта привязок:
|
|
||||||
```
|
|
||||||
=== Templates (70 defined: 49 field, 37 group) ===
|
|
||||||
|
|
||||||
Field bindings (49): (all trivial)
|
|
||||||
ОстаточнаяСтоимостьНа0101, ОстаточнаяСтоимостьНа0102, ...
|
|
||||||
|
|
||||||
Group bindings (37):
|
|
||||||
ВидНалоговойБазы
|
|
||||||
Header -> Макет3 (1 rows, 1 params)
|
|
||||||
СреднегодоваяСтоимость2019
|
|
||||||
Footer -> Макет50 (1 rows) spacer
|
|
||||||
GroupHeader -> Макет40 (3 rows)
|
|
||||||
```
|
|
||||||
|
|
||||||
С `-Name <группировка|поле>` — содержимое шаблонов:
|
|
||||||
```
|
|
||||||
=== Templates: СреднегодоваяСтоимость2019 ===
|
|
||||||
|
|
||||||
Footer -> Макет50 [1 rows, 1 cells]:
|
|
||||||
Row 1: (empty)
|
|
||||||
|
|
||||||
GroupHeader -> Макет40 [3 rows, 78 cells]:
|
|
||||||
Row 1: "№ п/п" | "###Группировки1###" | "Инв. номер" | ...
|
|
||||||
Row 2: "01.01" | "01.02" | ... | "31.12"
|
|
||||||
Row 3: "1" | "2" | ... | "26"
|
|
||||||
```
|
|
||||||
|
|
||||||
Для field-привязок:
|
|
||||||
```
|
|
||||||
=== Field template: ОстаточнаяСтоимостьНа0101 -> Макет4 ===
|
|
||||||
[1 rows, 1 cells]
|
|
||||||
Row 1: {ОстаточнаяСтоимостьНа0101}
|
|
||||||
(all params trivial)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Тривиальность выражений**: `Поле = Поле` и `Поле = Представление(Поле)` считаются тривиальными и НЕ выводятся. Показываются только нетривиальные — когда выражение содержит другое поле, вызов метода, пустую строку и т.д.
|
|
||||||
|
|
||||||
## trace — трассировка поля от заголовка до запроса
|
|
||||||
|
|
||||||
Ищет поле по dataPath ИЛИ заголовку (включая подстроку) и показывает полную цепочку происхождения за один вызов:
|
|
||||||
|
|
||||||
```
|
|
||||||
=== Trace: КоэффициентКи "Коэффициент Ки" ===
|
|
||||||
|
|
||||||
Dataset: (schema-level only, not in dataset fields)
|
|
||||||
|
|
||||||
Calculated:
|
|
||||||
ВЫБОР КОГДА ... ТОГДА 0 ИНАЧЕ ... КОНЕЦ
|
|
||||||
Operands:
|
|
||||||
КоличествоМесяцевИспользования -> РасчетНалогаНаИмущество [Query]
|
|
||||||
КоличествоМесяцевВладения -> РасчетНалогаНаИмущество [Query]
|
|
||||||
|
|
||||||
Resource:
|
|
||||||
[ОсновноеСредство] Сумма(КоэффициентКи)
|
|
||||||
```
|
|
||||||
|
|
||||||
Типичный сценарий: пользователь видит колонку "Коэффициент Ки" в отчёте и спрашивает как она считается. Один вызов `trace` показывает: формулу вычисления, откуда берутся операнды, как агрегируется в ресурс.
|
|
||||||
|
|
||||||
## Что не выводится
|
|
||||||
|
|
||||||
- XML namespace-декларации
|
|
||||||
- Обёртки v8:item/v8:lang/v8:content (извлекаем чистый текст)
|
|
||||||
- userSettingID (GUID-ы пользовательских настроек)
|
|
||||||
- Дефолтные periodAdditionBegin/End = 0001-01-01
|
|
||||||
- viewMode
|
|
||||||
@@ -1,433 +0,0 @@
|
|||||||
# Regression suite authoring
|
|
||||||
|
|
||||||
Use this when the user asks to cover a 1C solution with automated regression tests, build out a test suite, or run an existing suite and analyse failures. For ad-hoc single-script automation, stay with the `run`/`exec` modes from SKILL.md instead.
|
|
||||||
|
|
||||||
The runner is the same `run.mjs`. The mode is `test`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node $RUN test [url] <dir|file> [flags]
|
|
||||||
```
|
|
||||||
|
|
||||||
Tests live next to the project they cover (not inside the skill). Convention: `tests/` at the project root, with `_hooks.mjs` and `webtest.config.mjs` at the suite root. Tests are ES modules with `*.test.mjs` suffix.
|
|
||||||
|
|
||||||
## When to choose `test` over `exec`
|
|
||||||
|
|
||||||
| Goal | Mode |
|
|
||||||
|------|------|
|
|
||||||
| Explore a form, prototype a single step, debug one selector | `exec` (interactive session) |
|
|
||||||
| **Walk through a scenario live before committing it as a test** | `exec` first, then `test` |
|
|
||||||
| Reproduce a bug as a failing test before fixing it | `test` |
|
|
||||||
| Cover a feature so future changes are checked automatically | `test` |
|
|
||||||
| Run the project's regression on a new build | `test` |
|
|
||||||
| Generate a screencast walkthrough | `exec` with `startRecording` |
|
|
||||||
|
|
||||||
Don't write a `.test.mjs` for a one-shot user request. Don't drive a regression suite through chained `exec` calls.
|
|
||||||
|
|
||||||
## Before writing tests — recon
|
|
||||||
|
|
||||||
Two layers, in order. Don't skip either.
|
|
||||||
|
|
||||||
### 1. Static recon — metadata
|
|
||||||
|
|
||||||
Never invent identifiers. For every metadata object the user mentions (or that you decide to cover), run the matching info skill first:
|
|
||||||
|
|
||||||
| Object type | Skill |
|
|
||||||
|-------------|-------|
|
|
||||||
| Catalog/document/register attributes, tabular sections | `/meta-info` |
|
|
||||||
| Form layout — fields, buttons, tabs, tables | `/form-info` |
|
|
||||||
| DCS report — fields, parameters, filters | `/skd-info` |
|
|
||||||
| Spreadsheet template areas/parameters | `/mxl-info` |
|
|
||||||
| Role rights / restrictions | `/role-info` |
|
|
||||||
| Subsystem composition / command interface | `/subsystem-info` |
|
|
||||||
|
|
||||||
This gives the real Russian field labels, command names, column headers, table-section names. Without it, fuzzy matching will silently land on the wrong element, or fail with no useful diagnostic.
|
|
||||||
|
|
||||||
If the user names objects you cannot find: stop and ask. Do not guess.
|
|
||||||
|
|
||||||
### 2. Live recon — interactive walkthrough
|
|
||||||
|
|
||||||
For any non-trivial scenario, walk the path live in `exec` mode before writing it down. Metadata tells you what exists; the live walkthrough tells you what actually happens — which button posts the document, which dialog 1C raises, how the form looks after `clickElement('Создать')`, what fields are required, where `wait()` is genuinely needed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start a session (background).
|
|
||||||
node $RUN start http://localhost:9191/myapp/ru_RU
|
|
||||||
|
|
||||||
# Step the scenario interactively. After each step, inspect.
|
|
||||||
cat <<'EOF' | node $RUN exec -
|
|
||||||
await navigateSection('Склад');
|
|
||||||
const cmds = await getCommands();
|
|
||||||
console.log(cmds);
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<'EOF' | node $RUN exec -
|
|
||||||
await openCommand('Приходная накладная');
|
|
||||||
await clickElement('Создать');
|
|
||||||
const s = await getFormState();
|
|
||||||
console.log(JSON.stringify(s.fields.map(f => ({ name: f.name, label: f.label, required: f.required })), null, 2));
|
|
||||||
console.log('buttons:', s.buttons.map(b => b.name));
|
|
||||||
console.log('tables:', s.tables.map(t => ({ name: t.name, label: t.label, columns: t.columns })));
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Try the actions you plan to encode. If a step fails, fix and re-try
|
|
||||||
# before transcribing it.
|
|
||||||
cat <<'EOF' | node $RUN exec -
|
|
||||||
await fillFields({ 'Контрагент': 'ООО Север' });
|
|
||||||
await fillTableRow({ 'Номенклатура': 'Товар 01', 'Количество': '5' },
|
|
||||||
{ table: 'Товары', add: true });
|
|
||||||
await clickElement('Провести и закрыть');
|
|
||||||
console.log(JSON.stringify(await getFormState()));
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# When done, stop the session (or leave it for the next test you write).
|
|
||||||
node $RUN stop
|
|
||||||
```
|
|
||||||
|
|
||||||
What to record from the walkthrough into the test:
|
|
||||||
- Exact button names (`'Провести и закрыть'`, not `'Сохранить'`).
|
|
||||||
- Field labels as 1C renders them (with possible non-breaking spaces — `fillFields` normalises, but be exact).
|
|
||||||
- Table section names from `getFormState().tables[].name`/`label` for multi-grid forms.
|
|
||||||
- Required `wait()` durations — only where a real async event happens (report generation, server-side calculation). Default actions await internally.
|
|
||||||
- The shape of `getFormState()` after each action — gives you the right `assert.equal(...)` paths.
|
|
||||||
|
|
||||||
After this, transcribe the working sequence into `*.test.mjs`, wrap each chunk in `step('...', async () => { ... })`, add assertions for the invariants you saw. Run the file once with `node $RUN test path/to/file.test.mjs` to confirm.
|
|
||||||
|
|
||||||
When live recon is overkill: trivial reads (`navigateSection` + `readTable` + assert non-empty), or scenarios you've already proven once in this session. When it's essential: anything with confirmation dialogs, posting/cancellation flows, reports with custom filters, multi-grid forms, or user-customised forms you've never seen.
|
|
||||||
|
|
||||||
## Suite layout
|
|
||||||
|
|
||||||
**Each application gets its own subfolder under `tests/`.** A single repo may host several independent suites side by side — they must not share `_hooks.mjs` or `webtest.config.mjs`, because each suite restores a different DB, publishes to a different URL, and ships its own test data.
|
|
||||||
|
|
||||||
```
|
|
||||||
tests/
|
|
||||||
web-test/ # engine self-tests (reserved if our repo layout)
|
|
||||||
<app-name>/ # application regression — one per solution
|
|
||||||
_hooks.mjs
|
|
||||||
webtest.config.mjs
|
|
||||||
01-login/
|
|
||||||
02-counterparties/
|
|
||||||
...
|
|
||||||
<another-app>/ # second solution, fully isolated
|
|
||||||
_hooks.mjs
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
`<app-name>` is the project/extension slug (`acc-payroll`, `erp-customisation`, etc.). Pick something stable and pass it on the CLI:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node $RUN test tests/<app-name>/
|
|
||||||
```
|
|
||||||
|
|
||||||
Inside the application subfolder, organize by **feature**, not by metadata kind. Numeric prefixes on both folder and file enforce run order (discovery is alphabetic by full path).
|
|
||||||
|
|
||||||
```
|
|
||||||
tests/<app-name>/
|
|
||||||
_hooks.mjs # stand prep + cross-cutting hooks (optional)
|
|
||||||
webtest.config.mjs # url, contexts, defaults (optional)
|
|
||||||
01-login/
|
|
||||||
01-open-base.test.mjs
|
|
||||||
02-section-navigation.test.mjs
|
|
||||||
02-counterparties/
|
|
||||||
01-create.test.mjs
|
|
||||||
02-edit-phone.test.mjs
|
|
||||||
03-goods-receipt/
|
|
||||||
01-fill.test.mjs
|
|
||||||
02-post.test.mjs
|
|
||||||
03-unpost.test.mjs
|
|
||||||
04-balance-report/
|
|
||||||
01-generate.test.mjs
|
|
||||||
02-warehouse-filter.test.mjs
|
|
||||||
05-approval-process/
|
|
||||||
01-end-to-end.test.mjs # multi-user
|
|
||||||
```
|
|
||||||
|
|
||||||
Per-folder `_hooks.mjs` / `webtest.config.mjs` inside the application subfolder are NOT supported. Only the application-root copies are loaded.
|
|
||||||
|
|
||||||
## Test file anatomy
|
|
||||||
|
|
||||||
```js
|
|
||||||
export const name = 'Создание контрагента'; // required
|
|
||||||
export const tags = ['catalog', 'create']; // optional, used for filtering + Allure
|
|
||||||
export const timeout = 60000; // optional, default 30000
|
|
||||||
// export const skip = 'pending fix #123'; // optional: true | string
|
|
||||||
// export const only = true; // debug-only — never commit
|
|
||||||
// export const context = 'manager'; // optional, single non-default context
|
|
||||||
// export const contexts = ['clerk', 'manager']; // optional, multi-user test
|
|
||||||
// export const severity = 'critical'; // optional, overrides config severity
|
|
||||||
|
|
||||||
export async function setup(ctx) {
|
|
||||||
// per-test prep — runs before default. Skip if not needed.
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function teardown(ctx) {
|
|
||||||
// per-test cleanup — runs after default, always (even on failure).
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function(ctx) {
|
|
||||||
const { navigateSection, openCommand, clickElement, fillFields,
|
|
||||||
readTable, closeForm, getFormState,
|
|
||||||
assert, step, log } = ctx;
|
|
||||||
|
|
||||||
await step('Открыть список контрагентов', async () => {
|
|
||||||
await navigateSection('Продажи');
|
|
||||||
await openCommand('Контрагенты');
|
|
||||||
});
|
|
||||||
|
|
||||||
await step('Создать нового контрагента', async () => {
|
|
||||||
await clickElement('Создать');
|
|
||||||
await fillFields({ 'Наименование': 'Тест ' + Date.now() });
|
|
||||||
await clickElement('Записать и закрыть');
|
|
||||||
});
|
|
||||||
|
|
||||||
await step('Убедиться, что элемент появился в списке', async () => {
|
|
||||||
const t = await readTable();
|
|
||||||
assert.tableHasRow(t, r => r['Наименование']?.startsWith('Тест '));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The runner injects every `browser.mjs` export into `ctx` plus `assert`, `step`, `log`, `testInfo`, `testResult` (afterEach only). For multi-context tests, each context name is its own scoped namespace (`ctx.clerk.clickElement(...)` etc.) — `step`/`assert` stay top-level.
|
|
||||||
|
|
||||||
**Step names — in Russian, descriptive.** Step labels surface in the console output, in JSON/JUnit, and as Allure step nodes. Russian-speaking QA reads them. Use a full action phrase (`'Создать нового контрагента'`, `'Проверить наличие документа в списке'`), not a tag (`'create'`, `'verify'`) and not a transliteration. Same applies to `export const name` and `displayName` in `webtest.config.mjs`.
|
|
||||||
|
|
||||||
## webtest.config.mjs
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default {
|
|
||||||
// Single-context: just url.
|
|
||||||
url: 'http://localhost:9191/myapp/ru_RU',
|
|
||||||
|
|
||||||
// OR multi-context: named contexts. Each test picks via `context`/`contexts` exports.
|
|
||||||
// contexts: {
|
|
||||||
// clerk: { url: 'http://localhost:9191/myapp-clerk/ru_RU', displayName: 'Кладовщик' },
|
|
||||||
// manager: { url: 'http://localhost:9191/myapp-manager/ru_RU', displayName: 'Менеджер' },
|
|
||||||
// },
|
|
||||||
// defaultContext: 'clerk',
|
|
||||||
|
|
||||||
timeout: 30000,
|
|
||||||
retries: 0,
|
|
||||||
screenshot: 'on-failure',
|
|
||||||
record: false,
|
|
||||||
|
|
||||||
// Severity → tags mapping for Allure. Each tag at most one bucket.
|
|
||||||
severity: {
|
|
||||||
critical: ['smoke', 'crud'],
|
|
||||||
minor: ['recording'],
|
|
||||||
},
|
|
||||||
defaultSeverity: 'normal',
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
CLI flags override config. Recommend latin context IDs + Russian `displayName` for video badges.
|
|
||||||
|
|
||||||
## _hooks.mjs
|
|
||||||
|
|
||||||
Two layers. Infra hooks run without a browser; testlevel hooks receive `ctx`.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { execSync } from 'child_process';
|
|
||||||
|
|
||||||
// Infra — runs once around the whole suite.
|
|
||||||
export async function prepare({ hookArgs, log, config }) {
|
|
||||||
// Restore DB, publish to Apache, build EPF, etc.
|
|
||||||
// hookArgs = everything after `--` on the CLI. Parse yourself.
|
|
||||||
if (hookArgs.includes('--rebuild-stand')) { /* full rebuild */ }
|
|
||||||
// Use idempotent hash-locks to skip work on warm starts.
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function cleanup({ log, config }) {
|
|
||||||
// Tear down or leave the stand running. Choose per project.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Testlevel — runs with browser ctx.
|
|
||||||
export async function beforeAll(ctx) { /* once after first context opens */ }
|
|
||||||
export async function afterAll(ctx) { /* once before final teardown */ }
|
|
||||||
export async function beforeEach(ctx) { /* ctx.testInfo is set */ }
|
|
||||||
export async function afterEach(ctx) { /* ctx.testResult is set */ }
|
|
||||||
|
|
||||||
// Per-context — runs whenever a context is created/closed.
|
|
||||||
export async function afterOpenContext(ctx, name, spec) { /* spec = config.contexts[name] */ }
|
|
||||||
export async function beforeCloseContext(ctx, name, spec) { }
|
|
||||||
```
|
|
||||||
|
|
||||||
Built-in state reset (`dismissPendingErrors` + close all forms) runs after `afterEach` automatically. Don't reimplement it.
|
|
||||||
|
|
||||||
**Where to put data setup:**
|
|
||||||
- DB restore, publication, EPF build → `prepare()`. Make it idempotent (hash-locks on inputs — config sources, EPF spec, DB dump) so warm starts skip everything but a liveness probe.
|
|
||||||
- Test-specific seed data (the document this test will edit, the counterparty it expects) → per-test `setup`.
|
|
||||||
- Shared session-wide warmup → `beforeAll`.
|
|
||||||
|
|
||||||
## Ready-to-paste patterns
|
|
||||||
|
|
||||||
### Catalog full cycle
|
|
||||||
|
|
||||||
```js
|
|
||||||
await step('Создать контрагента', async () => {
|
|
||||||
await navigateSection('Продажи');
|
|
||||||
await openCommand('Контрагенты');
|
|
||||||
await clickElement('Создать');
|
|
||||||
await fillFields({ 'Наименование': 'ТД Тест', 'ИНН': '7707083893' });
|
|
||||||
await clickElement('Записать и закрыть');
|
|
||||||
});
|
|
||||||
await step('Проверить наличие в списке', async () => {
|
|
||||||
const t = await readTable({ maxRows: 50 });
|
|
||||||
assert.tableHasRow(t, { 'Наименование': 'ТД Тест' });
|
|
||||||
});
|
|
||||||
await step('Удалить контрагента и подтвердить удаление', async () => {
|
|
||||||
await clickElement('ТД Тест');
|
|
||||||
const page = await getPage();
|
|
||||||
await page.keyboard.press('Delete');
|
|
||||||
await clickElement('Да');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Document create + post
|
|
||||||
|
|
||||||
```js
|
|
||||||
const marker = 'Тест-' + Date.now();
|
|
||||||
await openCommand('Приходная накладная');
|
|
||||||
await clickElement('Создать');
|
|
||||||
await fillFields({ 'Контрагент': 'ООО Север', 'Комментарий': marker });
|
|
||||||
await fillTableRow(
|
|
||||||
{ 'Номенклатура': 'Товар 01', 'Количество': '5', 'Цена': '100' },
|
|
||||||
{ table: 'Товары', add: true }
|
|
||||||
);
|
|
||||||
await clickElement('Провести и закрыть');
|
|
||||||
// Verify: re-open list, filter or scan, assert by `marker`.
|
|
||||||
```
|
|
||||||
|
|
||||||
Use a unique marker (`Date.now()` or random suffix) so re-runs don't collide. Identify your own row by it, not by position or natural keys that may already exist in the DB.
|
|
||||||
|
|
||||||
### DCS report
|
|
||||||
|
|
||||||
```js
|
|
||||||
await openCommand('Остатки товаров');
|
|
||||||
// Reset user settings — 1C persists them between sessions.
|
|
||||||
await clickElement('Ещё');
|
|
||||||
await clickElement('Установить стандартные настройки');
|
|
||||||
|
|
||||||
await selectValue('Номенклатура', 'Товар 02'); // auto-enables the filter checkbox
|
|
||||||
await clickElement('Сформировать');
|
|
||||||
await wait(3);
|
|
||||||
const r = await readSpreadsheet();
|
|
||||||
assert.deepEqual(r.headers, ['Номенклатура', 'Количество', 'Сумма']);
|
|
||||||
assert.ok(r.data.length >= 1);
|
|
||||||
assert.ok(r.totals?.['Сумма']);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-user process
|
|
||||||
|
|
||||||
```js
|
|
||||||
export const contexts = ['clerk', 'manager'];
|
|
||||||
|
|
||||||
export default async function({ clerk, manager, step, assert }) {
|
|
||||||
await step('Кладовщик создаёт накладную', async () => {
|
|
||||||
await clerk.navigateSection('Склад');
|
|
||||||
await clerk.openCommand('Приходные накладные');
|
|
||||||
await clerk.clickElement('Создать');
|
|
||||||
await clerk.fillFields({ 'Контрагент': 'ООО Север' });
|
|
||||||
await clerk.clickElement('Записать');
|
|
||||||
});
|
|
||||||
await step('Менеджер утверждает накладную', async () => {
|
|
||||||
await manager.navigateSection('Согласование');
|
|
||||||
await manager.openCommand('На утверждении');
|
|
||||||
await manager.clickElement('ООО Север', { dblclick: true });
|
|
||||||
await manager.clickElement('Утвердить');
|
|
||||||
});
|
|
||||||
await step('Кладовщик видит новый статус', async () => {
|
|
||||||
const s = await clerk.getFormState();
|
|
||||||
assert.equal(s.fields.find(f => f.name === 'Статус')?.value, 'Утверждён');
|
|
||||||
});
|
|
||||||
await step('Освободить сессию кладовщика', async () => {
|
|
||||||
await manager.closeContext('clerk'); // free a 1C license for the next test
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
License caveat: stock 1C allows ~2 web sessions concurrently. Close contexts you no longer need before the next multi-user test starts.
|
|
||||||
|
|
||||||
### Failing-test repro
|
|
||||||
|
|
||||||
```js
|
|
||||||
export const name = 'Bug #123: накладная без контрагента не должна проводиться';
|
|
||||||
export const tags = ['bug', 'validation'];
|
|
||||||
|
|
||||||
export default async function({ openCommand, clickElement, getFormState, assert, step }) {
|
|
||||||
await openCommand('Приходные накладные');
|
|
||||||
await clickElement('Создать');
|
|
||||||
await clickElement('Провести');
|
|
||||||
const s = await getFormState();
|
|
||||||
assert.ok(s.errorModal || s.fields.find(f => f.name === 'Контрагент')?.required,
|
|
||||||
'Должна быть ошибка валидации или поле помечено обязательным');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Write it red first, hand it to the user, fix the underlying issue, re-run green.
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node $RUN test tests/<app-name>/ # full app suite
|
|
||||||
node $RUN test tests/<app-name>/03-goods-receipt/ # one feature folder
|
|
||||||
node $RUN test tests/<app-name>/02-counterparties/01-create.test.mjs # one file
|
|
||||||
node $RUN test tests/<app-name>/ --tags=smoke # by tag (intersection)
|
|
||||||
node $RUN test tests/<app-name>/ --grep='накладн' # by name regex
|
|
||||||
node $RUN test tests/<app-name>/ --bail --retry=1 # stop on first fail, allow 1 retry
|
|
||||||
node $RUN test tests/<app-name>/ --report=allure-results --format=allure --report-dir=allure-results
|
|
||||||
node $RUN test tests/<app-name>/ -- --rebuild-stand # everything after `--` goes to hooks
|
|
||||||
```
|
|
||||||
|
|
||||||
Default report is JSON when `--report=…` is given. Allure needs `--format=allure` + a directory. JUnit similarly with `--format=junit`.
|
|
||||||
|
|
||||||
### Allure static config — `_allure/` directory
|
|
||||||
|
|
||||||
The runner copies `<testDir>/_allure/` into the report directory before generating Allure output. Standard Allure convention applies — three files are typically used:
|
|
||||||
|
|
||||||
- **`categories.json`** — failure classification. Always emit this when setting up a suite, with 1C-specific patterns: license pool exhaustion (`Не обнаружено свободной лицензии`), 1C application errors (`ВызватьИсключение|Произошла ошибка|…`), navigation/element lookup misses, runner timeouts, assertion failures.
|
|
||||||
- **`environment.properties`** — `key=value` lines for the Environment widget. Useful when the suite runs across builds/branches (URL, 1C platform version, git branch, configuration version). Often emitted dynamically by `prepare()` rather than committed as a static file.
|
|
||||||
- **`executor.json`** — CI metadata (Jenkins URL, GitHub run ID, etc.). Only relevant when the suite runs on a CI server; for local runs, skip it.
|
|
||||||
|
|
||||||
Discovery skips the underscored directory, so it never collides with tests.
|
|
||||||
|
|
||||||
## Severity guidance
|
|
||||||
|
|
||||||
When the user doesn't dictate, default to:
|
|
||||||
|
|
||||||
| Test kind | Severity |
|
|
||||||
|-----------|----------|
|
|
||||||
| Login + section navigation, basic CRUD on covered entities | `critical` (also tag `smoke`) |
|
|
||||||
| Documents posting, report generation, end-to-end processes | `critical` |
|
|
||||||
| Field-level edge cases, formatting, optional flows | `normal` |
|
|
||||||
| Cosmetic / recording / non-functional | `minor` |
|
|
||||||
| Reserved for show-stopper protections | `blocker` (use sparingly) |
|
|
||||||
|
|
||||||
Don't promote everything to `critical` — it loses signal in the Allure dashboard.
|
|
||||||
|
|
||||||
## Anti-patterns
|
|
||||||
|
|
||||||
- **Sleeps as a substitute for assertions.** `wait(5)` after `openCommand` is fine; `wait(30)` because something flakes is a bug — find what state you can wait on with `getFormState` instead.
|
|
||||||
- **Retry as a substitute for understanding.** "Not found" twice means the data isn't there or the label is wrong. Don't loop.
|
|
||||||
- **Raw DOM via `getPage().$$(...)`.** Use `getFormState`, `readTable`, `readSpreadsheet`. Raw selectors break across 1C platform versions.
|
|
||||||
- **`clickElement('×')` or `clickElement('Закрыть')`** to dismiss a form. Use `closeForm({ save: true|false })` — handles confirmation correctly.
|
|
||||||
- **Position-based row identification** (`rows[0]`) when the DB has shared seed data. Filter by unique marker or label instead.
|
|
||||||
- **Skipping recon** because "I know what this catalog looks like." You don't — the project's customisation almost certainly differs from a stock config.
|
|
||||||
- **`tags: ['smoke']` on a 90-second test.** Smoke means fast.
|
|
||||||
- **Hand-writing reset code** in `afterEach`. The runner already closes forms and dismisses errors.
|
|
||||||
- **Cross-test state assumptions.** Each test must start from desktop and seed its own data. Order-of-execution coupling is a regression-suite trap.
|
|
||||||
|
|
||||||
## After a run — failure triage
|
|
||||||
|
|
||||||
1. Scan the JSON or Allure summary for `failed`.
|
|
||||||
2. For each failure, read `error.message` + `error.step` + screenshot (saved next to the report).
|
|
||||||
3. If `error.onecError.stack` is present — it's a 1C exception, look at the platform trace.
|
|
||||||
4. Classify:
|
|
||||||
- **Test bug** — selector wrong, expectation wrong, race with no anchor → fix the test.
|
|
||||||
- **Application bug** — actual misbehaviour reproduced → report to the user with the failing step name and the platform stack.
|
|
||||||
- **Stand flake** — Apache timeout, login form not loading, license shortage → fix the hook idempotency or session-cleanup logic, not the test.
|
|
||||||
5. After fixes, re-run only the affected files (`node $RUN test tests/03-goods-receipt/`) before the full suite.
|
|
||||||
|
|
||||||
Report back to the user with the classification, not raw failure dumps.
|
|
||||||
|
|
||||||
## Reference
|
|
||||||
|
|
||||||
- Browser API: [SKILL.md](SKILL.md)
|
|
||||||
- Video and narration: [recording.md](recording.md)
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
|||||||
# 1C Skills for {{PLATFORM_LABEL}} ({{RUNTIME_LABEL}})
|
|
||||||
|
|
||||||
Автоматическая сборка из [main]({{MAIN_REPO_URL}}) — навыки 1С:Предприятие 8.3 для AI-агента **{{PLATFORM_LABEL}}** с рантаймом **{{RUNTIME_LABEL}}**.
|
|
||||||
|
|
||||||
> Эта ветка генерируется CI на каждый push в main. **Не редактируйте напрямую** — все правки идут в [main]({{MAIN_REPO_URL}}).
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
|
|
||||||
1. Скачайте ZIP этой ветки: **Code → Download ZIP** (или `git archive`).
|
|
||||||
2. Распакуйте в корень своего проекта — должна появиться папка `{{PLATFORM_DIR}}/`.
|
|
||||||
3. Запустите {{PLATFORM_LABEL}} из этого проекта — навыки станут доступны.
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
|
|
||||||
- **Windows** с PowerShell 5.1+ (входит в Windows) — для PowerShell-сборки.
|
|
||||||
- **Python 3.10+** — для Python-сборки. Зависимости: `lxml>=4.9.0`, `psutil>=5.9.0` (для DOM- и web-навыков).
|
|
||||||
- **1С:Предприятие 8.3** — для сборки/разборки EPF/ERF и работы с базами.
|
|
||||||
- **Node.js 18+** — для `/web-test`.
|
|
||||||
|
|
||||||
## Документация
|
|
||||||
|
|
||||||
Полные гайды, спецификации и описание навыков — в [main]({{MAIN_REPO_URL}}).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Source: {{MAIN_REPO_URL}}
|
|
||||||
Build commit: `{{COMMIT_SHA}}`
|
|
||||||
@@ -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,224 +0,0 @@
|
|||||||
name: Build port branches
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- '.claude/skills/**'
|
|
||||||
- 'scripts/switch.py'
|
|
||||||
- '.github/templates/README.port.md.tmpl'
|
|
||||||
- '.github/templates/codex-plugin.json.tmpl'
|
|
||||||
- '.github/templates/claude-plugin.json.tmpl'
|
|
||||||
- '.github/workflows/build-ports.yml'
|
|
||||||
- 'LICENSE'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- platform: claude-code
|
|
||||||
runtime: python
|
|
||||||
branch: port-claude-code-py
|
|
||||||
label: Claude Code
|
|
||||||
target_dir: .claude/skills
|
|
||||||
- platform: cursor
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-cursor
|
|
||||||
label: Cursor
|
|
||||||
target_dir: .cursor/skills
|
|
||||||
- platform: cursor
|
|
||||||
runtime: python
|
|
||||||
branch: port-cursor-py
|
|
||||||
label: Cursor
|
|
||||||
target_dir: .cursor/skills
|
|
||||||
- platform: codex
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-codex
|
|
||||||
label: Codex
|
|
||||||
target_dir: .codex/skills
|
|
||||||
- platform: codex
|
|
||||||
runtime: python
|
|
||||||
branch: port-codex-py
|
|
||||||
label: Codex
|
|
||||||
target_dir: .codex/skills
|
|
||||||
- platform: copilot
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-copilot
|
|
||||||
label: GitHub Copilot
|
|
||||||
target_dir: .github/skills
|
|
||||||
- platform: copilot
|
|
||||||
runtime: python
|
|
||||||
branch: port-copilot-py
|
|
||||||
label: GitHub Copilot
|
|
||||||
target_dir: .github/skills
|
|
||||||
- platform: augment
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-augment
|
|
||||||
label: Augment
|
|
||||||
target_dir: .augment/skills
|
|
||||||
- platform: augment
|
|
||||||
runtime: python
|
|
||||||
branch: port-augment-py
|
|
||||||
label: Augment
|
|
||||||
target_dir: .augment/skills
|
|
||||||
- platform: cline
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-cline
|
|
||||||
label: Cline
|
|
||||||
target_dir: .cline/skills
|
|
||||||
- platform: cline
|
|
||||||
runtime: python
|
|
||||||
branch: port-cline-py
|
|
||||||
label: Cline
|
|
||||||
target_dir: .cline/skills
|
|
||||||
- platform: kilo
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-kilo
|
|
||||||
label: Kilo Code
|
|
||||||
target_dir: .kilocode/skills
|
|
||||||
- platform: kilo
|
|
||||||
runtime: python
|
|
||||||
branch: port-kilo-py
|
|
||||||
label: Kilo Code
|
|
||||||
target_dir: .kilocode/skills
|
|
||||||
- platform: kiro
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-kiro
|
|
||||||
label: Kiro
|
|
||||||
target_dir: .kiro/skills
|
|
||||||
- platform: kiro
|
|
||||||
runtime: python
|
|
||||||
branch: port-kiro-py
|
|
||||||
label: Kiro
|
|
||||||
target_dir: .kiro/skills
|
|
||||||
- platform: gemini
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-gemini
|
|
||||||
label: Gemini CLI
|
|
||||||
target_dir: .gemini/skills
|
|
||||||
- platform: gemini
|
|
||||||
runtime: python
|
|
||||||
branch: port-gemini-py
|
|
||||||
label: Gemini CLI
|
|
||||||
target_dir: .gemini/skills
|
|
||||||
- platform: opencode
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-opencode
|
|
||||||
label: OpenCode
|
|
||||||
target_dir: .opencode/skills
|
|
||||||
- platform: opencode
|
|
||||||
runtime: python
|
|
||||||
branch: port-opencode-py
|
|
||||||
label: OpenCode
|
|
||||||
target_dir: .opencode/skills
|
|
||||||
- platform: roo
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-roo
|
|
||||||
label: Roo Code
|
|
||||||
target_dir: .roo/skills
|
|
||||||
- platform: roo
|
|
||||||
runtime: python
|
|
||||||
branch: port-roo-py
|
|
||||||
label: Roo Code
|
|
||||||
target_dir: .roo/skills
|
|
||||||
- platform: windsurf
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-windsurf
|
|
||||||
label: Windsurf
|
|
||||||
target_dir: .windsurf/skills
|
|
||||||
- platform: windsurf
|
|
||||||
runtime: python
|
|
||||||
branch: port-windsurf-py
|
|
||||||
label: Windsurf
|
|
||||||
target_dir: .windsurf/skills
|
|
||||||
- platform: agents
|
|
||||||
runtime: powershell
|
|
||||||
branch: port-agents
|
|
||||||
label: Agent Skills
|
|
||||||
target_dir: .agents/skills
|
|
||||||
- platform: agents
|
|
||||||
runtime: python
|
|
||||||
branch: port-agents-py
|
|
||||||
label: Agent Skills
|
|
||||||
target_dir: .agents/skills
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout main
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: '3.11'
|
|
||||||
|
|
||||||
- name: Build skills tree for ${{ matrix.platform }} (${{ matrix.runtime }})
|
|
||||||
run: |
|
|
||||||
python scripts/switch.py "${{ matrix.platform }}" \
|
|
||||||
--project-dir build \
|
|
||||||
--runtime "${{ matrix.runtime }}"
|
|
||||||
|
|
||||||
- name: Render port README
|
|
||||||
env:
|
|
||||||
PLATFORM_LABEL: ${{ matrix.label }}
|
|
||||||
PLATFORM_DIR: ${{ matrix.target_dir }}
|
|
||||||
RUNTIME_LABEL: ${{ matrix.runtime == 'powershell' && 'PowerShell' || 'Python' }}
|
|
||||||
COMMIT_SHA: ${{ github.sha }}
|
|
||||||
MAIN_REPO_URL: https://github.com/${{ github.repository }}
|
|
||||||
run: |
|
|
||||||
sed \
|
|
||||||
-e "s|{{PLATFORM_LABEL}}|${PLATFORM_LABEL}|g" \
|
|
||||||
-e "s|{{PLATFORM_DIR}}|${PLATFORM_DIR}|g" \
|
|
||||||
-e "s|{{RUNTIME_LABEL}}|${RUNTIME_LABEL}|g" \
|
|
||||||
-e "s|{{COMMIT_SHA}}|${COMMIT_SHA}|g" \
|
|
||||||
-e "s|{{MAIN_REPO_URL}}|${MAIN_REPO_URL}|g" \
|
|
||||||
.github/templates/README.port.md.tmpl > build/README.md
|
|
||||||
|
|
||||||
- name: Render Codex plugin manifest
|
|
||||||
if: matrix.platform == 'codex'
|
|
||||||
env:
|
|
||||||
PLUGIN_NAME: ${{ matrix.runtime == 'python' && '1c-skills-py' || '1c-skills' }}
|
|
||||||
RUNTIME_LABEL: ${{ matrix.runtime == 'powershell' && 'PowerShell' || 'Python' }}
|
|
||||||
SHORT_DESCRIPTION: ${{ matrix.runtime == 'python' && 'Python runtime (Linux/Mac/Windows)' || 'PowerShell runtime (Windows-first)' }}
|
|
||||||
COMMIT_SHA: ${{ github.sha }}
|
|
||||||
run: |
|
|
||||||
VERSION="$(date -u +%Y.%-m.%-d)+${COMMIT_SHA::7}"
|
|
||||||
mkdir -p build/.codex-plugin
|
|
||||||
sed \
|
|
||||||
-e "s|{{PLUGIN_NAME}}|${PLUGIN_NAME}|g" \
|
|
||||||
-e "s|{{VERSION}}|${VERSION}|g" \
|
|
||||||
-e "s|{{RUNTIME_LABEL}}|${RUNTIME_LABEL}|g" \
|
|
||||||
-e "s|{{SHORT_DESCRIPTION}}|${SHORT_DESCRIPTION}|g" \
|
|
||||||
.github/templates/codex-plugin.json.tmpl > build/.codex-plugin/plugin.json
|
|
||||||
|
|
||||||
- name: Render Claude plugin manifest (Py variant)
|
|
||||||
if: matrix.platform == 'claude-code' && matrix.runtime == 'python'
|
|
||||||
env:
|
|
||||||
PLUGIN_NAME: 1c-skills-py
|
|
||||||
run: |
|
|
||||||
mkdir -p build/.claude-plugin
|
|
||||||
sed -e "s|{{PLUGIN_NAME}}|${PLUGIN_NAME}|g" \
|
|
||||||
.github/templates/claude-plugin.json.tmpl > build/.claude-plugin/plugin.json
|
|
||||||
|
|
||||||
- name: Copy LICENSE
|
|
||||||
run: cp LICENSE build/LICENSE
|
|
||||||
|
|
||||||
- name: Force-push orphan snapshot to ${{ matrix.branch }}
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
git init -q -b master
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add -A
|
|
||||||
git commit -q -m "Auto-build: ${{ matrix.platform }} (${{ matrix.runtime }}) from ${GITHUB_SHA::7}"
|
|
||||||
git push --force \
|
|
||||||
"https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" \
|
|
||||||
"master:${{ matrix.branch }}"
|
|
||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
# Реальные выгрузки обработок (примеры, не для версионирования)
|
|
||||||
upload/
|
|
||||||
|
|
||||||
# Результаты сборки
|
|
||||||
build/
|
|
||||||
base/
|
|
||||||
*.epf
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Временные файлы тестов
|
|
||||||
test-tmp/
|
|
||||||
|
|
||||||
# Локальные настройки Claude Code
|
|
||||||
.claude/settings.local.json
|
|
||||||
|
|
||||||
# Инструменты (portable Apache и т.д.)
|
|
||||||
tools/
|
|
||||||
|
|
||||||
# Отладка навыков (eval, trigger-test, run_loop результаты)
|
|
||||||
debug/
|
|
||||||
|
|
||||||
# Кэш тестов навыков
|
|
||||||
tests/skills/.cache/
|
|
||||||
|
|
||||||
# Python кэш
|
|
||||||
__pycache__/
|
|
||||||
|
|
||||||
# Локальный реестр баз данных 1С
|
|
||||||
.v8-project.json
|
|
||||||
|
|
||||||
# web-test: Node.js зависимости и runtime-артефакты
|
|
||||||
.claude/skills/web-test/scripts/node_modules/
|
|
||||||
.claude/skills/web-test/.browser-session.json
|
|
||||||
|
|
||||||
# Скриншоты и видео (артефакты тестирования web-test)
|
|
||||||
*.png
|
|
||||||
*.mp4
|
|
||||||
|
|
||||||
# Навыки, скопированные для других AI-платформ (генерируются scripts/switch.py)
|
|
||||||
.agents/skills/
|
|
||||||
.augment/
|
|
||||||
.cline/
|
|
||||||
.codex/
|
|
||||||
.cursor/
|
|
||||||
.gemini/
|
|
||||||
.github/skills/
|
|
||||||
.kilocode/
|
|
||||||
.kiro/
|
|
||||||
.opencode/
|
|
||||||
.roo/
|
|
||||||
.windsurf/
|
|
||||||
@@ -1 +1 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
@@ -1,60 +1,60 @@
|
|||||||
---
|
---
|
||||||
name: cf-edit
|
name: cf-edit
|
||||||
description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию, поменять раскладку панелей, настроить начальную страницу
|
description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию, поменять раскладку панелей, настроить начальную страницу
|
||||||
argument-hint: -ConfigPath <path> -Operation <op> -Value <value>
|
argument-hint: -ConfigPath <path> -Operation <op> -Value <value>
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Write
|
- Write
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cf-edit — редактирование конфигурации 1С
|
# /cf-edit — редактирование конфигурации 1С
|
||||||
|
|
||||||
Точечное редактирование Configuration.xml: свойства, состав ChildObjects, роли по умолчанию.
|
Точечное редактирование Configuration.xml: свойства, состав ChildObjects, роли по умолчанию.
|
||||||
|
|
||||||
## Параметры и команда
|
## Параметры и команда
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
|
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
|
||||||
| `Operation` | Операция (см. таблицу) |
|
| `Operation` | Операция (см. таблицу) |
|
||||||
| `Value` | Значение для операции (batch через `;;`) |
|
| `Value` | Значение для операции (batch через `;;`) |
|
||||||
| `DefinitionFile` | JSON-файл с массивом операций |
|
| `DefinitionFile` | JSON-файл с массивом операций |
|
||||||
| `NoValidate` | Пропустить авто-валидацию |
|
| `NoValidate` | Пропустить авто-валидацию |
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-edit.ps1" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
|
powershell.exe -NoProfile -File ".opencode/skills/cf-edit/scripts/cf-edit.ps1" -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Операции
|
## Операции
|
||||||
|
|
||||||
| Операция | Формат Value | Описание |
|
| Операция | Формат Value | Описание |
|
||||||
|----------|-------------|----------|
|
|----------|-------------|----------|
|
||||||
| `modify-property` | `Ключ=Значение` (batch `;;`) | Изменить свойство |
|
| `modify-property` | `Ключ=Значение` (batch `;;`) | Изменить свойство |
|
||||||
| `add-childObject` | `Type.Name` (batch `;;`) | Зарегистрировать уже существующий файл объекта в ChildObjects. Для создания нового объекта используй `/meta-compile`, `/role-compile`, `/subsystem-compile` — они регистрируют автоматически |
|
| `add-childObject` | `Type.Name` (batch `;;`) | Зарегистрировать уже существующий файл объекта в ChildObjects. Для создания нового объекта используй `/meta-compile`, `/role-compile`, `/subsystem-compile` — они регистрируют автоматически |
|
||||||
| `remove-childObject` | `Type.Name` (batch `;;`) | Удалить объект из ChildObjects |
|
| `remove-childObject` | `Type.Name` (batch `;;`) | Удалить объект из ChildObjects |
|
||||||
| `add-defaultRole` | `Role.Name` или `Name` | Добавить роль по умолчанию |
|
| `add-defaultRole` | `Role.Name` или `Name` | Добавить роль по умолчанию |
|
||||||
| `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию |
|
| `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию |
|
||||||
| `set-defaultRoles` | Имена через `;;` | Заменить список ролей по умолчанию |
|
| `set-defaultRoles` | Имена через `;;` | Заменить список ролей по умолчанию |
|
||||||
| `set-panels` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/ClientApplicationInterface.xml` (раскладка панелей) |
|
| `set-panels` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/ClientApplicationInterface.xml` (раскладка панелей) |
|
||||||
| `set-home-page` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/HomePageWorkArea.xml` (начальная страница) |
|
| `set-home-page` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/HomePageWorkArea.xml` (начальная страница) |
|
||||||
|
|
||||||
Допустимые значения свойств, формат DefinitionFile (JSON), каноничный порядок: [reference.md](reference.md)
|
Допустимые значения свойств, формат DefinitionFile (JSON), каноничный порядок: [reference.md](reference.md)
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Изменить версию и поставщика
|
# Изменить версию и поставщика
|
||||||
... -ConfigPath src -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С"
|
... -ConfigPath src -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С"
|
||||||
|
|
||||||
# Добавить объекты
|
# Добавить объекты
|
||||||
... -ConfigPath src -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ"
|
... -ConfigPath src -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ"
|
||||||
|
|
||||||
# Удалить объект
|
# Удалить объект
|
||||||
... -ConfigPath src -Operation remove-childObject -Value "Catalog.Устаревший"
|
... -ConfigPath src -Operation remove-childObject -Value "Catalog.Устаревший"
|
||||||
|
|
||||||
# Роли по умолчанию
|
# Роли по умолчанию
|
||||||
... -ConfigPath src -Operation add-defaultRole -Value "ПолныеПрава"
|
... -ConfigPath src -Operation add-defaultRole -Value "ПолныеПрава"
|
||||||
... -ConfigPath src -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор"
|
... -ConfigPath src -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор"
|
||||||
```
|
```
|
||||||
@@ -1,150 +1,150 @@
|
|||||||
# cf-edit — справочник операций
|
# cf-edit — справочник операций
|
||||||
|
|
||||||
## modify-property
|
## modify-property
|
||||||
|
|
||||||
Свойства для редактирования:
|
Свойства для редактирования:
|
||||||
|
|
||||||
### Скалярные
|
### Скалярные
|
||||||
`Name`, `Version`, `Vendor`, `Comment`, `NamePrefix`, `UpdateCatalogAddress`
|
`Name`, `Version`, `Vendor`, `Comment`, `NamePrefix`, `UpdateCatalogAddress`
|
||||||
|
|
||||||
### LocalString (многоязычные)
|
### LocalString (многоязычные)
|
||||||
`Synonym`, `BriefInformation`, `DetailedInformation`, `Copyright`, `VendorInformationAddress`, `ConfigurationInformationAddress`
|
`Synonym`, `BriefInformation`, `DetailedInformation`, `Copyright`, `VendorInformationAddress`, `ConfigurationInformationAddress`
|
||||||
|
|
||||||
### Enum
|
### Enum
|
||||||
| Свойство | Допустимые значения |
|
| Свойство | Допустимые значения |
|
||||||
|----------|---------------------|
|
|----------|---------------------|
|
||||||
| `CompatibilityMode` | `Version8_3_20` ... `Version8_3_28`, `Version8_5_1`, `DontUse` |
|
| `CompatibilityMode` | `Version8_3_20` ... `Version8_3_28`, `Version8_5_1`, `DontUse` |
|
||||||
| `ConfigurationExtensionCompatibilityMode` | то же |
|
| `ConfigurationExtensionCompatibilityMode` | то же |
|
||||||
| `DefaultRunMode` | `ManagedApplication`, `OrdinaryApplication`, `Auto` |
|
| `DefaultRunMode` | `ManagedApplication`, `OrdinaryApplication`, `Auto` |
|
||||||
| `ScriptVariant` | `Russian`, `English` |
|
| `ScriptVariant` | `Russian`, `English` |
|
||||||
| `DataLockControlMode` | `Managed`, `Automatic`, `AutomaticAndManaged` |
|
| `DataLockControlMode` | `Managed`, `Automatic`, `AutomaticAndManaged` |
|
||||||
| `ObjectAutonumerationMode` | `NotAutoFree`, `AutoFree` |
|
| `ObjectAutonumerationMode` | `NotAutoFree`, `AutoFree` |
|
||||||
| `ModalityUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
|
| `ModalityUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
|
||||||
| `SynchronousPlatformExtensionAndAddInCallUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
|
| `SynchronousPlatformExtensionAndAddInCallUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
|
||||||
| `InterfaceCompatibilityMode` | `Version8_2`, `Version8_2EnableTaxi`, `Taxi`, `TaxiEnableVersion8_2`, `TaxiEnableVersion8_5`, `Version8_5EnableTaxi`, `Version8_5` |
|
| `InterfaceCompatibilityMode` | `Version8_2`, `Version8_2EnableTaxi`, `Taxi`, `TaxiEnableVersion8_2`, `TaxiEnableVersion8_5`, `Version8_5EnableTaxi`, `Version8_5` |
|
||||||
| `DatabaseTablespacesUseMode` | `DontUse`, `Use` |
|
| `DatabaseTablespacesUseMode` | `DontUse`, `Use` |
|
||||||
| `MainClientApplicationWindowMode` | `Normal`, `Fullscreen`, `Kiosk` |
|
| `MainClientApplicationWindowMode` | `Normal`, `Fullscreen`, `Kiosk` |
|
||||||
|
|
||||||
### Ref
|
### Ref
|
||||||
`DefaultLanguage` — значение вида `Language.Русский`
|
`DefaultLanguage` — значение вида `Language.Русский`
|
||||||
|
|
||||||
### Формат batch
|
### Формат batch
|
||||||
`"Version=1.0.0.1 ;; Vendor=Фирма 1С ;; Synonym=Тестовая конфигурация"`
|
`"Version=1.0.0.1 ;; Vendor=Фирма 1С ;; Synonym=Тестовая конфигурация"`
|
||||||
|
|
||||||
## add-childObject / remove-childObject
|
## add-childObject / remove-childObject
|
||||||
|
|
||||||
Формат: `Type.Name` — XML-тип и имя объекта через точку.
|
Формат: `Type.Name` — XML-тип и имя объекта через точку.
|
||||||
|
|
||||||
**Важно про `add-childObject`**: регистрирует в `<ChildObjects>` объект, **файл которого уже существует на диске**. Если файла нет — exit 1. Для создания нового объекта используй профильный навык — `/meta-compile` (Catalog, Document, Enum, Report, регистры и т.д.), `/role-compile` (Role), `/subsystem-compile` (Subsystem). Они создают файл И регистрируют его за один вызов.
|
**Важно про `add-childObject`**: регистрирует в `<ChildObjects>` объект, **файл которого уже существует на диске**. Если файла нет — exit 1. Для создания нового объекта используй профильный навык — `/meta-compile` (Catalog, Document, Enum, Report, регистры и т.д.), `/role-compile` (Role), `/subsystem-compile` (Subsystem). Они создают файл И регистрируют его за один вызов.
|
||||||
|
|
||||||
Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"`
|
Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"`
|
||||||
|
|
||||||
## add-defaultRole / remove-defaultRole / set-defaultRoles
|
## add-defaultRole / remove-defaultRole / set-defaultRoles
|
||||||
|
|
||||||
Имя роли: `ПолныеПрава` или `Role.ПолныеПрава` (префикс `Role.` добавляется автоматически).
|
Имя роли: `ПолныеПрава` или `Role.ПолныеПрава` (префикс `Role.` добавляется автоматически).
|
||||||
|
|
||||||
`set-defaultRoles` полностью заменяет список ролей.
|
`set-defaultRoles` полностью заменяет список ролей.
|
||||||
|
|
||||||
## set-panels
|
## set-panels
|
||||||
|
|
||||||
Перезаписывает `Ext/ClientApplicationInterface.xml` — раскладку панелей рабочего пространства Taxi. Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует на экране.
|
Перезаписывает `Ext/ClientApplicationInterface.xml` — раскладку панелей рабочего пространства Taxi. Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует на экране.
|
||||||
|
|
||||||
`value` — объект с ключами `top`, `left`, `right`, `bottom`. Каждый ключ — массив записей. Ключ можно опустить (= пустая сторона).
|
`value` — объект с ключами `top`, `left`, `right`, `bottom`. Каждый ключ — массив записей. Ключ можно опустить (= пустая сторона).
|
||||||
|
|
||||||
**Запись** — одна из:
|
**Запись** — одна из:
|
||||||
- Строка-алиас (одна панель в этом слоте)
|
- Строка-алиас (одна панель в этом слоте)
|
||||||
- Объект `{"group": [...]}` (стек: панели/подгруппы внутри располагаются друг под другом)
|
- Объект `{"group": [...]}` (стек: панели/подгруппы внутри располагаются друг под другом)
|
||||||
|
|
||||||
**Алиасы панелей:**
|
**Алиасы панелей:**
|
||||||
|
|
||||||
| Алиас | Панель |
|
| Алиас | Панель |
|
||||||
|-------|--------|
|
|-------|--------|
|
||||||
| `sections` | Панель разделов |
|
| `sections` | Панель разделов |
|
||||||
| `open` | Панель открытых |
|
| `open` | Панель открытых |
|
||||||
| `favorites` | Панель избранного |
|
| `favorites` | Панель избранного |
|
||||||
| `history` | Панель истории |
|
| `history` | Панель истории |
|
||||||
| `functions` | Панель функций текущего раздела |
|
| `functions` | Панель функций текущего раздела |
|
||||||
|
|
||||||
**Семантика:**
|
**Семантика:**
|
||||||
- Несколько записей в одной стороне → отдельные слоты «рядом» (несколько тегов `<top>`/...)
|
- Несколько записей в одной стороне → отдельные слоты «рядом» (несколько тегов `<top>`/...)
|
||||||
- `{"group":[...]}` → один тег с `<group>`-обёрткой, элементы внутри идут стеком
|
- `{"group":[...]}` → один тег с `<group>`-обёрткой, элементы внутри идут стеком
|
||||||
|
|
||||||
**Пример** (DefinitionFile):
|
**Пример** (DefinitionFile):
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"operation": "set-panels",
|
"operation": "set-panels",
|
||||||
"value": {
|
"value": {
|
||||||
"top": ["open"],
|
"top": ["open"],
|
||||||
"left": ["sections"],
|
"left": ["sections"],
|
||||||
"right": [{ "group": ["favorites", "history"] }],
|
"right": [{ "group": ["favorites", "history"] }],
|
||||||
"bottom": ["functions"]
|
"bottom": ["functions"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Через `-Value` (CLI): передай объект как JSON-строку — `... -Operation set-panels -Value '{"top":["open"]}'`.
|
Через `-Value` (CLI): передай объект как JSON-строку — `... -Operation set-panels -Value '{"top":["open"]}'`.
|
||||||
|
|
||||||
## set-home-page
|
## set-home-page
|
||||||
|
|
||||||
Перезаписывает `Ext/HomePageWorkArea.xml` — раскладка форм на начальной странице (рабочая область). Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует.
|
Перезаписывает `Ext/HomePageWorkArea.xml` — раскладка форм на начальной странице (рабочая область). Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует.
|
||||||
|
|
||||||
`value` — объект:
|
`value` — объект:
|
||||||
|
|
||||||
| Ключ | Канонич. (XML) | Описание |
|
| Ключ | Канонич. (XML) | Описание |
|
||||||
|------|----------------|----------|
|
|------|----------------|----------|
|
||||||
| `template` | `WorkingAreaTemplate` | `OneColumn` / `TwoColumnsEqualWidth` (дефолт) / `TwoColumnsVariableWidth` |
|
| `template` | `WorkingAreaTemplate` | `OneColumn` / `TwoColumnsEqualWidth` (дефолт) / `TwoColumnsVariableWidth` |
|
||||||
| `left` | `LeftColumn` | массив записей форм |
|
| `left` | `LeftColumn` | массив записей форм |
|
||||||
| `right` | `RightColumn` | массив записей форм (запрещён при `OneColumn`) |
|
| `right` | `RightColumn` | массив записей форм (запрещён при `OneColumn`) |
|
||||||
|
|
||||||
Принимаются и короткие и канонич. ключи (XML-имена) — оба работают.
|
Принимаются и короткие и канонич. ключи (XML-имена) — оба работают.
|
||||||
|
|
||||||
**Запись формы** — одна из:
|
**Запись формы** — одна из:
|
||||||
- Строка `"<form>"` — только имя формы, дефолты `height=10`, `visibility=true`
|
- Строка `"<form>"` — только имя формы, дефолты `height=10`, `visibility=true`
|
||||||
- Объект `{form, height?, visibility?, roles?}`
|
- Объект `{form, height?, visibility?, roles?}`
|
||||||
|
|
||||||
| Поле | Канонич. | Дефолт | Описание |
|
| Поле | Канонич. | Дефолт | Описание |
|
||||||
|------|----------|--------|----------|
|
|------|----------|--------|----------|
|
||||||
| `form` | `Form` | — | `CommonForm.X` или `Type.Object.Form.Name` (или UUID) |
|
| `form` | `Form` | — | `CommonForm.X` или `Type.Object.Form.Name` (или UUID) |
|
||||||
| `height` | `Height` | `10` | Высота |
|
| `height` | `Height` | `10` | Высота |
|
||||||
| `visibility` | `Visibility` | `true` | Общая видимость (`<xr:Common>`) |
|
| `visibility` | `Visibility` | `true` | Общая видимость (`<xr:Common>`) |
|
||||||
| `roles` | — | — | `{"Role.Имя": true|false, ...}` — переопределения по ролям |
|
| `roles` | — | — | `{"Role.Имя": true|false, ...}` — переопределения по ролям |
|
||||||
|
|
||||||
**Семантика visibility:** `visibility` = общее правило, `roles` — точечные исключения. Скрыть для всех кроме одной роли: `{"visibility": false, "roles": {"Role.Опер": true}}`.
|
**Семантика visibility:** `visibility` = общее правило, `roles` — точечные исключения. Скрыть для всех кроме одной роли: `{"visibility": false, "roles": {"Role.Опер": true}}`.
|
||||||
|
|
||||||
**Пример:**
|
**Пример:**
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"operation": "set-home-page",
|
"operation": "set-home-page",
|
||||||
"value": {
|
"value": {
|
||||||
"template": "TwoColumnsVariableWidth",
|
"template": "TwoColumnsVariableWidth",
|
||||||
"left": [
|
"left": [
|
||||||
"CommonForm.НачалоРаботы",
|
"CommonForm.НачалоРаботы",
|
||||||
{ "form": "CommonForm.СписокЗадач", "height": 100, "visibility": false },
|
{ "form": "CommonForm.СписокЗадач", "height": 100, "visibility": false },
|
||||||
{ "form": "Catalog.Контрагенты.Form.ФормаСписка", "height": 50 },
|
{ "form": "Catalog.Контрагенты.Form.ФормаСписка", "height": 50 },
|
||||||
{
|
{
|
||||||
"form": "CommonForm.РабочийСтолОператора",
|
"form": "CommonForm.РабочийСтолОператора",
|
||||||
"visibility": false,
|
"visibility": false,
|
||||||
"roles": { "Role.Оператор": true, "Role.ПолныеПрава": false }
|
"roles": { "Role.Оператор": true, "Role.ПолныеПрава": false }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"right": [
|
"right": [
|
||||||
{ "form": "DataProcessor.Поиск.Form.ФормаПоиска", "height": 30 }
|
{ "form": "DataProcessor.Поиск.Form.ФормаПоиска", "height": 30 }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## DefinitionFile (JSON)
|
## DefinitionFile (JSON)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{ "operation": "modify-property", "value": "Version=2.0.0.1 ;; Vendor=Test" },
|
{ "operation": "modify-property", "value": "Version=2.0.0.1 ;; Vendor=Test" },
|
||||||
{ "operation": "add-childObject", "value": "Catalog.Товары ;; Document.Заказ" },
|
{ "operation": "add-childObject", "value": "Catalog.Товары ;; Document.Заказ" },
|
||||||
{ "operation": "add-defaultRole", "value": "ПолныеПрава" }
|
{ "operation": "add-defaultRole", "value": "ПолныеПрава" }
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
+989
-869
File diff suppressed because it is too large
Load Diff
+165
-2
@@ -1,16 +1,177 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cf-edit v1.4 — Edit 1C configuration root (Configuration.xml)
|
# cf-edit v1.7 — Edit 1C configuration root (Configuration.xml)
|
||||||
# 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
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import uuid as _uuid
|
import uuid as _uuid
|
||||||
from html import escape as html_escape
|
from html import escape as html_escape
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
|
# Blocks edits of vendor objects "на замке" / read-only configs. Trigger = bin
|
||||||
|
# present; reaction from .v8-project.json editingAllowedCheck (deny|warn|off,
|
||||||
|
# default deny). Never throws (except sys.exit on deny) — errors degrade to allow.
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def _sg_root_uuid(xml_path):
|
||||||
|
if not os.path.isfile(xml_path):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
mx = etree.parse(xml_path).getroot()
|
||||||
|
for child in mx:
|
||||||
|
if isinstance(child.tag, str) and child.get("uuid"):
|
||||||
|
return child.get("uuid")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _sg_find_v8project(start_dir):
|
||||||
|
d = start_dir
|
||||||
|
for _ in range(20):
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
pj = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pj):
|
||||||
|
return pj
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _sg_get_edit_mode(cfg_dir):
|
||||||
|
try:
|
||||||
|
pj = _sg_find_v8project(os.getcwd()) or _sg_find_v8project(cfg_dir)
|
||||||
|
if not pj:
|
||||||
|
return "deny"
|
||||||
|
proj = json.loads(open(pj, encoding="utf-8-sig").read())
|
||||||
|
cfg_full = os.path.normcase(os.path.abspath(cfg_dir)).rstrip("\\/")
|
||||||
|
for db in proj.get("databases", []):
|
||||||
|
src = db.get("configSrc")
|
||||||
|
if src:
|
||||||
|
src_full = os.path.normcase(os.path.abspath(src)).rstrip("\\/")
|
||||||
|
if cfg_full == src_full or cfg_full.startswith(src_full + os.sep):
|
||||||
|
if db.get("editingAllowedCheck"):
|
||||||
|
return db["editingAllowedCheck"]
|
||||||
|
if proj.get("editingAllowedCheck"):
|
||||||
|
return proj["editingAllowedCheck"]
|
||||||
|
return "deny"
|
||||||
|
except Exception:
|
||||||
|
return "deny"
|
||||||
|
|
||||||
|
|
||||||
|
def assert_edit_allowed(target_path, require):
|
||||||
|
try:
|
||||||
|
rp = os.path.abspath(target_path)
|
||||||
|
elem_uuid = _sg_root_uuid(rp)
|
||||||
|
cfg_dir = None
|
||||||
|
bin_path = None
|
||||||
|
d = rp if os.path.isdir(rp) else os.path.dirname(rp)
|
||||||
|
for _ in range(12):
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
if not elem_uuid:
|
||||||
|
elem_uuid = _sg_root_uuid(d + ".xml")
|
||||||
|
if not cfg_dir:
|
||||||
|
cand = os.path.join(d, "Ext", "ParentConfigurations.bin")
|
||||||
|
if os.path.exists(cand) or os.path.exists(os.path.join(d, "Configuration.xml")):
|
||||||
|
cfg_dir = d
|
||||||
|
bin_path = cand
|
||||||
|
if elem_uuid and cfg_dir:
|
||||||
|
break
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
if not elem_uuid and cfg_dir:
|
||||||
|
elem_uuid = _sg_root_uuid(os.path.join(cfg_dir, "Configuration.xml"))
|
||||||
|
if not bin_path or not os.path.exists(bin_path):
|
||||||
|
return
|
||||||
|
data = open(bin_path, "rb").read()
|
||||||
|
if len(data) <= 32:
|
||||||
|
return
|
||||||
|
if data[:3] == b"\xef\xbb\xbf":
|
||||||
|
data = data[3:]
|
||||||
|
text = data.decode("utf-8", "replace")
|
||||||
|
h = re.match(r"\{6,(\d+),(\d+),", text)
|
||||||
|
if not h:
|
||||||
|
return
|
||||||
|
g = int(h.group(1))
|
||||||
|
k = int(h.group(2))
|
||||||
|
if k == 0:
|
||||||
|
return
|
||||||
|
best = None
|
||||||
|
if elem_uuid:
|
||||||
|
for m in re.finditer(r"([0-2]),0," + re.escape(elem_uuid.lower()), text):
|
||||||
|
f1 = int(m.group(1))
|
||||||
|
if best is None or f1 < best:
|
||||||
|
best = f1
|
||||||
|
blocked = False
|
||||||
|
code = ""
|
||||||
|
reason = ""
|
||||||
|
if g == 1:
|
||||||
|
blocked = True
|
||||||
|
code = "capability-off"
|
||||||
|
reason = "возможность изменения конфигурации выключена (вся конфигурация read-only)"
|
||||||
|
elif require == "removed":
|
||||||
|
if best is not None and best != 2:
|
||||||
|
blocked = True
|
||||||
|
code = "not-removed"
|
||||||
|
reason = "объект не снят с поддержки — удаление сломает обновления"
|
||||||
|
else:
|
||||||
|
if best is not None and best == 0:
|
||||||
|
blocked = True
|
||||||
|
code = "locked"
|
||||||
|
reason = "объект на замке — редактирование сломает обновления"
|
||||||
|
if not blocked:
|
||||||
|
return
|
||||||
|
mode = _sg_get_edit_mode(cfg_dir)
|
||||||
|
if mode == "off":
|
||||||
|
return
|
||||||
|
if mode == "warn":
|
||||||
|
sys.stderr.write(f"[support-guard] ПРЕДУПРЕЖДЕНИЕ: {reason}. Цель: {rp}\n")
|
||||||
|
return
|
||||||
|
head = "[support-guard] Редактирование отклонено: это объект типовой конфигурации на поддержке поставщика, прямое редактирование молча сломает будущие обновления."
|
||||||
|
cfe = "Рекомендуемый путь: внести доработку в расширение (навыки cfe-borrow / cfe-patch-method) — состояние поддержки менять не нужно, обновления вендора сохраняются."
|
||||||
|
off_note = "Снять проверку для этой базы: editingAllowedCheck = warn|off в .v8-project.json."
|
||||||
|
if code == "capability-off":
|
||||||
|
state = f"Состояние: у всей конфигурации выключена возможность изменения (режим read-only «из коробки») — поэтому объект «{rp}» редактировать нельзя."
|
||||||
|
fix = (
|
||||||
|
"Либо снять защиту явно (навык support-edit, два шага):\n"
|
||||||
|
f' 1. support-edit -Path "{cfg_dir}" -Capability on — включить возможность изменения (объекты пока остаются на замке);\n'
|
||||||
|
f' 2. support-edit -Path "{rp}" -Set editable — открыть этот объект для редактирования.\n'
|
||||||
|
" Изменение применяется в базу полной загрузкой выгрузки и обходит механизм обновлений вендора."
|
||||||
|
)
|
||||||
|
elif code == "not-removed":
|
||||||
|
state = f"Состояние: объект «{rp}» на поддержке (не снят с поддержки) — его удаление разорвёт обновления вендора."
|
||||||
|
fix = (
|
||||||
|
"Либо сначала снять объект с поддержки, затем удалять:\n"
|
||||||
|
f' support-edit -Path "{rp}" -Set off-support — объект уходит из-под обновлений, после этого удаление безопасно.'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
state = f"Состояние: объект «{rp}» на замке (возможность изменения конфигурации включена, но сам объект не редактируется)."
|
||||||
|
fix = (
|
||||||
|
"Либо разрешить редактирование этого объекта (навык support-edit, выбрать одно):\n"
|
||||||
|
f' support-edit -Path "{rp}" -Set editable — редактировать и дальше получать обновления вендора (возможны конфликты слияния);\n'
|
||||||
|
f' support-edit -Path "{rp}" -Set off-support — снять с поддержки: обновления по объекту больше не приходят.'
|
||||||
|
)
|
||||||
|
sys.stderr.write(head + "\n" + state + "\n" + cfe + "\n" + fix + "\n" + off_note + "\n")
|
||||||
|
sys.exit(1)
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||||
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
||||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
@@ -190,6 +351,8 @@ def main():
|
|||||||
resolved_path = os.path.abspath(config_path)
|
resolved_path = os.path.abspath(config_path)
|
||||||
config_dir = os.path.dirname(resolved_path)
|
config_dir = os.path.dirname(resolved_path)
|
||||||
|
|
||||||
|
assert_edit_allowed(resolved_path, "editable")
|
||||||
|
|
||||||
xml_parser = etree.XMLParser(remove_blank_text=False)
|
xml_parser = etree.XMLParser(remove_blank_text=False)
|
||||||
tree = etree.parse(resolved_path, xml_parser)
|
tree = etree.parse(resolved_path, xml_parser)
|
||||||
xml_root = tree.getroot()
|
xml_root = tree.getroot()
|
||||||
@@ -806,7 +969,7 @@ def main():
|
|||||||
if os.path.isfile(validate_script):
|
if os.path.isfile(validate_script):
|
||||||
print()
|
print()
|
||||||
print("--- Running cf-validate ---")
|
print("--- Running cf-validate ---")
|
||||||
subprocess.run([sys.executable, validate_script, "-ConfigPath", "-Path", resolved_path])
|
subprocess.run([sys.executable, validate_script, "-ConfigPath", resolved_path])
|
||||||
|
|
||||||
# --- Summary ---
|
# --- Summary ---
|
||||||
print()
|
print()
|
||||||
@@ -1,54 +1,54 @@
|
|||||||
---
|
---
|
||||||
name: cf-info
|
name: cf-info
|
||||||
description: Анализ структуры конфигурации 1С — свойства, состав, счётчики объектов. Используй для обзора конфигурации — какие объекты есть, сколько их, какие настройки
|
description: Анализ структуры конфигурации 1С — свойства, состав, счётчики объектов. Используй для обзора конфигурации — какие объекты есть, сколько их, какие настройки
|
||||||
argument-hint: <ConfigPath> [-Mode overview|brief|full] [-Section home-page]
|
argument-hint: <ConfigPath> [-Mode overview|brief|full] [-Section home-page]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cf-info — Структура конфигурации 1С
|
# /cf-info — Структура конфигурации 1С
|
||||||
|
|
||||||
Читает Configuration.xml из выгрузки конфигурации и выводит компактное описание структуры.
|
Читает Configuration.xml из выгрузки конфигурации и выводит компактное описание структуры.
|
||||||
|
|
||||||
## Параметры и команда
|
## Параметры и команда
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
|
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
|
||||||
| `Mode` | Режим: `overview` (default), `brief`, `full` |
|
| `Mode` | Режим: `overview` (default), `brief`, `full` |
|
||||||
| `Section` | Drill-down по разделу (alias: `Name`). Сейчас: `home-page` |
|
| `Section` | Drill-down по разделу (alias: `Name`). Сейчас: `home-page` |
|
||||||
| `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) |
|
| `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) |
|
||||||
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
|
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-info.ps1" -ConfigPath "<путь>"
|
powershell.exe -NoProfile -File ".opencode/skills/cf-info/scripts/cf-info.ps1" -ConfigPath "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Три режима
|
## Три режима
|
||||||
|
|
||||||
| Режим | Что показывает |
|
| Режим | Что показывает |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `overview` *(default)* | Заголовок + ключевые свойства + таблица счётчиков объектов по типам |
|
| `overview` *(default)* | Заголовок + ключевые свойства + таблица счётчиков объектов по типам |
|
||||||
| `brief` | Одна строка: Имя — "Синоним" vВерсия \| N объектов \| совместимость |
|
| `brief` | Одна строка: Имя — "Синоним" vВерсия \| N объектов \| совместимость |
|
||||||
| `full` | Все свойства по категориям + полный список ChildObjects + DefaultRoles + мобильные функциональности |
|
| `full` | Все свойства по категориям + полный список ChildObjects + DefaultRoles + мобильные функциональности |
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Обзор пустой конфигурации
|
# Обзор пустой конфигурации
|
||||||
... -ConfigPath src
|
... -ConfigPath src
|
||||||
|
|
||||||
# Краткая сводка реальной конфигурации
|
# Краткая сводка реальной конфигурации
|
||||||
... -ConfigPath src -Mode brief
|
... -ConfigPath src -Mode brief
|
||||||
|
|
||||||
# Полная информация
|
# Полная информация
|
||||||
... -ConfigPath src -Mode full
|
... -ConfigPath src -Mode full
|
||||||
|
|
||||||
# С пагинацией
|
# С пагинацией
|
||||||
... -ConfigPath src -Mode full -Limit 50 -Offset 100
|
... -ConfigPath src -Mode full -Limit 50 -Offset 100
|
||||||
|
|
||||||
# Drill-down: только начальная страница (раскладка форм с ролями)
|
# Drill-down: только начальная страница (раскладка форм с ролями)
|
||||||
... -ConfigPath src -Section home-page
|
... -ConfigPath src -Section home-page
|
||||||
```
|
```
|
||||||
+654
-579
File diff suppressed because it is too large
Load Diff
+72
-1
@@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cf-info v1.2 — Compact summary of 1C configuration root
|
# cf-info v1.3 — Compact summary of 1C configuration root
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
@@ -219,6 +220,71 @@ def get_home_page_layout():
|
|||||||
|
|
||||||
home_page = get_home_page_layout()
|
home_page = get_home_page_layout()
|
||||||
|
|
||||||
|
# --- Support state (Ext/ParentConfigurations.bin) ---
|
||||||
|
# Decodes the 1C support-state file. See docs/1c-support-state-spec.md.
|
||||||
|
# Returns None on absent/error; else dict: state='absent'|'removed'|'parsed',
|
||||||
|
# g (0=editing on, 1=off), k (vendor configs), vendors [{vendor,name,version}],
|
||||||
|
# counts [locked, editable, removed] by f1 — record tally (k>1 counts each
|
||||||
|
# vendor block separately); only computed when g==0.
|
||||||
|
def read_support_state(bin_path):
|
||||||
|
try:
|
||||||
|
if not os.path.isfile(bin_path):
|
||||||
|
return {"state": "absent"}
|
||||||
|
data = open(bin_path, "rb").read()
|
||||||
|
if len(data) <= 32:
|
||||||
|
return {"state": "removed"}
|
||||||
|
if data[:3] == b"\xef\xbb\xbf":
|
||||||
|
data = data[3:]
|
||||||
|
text = data.decode("utf-8", "replace")
|
||||||
|
h = re.match(r"\{6,(\d+),(\d+),", text)
|
||||||
|
if not h:
|
||||||
|
return None
|
||||||
|
g = int(h.group(1))
|
||||||
|
k = int(h.group(2))
|
||||||
|
if k == 0:
|
||||||
|
return {"state": "removed"}
|
||||||
|
vendors = []
|
||||||
|
for m in re.finditer(r'"((?:[^"]|"")*)","((?:[^"]|"")*)","((?:[^"]|"")*)",\d+,', text):
|
||||||
|
vendors.append({
|
||||||
|
"version": m.group(1).replace('""', '"'),
|
||||||
|
"vendor": m.group(2).replace('""', '"'),
|
||||||
|
"name": m.group(3).replace('""', '"'),
|
||||||
|
})
|
||||||
|
counts = None
|
||||||
|
if g == 0:
|
||||||
|
counts = [0, 0, 0]
|
||||||
|
for m in re.finditer(r"([0-2]),0,[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", text):
|
||||||
|
counts[int(m.group(1))] += 1
|
||||||
|
return {"state": "parsed", "g": g, "k": k, "vendors": vendors, "counts": counts}
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_support_lines():
|
||||||
|
config_dir = os.path.dirname(config_path)
|
||||||
|
bin_path = os.path.join(config_dir, "Ext", "ParentConfigurations.bin")
|
||||||
|
st = read_support_state(bin_path)
|
||||||
|
res = []
|
||||||
|
if not st or st["state"] == "absent":
|
||||||
|
if cfg_ext_purpose:
|
||||||
|
res.append("Поддержка: расширение (CFE), правки свободны")
|
||||||
|
else:
|
||||||
|
res.append("Поддержка: не на поддержке (своя конфигурация)")
|
||||||
|
return res
|
||||||
|
if st["state"] == "removed":
|
||||||
|
res.append("Поддержка: снята с поддержки полностью")
|
||||||
|
return res
|
||||||
|
res.append("Поддержка: на поддержке")
|
||||||
|
if st["g"] == 0:
|
||||||
|
res.append(" Возможность изменения: включена")
|
||||||
|
res.append(f" Объектов: на замке {st['counts'][0]} / редактируется {st['counts'][1]} / снято {st['counts'][2]}")
|
||||||
|
else:
|
||||||
|
res.append(" Возможность изменения: выключена — вся конфигурация read-only (правки заблокированы)")
|
||||||
|
res.append(f" Конфигураций поставщика: {st['k']}")
|
||||||
|
if st["k"] > 1:
|
||||||
|
for v in st["vendors"]:
|
||||||
|
res.append(f" Поставщик: {v['vendor']} — {v['name']} {v['version']}")
|
||||||
|
return res
|
||||||
|
|
||||||
def format_home_page_item(it, detailed):
|
def format_home_page_item(it, detailed):
|
||||||
badges = [f"h={it['height']}"]
|
badges = [f"h={it['height']}"]
|
||||||
if not it["common"]:
|
if not it["common"]:
|
||||||
@@ -249,6 +315,7 @@ cfg_version = get_prop_text("Version")
|
|||||||
cfg_vendor = get_prop_text("Vendor")
|
cfg_vendor = get_prop_text("Vendor")
|
||||||
cfg_compat = get_prop_text("CompatibilityMode")
|
cfg_compat = get_prop_text("CompatibilityMode")
|
||||||
cfg_ext_compat = get_prop_text("ConfigurationExtensionCompatibilityMode")
|
cfg_ext_compat = get_prop_text("ConfigurationExtensionCompatibilityMode")
|
||||||
|
cfg_ext_purpose = get_prop_text("ConfigurationExtensionPurpose")
|
||||||
cfg_default_run = get_prop_text("DefaultRunMode")
|
cfg_default_run = get_prop_text("DefaultRunMode")
|
||||||
cfg_script = get_prop_text("ScriptVariant")
|
cfg_script = get_prop_text("ScriptVariant")
|
||||||
cfg_default_lang = get_prop_text("DefaultLanguage")
|
cfg_default_lang = get_prop_text("DefaultLanguage")
|
||||||
@@ -281,6 +348,8 @@ if args.Mode == "overview" and not args.Section:
|
|||||||
out(f"Поставщик: {cfg_vendor}")
|
out(f"Поставщик: {cfg_vendor}")
|
||||||
if cfg_version:
|
if cfg_version:
|
||||||
out(f"Версия: {cfg_version}")
|
out(f"Версия: {cfg_version}")
|
||||||
|
for ln in get_support_lines():
|
||||||
|
out(ln)
|
||||||
out(f"Совместимость: {cfg_compat}")
|
out(f"Совместимость: {cfg_compat}")
|
||||||
out(f"Режим запуска: {cfg_default_run}")
|
out(f"Режим запуска: {cfg_default_run}")
|
||||||
out(f"Язык скриптов: {cfg_script}")
|
out(f"Язык скриптов: {cfg_script}")
|
||||||
@@ -369,6 +438,8 @@ if args.Mode == "full" and not args.Section:
|
|||||||
out(f"Поставщик: {cfg_vendor}")
|
out(f"Поставщик: {cfg_vendor}")
|
||||||
if cfg_version:
|
if cfg_version:
|
||||||
out(f"Версия: {cfg_version}")
|
out(f"Версия: {cfg_version}")
|
||||||
|
for ln in get_support_lines():
|
||||||
|
out(ln)
|
||||||
cfg_update_addr = get_prop_text("UpdateCatalogAddress")
|
cfg_update_addr = get_prop_text("UpdateCatalogAddress")
|
||||||
if cfg_update_addr:
|
if cfg_update_addr:
|
||||||
out(f"Каталог обн.: {cfg_update_addr}")
|
out(f"Каталог обн.: {cfg_update_addr}")
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
---
|
---
|
||||||
name: cf-init
|
name: cf-init
|
||||||
description: Создать пустую конфигурацию 1С (scaffold XML-исходников). Используй когда нужно начать новую конфигурацию с нуля
|
description: Создать пустую конфигурацию 1С (scaffold XML-исходников). Используй когда нужно начать новую конфигурацию с нуля
|
||||||
argument-hint: <Name> [-Synonym <name>] [-OutputDir src]
|
argument-hint: <Name> [-Synonym <name>] [-OutputDir src]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cf-init — Создание пустой конфигурации 1С
|
# /cf-init — Создание пустой конфигурации 1С
|
||||||
|
|
||||||
Создаёт scaffold исходников пустой конфигурации 1С: `Configuration.xml`, `Languages/Русский.xml`.
|
Создаёт scaffold исходников пустой конфигурации 1С: `Configuration.xml`, `Languages/Русский.xml`.
|
||||||
|
|
||||||
## Параметры и команда
|
## Параметры и команда
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| `Name` | Имя конфигурации (обязат.) |
|
| `Name` | Имя конфигурации (обязат.) |
|
||||||
| `Synonym` | Синоним (= Name если не указан) |
|
| `Synonym` | Синоним (= Name если не указан) |
|
||||||
| `OutputDir` | Каталог для создания (default: `src`) |
|
| `OutputDir` | Каталог для создания (default: `src`) |
|
||||||
| `Version` | Версия конфигурации |
|
| `Version` | Версия конфигурации |
|
||||||
| `Vendor` | Поставщик |
|
| `Vendor` | Поставщик |
|
||||||
| `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) |
|
| `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) |
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-init.ps1" -Name "МояКонфигурация"
|
powershell.exe -NoProfile -File ".opencode/skills/cf-init/scripts/cf-init.ps1" -Name "МояКонфигурация"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Базовая конфигурация
|
# Базовая конфигурация
|
||||||
... -Name МояКонфигурация -Synonym "Моя конфигурация" -OutputDir test-tmp/cf
|
... -Name МояКонфигурация -Synonym "Моя конфигурация" -OutputDir test-tmp/cf
|
||||||
|
|
||||||
# С версией и поставщиком
|
# С версией и поставщиком
|
||||||
... -Name TestCfg -Synonym "Тестовая" -Version "1.0.0.1" -Vendor "Фирма 1С" -OutputDir test-tmp/cf2
|
... -Name TestCfg -Synonym "Тестовая" -Version "1.0.0.1" -Vendor "Фирма 1С" -OutputDir test-tmp/cf2
|
||||||
|
|
||||||
# Другой режим совместимости
|
# Другой режим совместимости
|
||||||
... -Name TestCfg -CompatibilityMode Version8_3_27 -OutputDir test-tmp/cf3
|
... -Name TestCfg -CompatibilityMode Version8_3_27 -OutputDir test-tmp/cf3
|
||||||
```
|
```
|
||||||
|
|
||||||
## Верификация
|
## Верификация
|
||||||
|
|
||||||
```
|
```
|
||||||
/cf-init TestConfig -OutputDir test-tmp/cf
|
/cf-init TestConfig -OutputDir test-tmp/cf
|
||||||
/cf-info test-tmp/cf — проверить созданное
|
/cf-info test-tmp/cf — проверить созданное
|
||||||
/cf-validate test-tmp/cf — валидировать
|
/cf-validate test-tmp/cf — валидировать
|
||||||
```
|
```
|
||||||
+249
-249
@@ -1,249 +1,249 @@
|
|||||||
# cf-init v1.2 — Create empty 1C configuration scaffold
|
# cf-init v1.2 — Create empty 1C configuration scaffold
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string]$Name,
|
[string]$Name,
|
||||||
[string]$Synonym = $Name,
|
[string]$Synonym = $Name,
|
||||||
[string]$OutputDir = "src",
|
[string]$OutputDir = "src",
|
||||||
[string]$Version,
|
[string]$Version,
|
||||||
[string]$Vendor,
|
[string]$Vendor,
|
||||||
[string]$CompatibilityMode = "Version8_3_24"
|
[string]$CompatibilityMode = "Version8_3_24"
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve output dir ---
|
# --- Resolve output dir ---
|
||||||
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
|
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
|
||||||
$OutputDir = Join-Path (Get-Location).Path $OutputDir
|
$OutputDir = Join-Path (Get-Location).Path $OutputDir
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Check existing ---
|
# --- Check existing ---
|
||||||
$cfgFile = Join-Path $OutputDir "Configuration.xml"
|
$cfgFile = Join-Path $OutputDir "Configuration.xml"
|
||||||
if (Test-Path $cfgFile) {
|
if (Test-Path $cfgFile) {
|
||||||
Write-Error "Configuration.xml already exists: $cfgFile"
|
Write-Error "Configuration.xml already exists: $cfgFile"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Generate UUIDs ---
|
# --- Generate UUIDs ---
|
||||||
$uuidCfg = [guid]::NewGuid().ToString()
|
$uuidCfg = [guid]::NewGuid().ToString()
|
||||||
$uuidLang = [guid]::NewGuid().ToString()
|
$uuidLang = [guid]::NewGuid().ToString()
|
||||||
# 7 ContainedObject ObjectIds
|
# 7 ContainedObject ObjectIds
|
||||||
$co1 = [guid]::NewGuid().ToString()
|
$co1 = [guid]::NewGuid().ToString()
|
||||||
$co2 = [guid]::NewGuid().ToString()
|
$co2 = [guid]::NewGuid().ToString()
|
||||||
$co3 = [guid]::NewGuid().ToString()
|
$co3 = [guid]::NewGuid().ToString()
|
||||||
$co4 = [guid]::NewGuid().ToString()
|
$co4 = [guid]::NewGuid().ToString()
|
||||||
$co5 = [guid]::NewGuid().ToString()
|
$co5 = [guid]::NewGuid().ToString()
|
||||||
$co6 = [guid]::NewGuid().ToString()
|
$co6 = [guid]::NewGuid().ToString()
|
||||||
$co7 = [guid]::NewGuid().ToString()
|
$co7 = [guid]::NewGuid().ToString()
|
||||||
|
|
||||||
# --- Mobile functionalities ---
|
# --- Mobile functionalities ---
|
||||||
$mobileFuncs = @(
|
$mobileFuncs = @(
|
||||||
@("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"),
|
@("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"),
|
||||||
@("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"),
|
@("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"),
|
||||||
@("Calendars","false"), @("PushNotifications","false"), @("LocalNotifications","false"),
|
@("Calendars","false"), @("PushNotifications","false"), @("LocalNotifications","false"),
|
||||||
@("InAppPurchases","false"), @("PersonalComputerFileExchange","false"), @("Ads","false"),
|
@("InAppPurchases","false"), @("PersonalComputerFileExchange","false"), @("Ads","false"),
|
||||||
@("NumberDialing","false"), @("CallProcessing","false"), @("CallLog","false"),
|
@("NumberDialing","false"), @("CallProcessing","false"), @("CallLog","false"),
|
||||||
@("AutoSendSMS","false"), @("ReceiveSMS","false"), @("SMSLog","false"),
|
@("AutoSendSMS","false"), @("ReceiveSMS","false"), @("SMSLog","false"),
|
||||||
@("Camera","false"), @("Microphone","false"), @("MusicLibrary","false"),
|
@("Camera","false"), @("Microphone","false"), @("MusicLibrary","false"),
|
||||||
@("PictureAndVideoLibraries","false"), @("AudioPlaybackAndVibration","false"),
|
@("PictureAndVideoLibraries","false"), @("AudioPlaybackAndVibration","false"),
|
||||||
@("BackgroundAudioPlaybackAndVibration","false"), @("InstallPackages","false"),
|
@("BackgroundAudioPlaybackAndVibration","false"), @("InstallPackages","false"),
|
||||||
@("OSBackup","true"), @("ApplicationUsageStatistics","false"),
|
@("OSBackup","true"), @("ApplicationUsageStatistics","false"),
|
||||||
@("BarcodeScanning","false"), @("BackgroundAudioRecording","false"),
|
@("BarcodeScanning","false"), @("BackgroundAudioRecording","false"),
|
||||||
@("AllFilesAccess","false"), @("Videoconferences","false"), @("NFC","false"),
|
@("AllFilesAccess","false"), @("Videoconferences","false"), @("NFC","false"),
|
||||||
@("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"),
|
@("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"),
|
||||||
@("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false")
|
@("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false")
|
||||||
)
|
)
|
||||||
|
|
||||||
$mobileXml = ""
|
$mobileXml = ""
|
||||||
foreach ($mf in $mobileFuncs) {
|
foreach ($mf in $mobileFuncs) {
|
||||||
$mobileXml += "`r`n`t`t`t`t<app:functionality>`r`n`t`t`t`t`t<app:functionality>$($mf[0])</app:functionality>`r`n`t`t`t`t`t<app:use>$($mf[1])</app:use>`r`n`t`t`t`t</app:functionality>"
|
$mobileXml += "`r`n`t`t`t`t<app:functionality>`r`n`t`t`t`t`t<app:functionality>$($mf[0])</app:functionality>`r`n`t`t`t`t`t<app:use>$($mf[1])</app:use>`r`n`t`t`t`t</app:functionality>"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Synonym XML ---
|
# --- Synonym XML ---
|
||||||
$synonymXml = ""
|
$synonymXml = ""
|
||||||
if ($Synonym) {
|
if ($Synonym) {
|
||||||
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
|
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Optional properties ---
|
# --- Optional properties ---
|
||||||
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
|
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
|
||||||
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
|
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
|
||||||
|
|
||||||
# --- Configuration.xml ---
|
# --- Configuration.xml ---
|
||||||
$cfgXml = @"
|
$cfgXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||||
<Configuration uuid="$uuidCfg">
|
<Configuration uuid="$uuidCfg">
|
||||||
<InternalInfo>
|
<InternalInfo>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
|
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
|
||||||
<xr:ObjectId>$co1</xr:ObjectId>
|
<xr:ObjectId>$co1</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
|
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
|
||||||
<xr:ObjectId>$co2</xr:ObjectId>
|
<xr:ObjectId>$co2</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
|
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
|
||||||
<xr:ObjectId>$co3</xr:ObjectId>
|
<xr:ObjectId>$co3</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
|
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
|
||||||
<xr:ObjectId>$co4</xr:ObjectId>
|
<xr:ObjectId>$co4</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
|
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
|
||||||
<xr:ObjectId>$co5</xr:ObjectId>
|
<xr:ObjectId>$co5</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
|
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
|
||||||
<xr:ObjectId>$co6</xr:ObjectId>
|
<xr:ObjectId>$co6</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
|
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
|
||||||
<xr:ObjectId>$co7</xr:ObjectId>
|
<xr:ObjectId>$co7</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
</InternalInfo>
|
</InternalInfo>
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
|
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
|
||||||
<Synonym>$synonymXml</Synonym>
|
<Synonym>$synonymXml</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
<NamePrefix/>
|
<NamePrefix/>
|
||||||
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
|
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
|
||||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||||
<UsePurposes>
|
<UsePurposes>
|
||||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||||
</UsePurposes>
|
</UsePurposes>
|
||||||
<ScriptVariant>Russian</ScriptVariant>
|
<ScriptVariant>Russian</ScriptVariant>
|
||||||
<DefaultRoles/>
|
<DefaultRoles/>
|
||||||
<Vendor>$vendorXml</Vendor>
|
<Vendor>$vendorXml</Vendor>
|
||||||
<Version>$versionXml</Version>
|
<Version>$versionXml</Version>
|
||||||
<UpdateCatalogAddress/>
|
<UpdateCatalogAddress/>
|
||||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||||
<AdditionalFullTextSearchDictionaries/>
|
<AdditionalFullTextSearchDictionaries/>
|
||||||
<CommonSettingsStorage/>
|
<CommonSettingsStorage/>
|
||||||
<ReportsUserSettingsStorage/>
|
<ReportsUserSettingsStorage/>
|
||||||
<ReportsVariantsStorage/>
|
<ReportsVariantsStorage/>
|
||||||
<FormDataSettingsStorage/>
|
<FormDataSettingsStorage/>
|
||||||
<DynamicListsUserSettingsStorage/>
|
<DynamicListsUserSettingsStorage/>
|
||||||
<URLExternalDataStorage/>
|
<URLExternalDataStorage/>
|
||||||
<Content/>
|
<Content/>
|
||||||
<DefaultReportForm/>
|
<DefaultReportForm/>
|
||||||
<DefaultReportVariantForm/>
|
<DefaultReportVariantForm/>
|
||||||
<DefaultReportSettingsForm/>
|
<DefaultReportSettingsForm/>
|
||||||
<DefaultReportAppearanceTemplate/>
|
<DefaultReportAppearanceTemplate/>
|
||||||
<DefaultDynamicListSettingsForm/>
|
<DefaultDynamicListSettingsForm/>
|
||||||
<DefaultSearchForm/>
|
<DefaultSearchForm/>
|
||||||
<DefaultDataHistoryChangeHistoryForm/>
|
<DefaultDataHistoryChangeHistoryForm/>
|
||||||
<DefaultDataHistoryVersionDataForm/>
|
<DefaultDataHistoryVersionDataForm/>
|
||||||
<DefaultDataHistoryVersionDifferencesForm/>
|
<DefaultDataHistoryVersionDifferencesForm/>
|
||||||
<DefaultCollaborationSystemUsersChoiceForm/>
|
<DefaultCollaborationSystemUsersChoiceForm/>
|
||||||
<RequiredMobileApplicationPermissions/>
|
<RequiredMobileApplicationPermissions/>
|
||||||
<UsedMobileApplicationFunctionalities>$mobileXml
|
<UsedMobileApplicationFunctionalities>$mobileXml
|
||||||
</UsedMobileApplicationFunctionalities>
|
</UsedMobileApplicationFunctionalities>
|
||||||
<StandaloneConfigurationRestrictionRoles/>
|
<StandaloneConfigurationRestrictionRoles/>
|
||||||
<MobileApplicationURLs/>
|
<MobileApplicationURLs/>
|
||||||
<AllowedIncomingShareRequestTypes/>
|
<AllowedIncomingShareRequestTypes/>
|
||||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||||
<DefaultInterface/>
|
<DefaultInterface/>
|
||||||
<DefaultStyle/>
|
<DefaultStyle/>
|
||||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||||
<BriefInformation/>
|
<BriefInformation/>
|
||||||
<DetailedInformation/>
|
<DetailedInformation/>
|
||||||
<Copyright/>
|
<Copyright/>
|
||||||
<VendorInformationAddress/>
|
<VendorInformationAddress/>
|
||||||
<ConfigurationInformationAddress/>
|
<ConfigurationInformationAddress/>
|
||||||
<DataLockControlMode>Managed</DataLockControlMode>
|
<DataLockControlMode>Managed</DataLockControlMode>
|
||||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||||
<CompatibilityMode>$CompatibilityMode</CompatibilityMode>
|
<CompatibilityMode>$CompatibilityMode</CompatibilityMode>
|
||||||
<DefaultConstantsForm/>
|
<DefaultConstantsForm/>
|
||||||
</Properties>
|
</Properties>
|
||||||
<ChildObjects>
|
<ChildObjects>
|
||||||
<Language>Русский</Language>
|
<Language>Русский</Language>
|
||||||
</ChildObjects>
|
</ChildObjects>
|
||||||
</Configuration>
|
</Configuration>
|
||||||
</MetaDataObject>
|
</MetaDataObject>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
# --- Languages/Русский.xml ---
|
# --- Languages/Русский.xml ---
|
||||||
$langXml = @"
|
$langXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||||
<Language uuid="$uuidLang">
|
<Language uuid="$uuidLang">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>Русский</Name>
|
<Name>Русский</Name>
|
||||||
<Synonym>
|
<Synonym>
|
||||||
<v8:item>
|
<v8:item>
|
||||||
<v8:lang>ru</v8:lang>
|
<v8:lang>ru</v8:lang>
|
||||||
<v8:content>Русский</v8:content>
|
<v8:content>Русский</v8:content>
|
||||||
</v8:item>
|
</v8:item>
|
||||||
</Synonym>
|
</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
<LanguageCode>ru</LanguageCode>
|
<LanguageCode>ru</LanguageCode>
|
||||||
</Properties>
|
</Properties>
|
||||||
</Language>
|
</Language>
|
||||||
</MetaDataObject>
|
</MetaDataObject>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
# --- Ext/ClientApplicationInterface.xml (default ERP-style panel layout) ---
|
# --- Ext/ClientApplicationInterface.xml (default ERP-style panel layout) ---
|
||||||
# Open panel on top, Sections panel on left; Functions/Favorites/History declared
|
# Open panel on top, Sections panel on left; Functions/Favorites/History declared
|
||||||
# via panelDef but not placed by default. Without this file the web client renders
|
# via panelDef but not placed by default. Without this file the web client renders
|
||||||
# section icons without labels (icon-only mode).
|
# section icons without labels (icon-only mode).
|
||||||
$openPanelInst = [guid]::NewGuid().ToString()
|
$openPanelInst = [guid]::NewGuid().ToString()
|
||||||
$sectionsPanelInst = [guid]::NewGuid().ToString()
|
$sectionsPanelInst = [guid]::NewGuid().ToString()
|
||||||
$caiXml = @"
|
$caiXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||||
<top>
|
<top>
|
||||||
<panel id="$openPanelInst">
|
<panel id="$openPanelInst">
|
||||||
<uuid>cbab57f2-a0f3-4f0a-89ea-4cb19570ab75</uuid>
|
<uuid>cbab57f2-a0f3-4f0a-89ea-4cb19570ab75</uuid>
|
||||||
</panel>
|
</panel>
|
||||||
</top>
|
</top>
|
||||||
<left>
|
<left>
|
||||||
<panel id="$sectionsPanelInst">
|
<panel id="$sectionsPanelInst">
|
||||||
<uuid>b553047f-c9aa-4157-978d-448ecad24248</uuid>
|
<uuid>b553047f-c9aa-4157-978d-448ecad24248</uuid>
|
||||||
</panel>
|
</panel>
|
||||||
</left>
|
</left>
|
||||||
<panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/>
|
<panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/>
|
||||||
<panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/>
|
<panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/>
|
||||||
<panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/>
|
<panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/>
|
||||||
<panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/>
|
<panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/>
|
||||||
<panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/>
|
<panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/>
|
||||||
</ClientApplicationInterface>
|
</ClientApplicationInterface>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
# --- Create directories ---
|
# --- Create directories ---
|
||||||
if (-not (Test-Path $OutputDir)) {
|
if (-not (Test-Path $OutputDir)) {
|
||||||
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||||
}
|
}
|
||||||
$langDir = Join-Path $OutputDir "Languages"
|
$langDir = Join-Path $OutputDir "Languages"
|
||||||
if (-not (Test-Path $langDir)) {
|
if (-not (Test-Path $langDir)) {
|
||||||
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
|
||||||
}
|
}
|
||||||
$extDir = Join-Path $OutputDir "Ext"
|
$extDir = Join-Path $OutputDir "Ext"
|
||||||
if (-not (Test-Path $extDir)) {
|
if (-not (Test-Path $extDir)) {
|
||||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Write files with UTF-8 BOM ---
|
# --- Write files with UTF-8 BOM ---
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
|
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
|
||||||
$langFile = Join-Path $langDir "Русский.xml"
|
$langFile = Join-Path $langDir "Русский.xml"
|
||||||
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
|
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
|
||||||
$caiFile = Join-Path $extDir "ClientApplicationInterface.xml"
|
$caiFile = Join-Path $extDir "ClientApplicationInterface.xml"
|
||||||
[System.IO.File]::WriteAllText($caiFile, $caiXml, $enc)
|
[System.IO.File]::WriteAllText($caiFile, $caiXml, $enc)
|
||||||
|
|
||||||
# --- Output ---
|
# --- Output ---
|
||||||
Write-Host "[OK] Создана конфигурация: $Name"
|
Write-Host "[OK] Создана конфигурация: $Name"
|
||||||
Write-Host " Каталог: $OutputDir"
|
Write-Host " Каталог: $OutputDir"
|
||||||
Write-Host " Configuration.xml: $cfgFile"
|
Write-Host " Configuration.xml: $cfgFile"
|
||||||
Write-Host " Languages: $langFile"
|
Write-Host " Languages: $langFile"
|
||||||
Write-Host " Ext/CAI: $caiFile"
|
Write-Host " Ext/CAI: $caiFile"
|
||||||
@@ -1,29 +1,29 @@
|
|||||||
---
|
---
|
||||||
name: cf-validate
|
name: cf-validate
|
||||||
description: Валидация конфигурации 1С. Используй после создания или модификации конфигурации для проверки корректности
|
description: Валидация конфигурации 1С. Используй после создания или модификации конфигурации для проверки корректности
|
||||||
argument-hint: <ConfigPath> [-Detailed] [-MaxErrors 30]
|
argument-hint: <ConfigPath> [-Detailed] [-MaxErrors 30]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cf-validate — валидация конфигурации 1С
|
# /cf-validate — валидация конфигурации 1С
|
||||||
|
|
||||||
Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов.
|
Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов.
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
| Параметр | Обяз. | Умолч. | Описание |
|
| Параметр | Обяз. | Умолч. | Описание |
|
||||||
|------------|:-----:|---------|-------------------------------------------------|
|
|------------|:-----:|---------|-------------------------------------------------|
|
||||||
| ConfigPath | да | — | Путь к Configuration.xml или каталогу выгрузки |
|
| ConfigPath | да | — | Путь к Configuration.xml или каталогу выгрузки |
|
||||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty"
|
powershell.exe -NoProfile -File ".opencode/skills/cf-validate/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty/Configuration.xml"
|
powershell.exe -NoProfile -File ".opencode/skills/cf-validate/scripts/cf-validate.ps1" -ConfigPath "upload/cfempty/Configuration.xml"
|
||||||
```
|
```
|
||||||
+611
-611
File diff suppressed because it is too large
Load Diff
@@ -1,101 +1,101 @@
|
|||||||
---
|
---
|
||||||
name: cfe-borrow
|
name: cfe-borrow
|
||||||
description: Заимствование объектов из конфигурации 1С в расширение (CFE). Используй когда нужно перехватить метод, изменить форму или добавить реквизит к существующему объекту конфигурации
|
description: Заимствование объектов из конфигурации 1С в расширение (CFE). Используй когда нужно перехватить метод, изменить форму или добавить реквизит к существующему объекту конфигурации
|
||||||
argument-hint: -ExtensionPath <path> -ConfigPath <path> -Object "Catalog.Контрагенты.Form.ФормаЭлемента" -BorrowMainAttribute
|
argument-hint: -ExtensionPath <path> -ConfigPath <path> -Object "Catalog.Контрагенты.Form.ФормаЭлемента" -BorrowMainAttribute
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cfe-borrow — Заимствование объектов из конфигурации
|
# /cfe-borrow — Заимствование объектов из конфигурации
|
||||||
|
|
||||||
Заимствует объекты из основной конфигурации в расширение. Создаёт XML-файлы с `ObjectBelonging=Adopted` и `ExtendedConfigurationObject`, добавляет запись в ChildObjects расширения.
|
Заимствует объекты из основной конфигурации в расширение. Создаёт XML-файлы с `ObjectBelonging=Adopted` и `ExtendedConfigurationObject`, добавляет запись в ChildObjects расширения.
|
||||||
|
|
||||||
## Предусловие
|
## Предусловие
|
||||||
|
|
||||||
Расширение должно быть создано (`/cfe-init`) и содержать валидный `Configuration.xml`.
|
Расширение должно быть создано (`/cfe-init`) и содержать валидный `Configuration.xml`.
|
||||||
|
|
||||||
### Авто-определение ConfigPath
|
### Авто-определение ConfigPath
|
||||||
|
|
||||||
Если пользователь не указал `-ConfigPath` — попробуй определить автоматически:
|
Если пользователь не указал `-ConfigPath` — попробуй определить автоматически:
|
||||||
1. Прочитай `.v8-project.json` из корня проекта
|
1. Прочитай `.v8-project.json` из корня проекта
|
||||||
2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`)
|
2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`)
|
||||||
3. Если у базы есть поле `configSrc` — используй как `-ConfigPath`
|
3. Если у базы есть поле `configSrc` — используй как `-ConfigPath`
|
||||||
4. Если `configSrc` нет — спроси у пользователя
|
4. Если `configSrc` нет — спроси у пользователя
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| `ExtensionPath` | Путь к каталогу расширения (обязат.) |
|
| `ExtensionPath` | Путь к каталогу расширения (обязат.) |
|
||||||
| `ConfigPath` | Путь к конфигурации-источнику (обязат.) |
|
| `ConfigPath` | Путь к конфигурации-источнику (обязат.) |
|
||||||
| `Object` | Что заимствовать (обязат.), batch через `;;` |
|
| `Object` | Что заимствовать (обязат.), batch через `;;` |
|
||||||
| `BorrowMainAttribute` | Заимствовать основной реквизит формы. Без параметра — не заимствует. `Form` — реквизиты, используемые на форме. `All` — все реквизиты объекта. Требует форму в -Object |
|
| `BorrowMainAttribute` | Заимствовать основной реквизит формы. Без параметра — не заимствует. `Form` — реквизиты, используемые на форме. `All` — все реквизиты объекта. Требует форму в -Object |
|
||||||
|
|
||||||
## Формат -Object
|
## Формат -Object
|
||||||
|
|
||||||
- `Catalog.Контрагенты` — справочник
|
- `Catalog.Контрагенты` — справочник
|
||||||
- `CommonModule.РаботаСФайлами` — общий модуль
|
- `CommonModule.РаботаСФайлами` — общий модуль
|
||||||
- `Document.РеализацияТоваров` — документ
|
- `Document.РеализацияТоваров` — документ
|
||||||
- `Enum.ВидыОплат` — перечисление
|
- `Enum.ВидыОплат` — перечисление
|
||||||
- `Catalog.Контрагенты.Form.ФормаЭлемента` — форма объекта (заимствование формы)
|
- `Catalog.Контрагенты.Form.ФормаЭлемента` — форма объекта (заимствование формы)
|
||||||
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
|
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
|
||||||
Поддерживаются все 44 типа объектов конфигурации.
|
Поддерживаются все 44 типа объектов конфигурации.
|
||||||
|
|
||||||
### Заимствование форм
|
### Заимствование форм
|
||||||
|
|
||||||
Формат `Тип.Имя.Form.ИмяФормы` заимствует форму конкретного объекта. Если родительский объект ещё не заимствован — он будет заимствован автоматически.
|
Формат `Тип.Имя.Form.ИмяФормы` заимствует форму конкретного объекта. Если родительский объект ещё не заимствован — он будет заимствован автоматически.
|
||||||
|
|
||||||
Создаётся:
|
Создаётся:
|
||||||
1. **Метаданные формы** — `Forms/ИмяФормы.xml` с `ObjectBelonging=Adopted`, `FormType=Managed`
|
1. **Метаданные формы** — `Forms/ИмяФормы.xml` с `ObjectBelonging=Adopted`, `FormType=Managed`
|
||||||
2. **Form.xml** — `Forms/ИмяФормы/Ext/Form.xml` с копией исходной формы + `<BaseForm>` (начальное состояние)
|
2. **Form.xml** — `Forms/ИмяФормы/Ext/Form.xml` с копией исходной формы + `<BaseForm>` (начальное состояние)
|
||||||
3. **Module.bsl** — пустой файл `Forms/ИмяФормы/Ext/Form/Module.bsl`
|
3. **Module.bsl** — пустой файл `Forms/ИмяФормы/Ext/Form/Module.bsl`
|
||||||
4. **Регистрация** — `<Form>` в ChildObjects родительского объекта
|
4. **Регистрация** — `<Form>` в ChildObjects родительского объекта
|
||||||
|
|
||||||
### Заимствование основного реквизита формы (-BorrowMainAttribute)
|
### Заимствование основного реквизита формы (-BorrowMainAttribute)
|
||||||
|
|
||||||
**Когда нужно**: пользователь хочет добавить новый реквизит в существующий объект конфигурации и вывести его на заимствованную форму. Без `-BorrowMainAttribute` форма заимствуется "пустой" — только визуальные элементы, без привязки к данным объекта. С `-BorrowMainAttribute` форма сохраняет привязки к реквизитам объекта (DataPath), что позволяет затем добавить на неё новые элементы через `/form-edit`.
|
**Когда нужно**: пользователь хочет добавить новый реквизит в существующий объект конфигурации и вывести его на заимствованную форму. Без `-BorrowMainAttribute` форма заимствуется "пустой" — только визуальные элементы, без привязки к данным объекта. С `-BorrowMainAttribute` форма сохраняет привязки к реквизитам объекта (DataPath), что позволяет затем добавить на неё новые элементы через `/form-edit`.
|
||||||
|
|
||||||
**Два режима**:
|
**Два режима**:
|
||||||
- `Form` (по умолчанию) — заимствует только те реквизиты объекта, которые уже выведены на форму. Оптимальный выбор для большинства случаев
|
- `Form` (по умолчанию) — заимствует только те реквизиты объекта, которые уже выведены на форму. Оптимальный выбор для большинства случаев
|
||||||
- `All` — заимствует все реквизиты и табличные части объекта. Используй если планируешь выводить на форму реквизиты, которых на ней ещё нет
|
- `All` — заимствует все реквизиты и табличные части объекта. Используй если планируешь выводить на форму реквизиты, которых на ней ещё нет
|
||||||
|
|
||||||
**Типовой сценарий** (добавление реквизита + вывод на форму):
|
**Типовой сценарий** (добавление реквизита + вывод на форму):
|
||||||
1. `/cfe-borrow` с `-BorrowMainAttribute` — заимствовать форму с реквизитами
|
1. `/cfe-borrow` с `-BorrowMainAttribute` — заимствовать форму с реквизитами
|
||||||
2. `/meta-edit` — добавить новый реквизит в объект расширения
|
2. `/meta-edit` — добавить новый реквизит в объект расширения
|
||||||
3. `/form-edit` — вывести реквизит на заимствованную форму
|
3. `/form-edit` — вывести реквизит на заимствованную форму
|
||||||
|
|
||||||
**Защита существующих данных**: если зависимый объект уже заимствован с содержимым (реквизитами, формами) — скрипт не перезаписывает его, а добавляет только недостающее.
|
**Защита существующих данных**: если зависимый объект уже заимствован с содержимым (реквизитами, формами) — скрипт не перезаписывает его, а добавляет только недостающее.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-borrow.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
powershell.exe -NoProfile -File ".opencode/skills/cfe-borrow/scripts/cfe-borrow.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Заимствовать один объект
|
# Заимствовать один объект
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
||||||
|
|
||||||
# Заимствовать форму (автоматически заимствует родительский объект)
|
# Заимствовать форму (автоматически заимствует родительский объект)
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты.Form.ФормаЭлемента"
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты.Form.ФормаЭлемента"
|
||||||
|
|
||||||
# Несколько объектов за раз
|
# Несколько объектов за раз
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
|
||||||
|
|
||||||
# Заимствовать форму с основным реквизитом (реквизиты по DataPath формы)
|
# Заимствовать форму с основным реквизитом (реквизиты по DataPath формы)
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute
|
||||||
|
|
||||||
# Заимствовать форму с ВСЕМИ реквизитами объекта
|
# Заимствовать форму с ВСЕМИ реквизитами объекта
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All
|
||||||
```
|
```
|
||||||
|
|
||||||
## Верификация
|
## Верификация
|
||||||
|
|
||||||
```
|
```
|
||||||
/cfe-validate <ExtensionPath>
|
/cfe-validate <ExtensionPath>
|
||||||
```
|
```
|
||||||
|
|
||||||
+1866
-1772
File diff suppressed because it is too large
Load Diff
+202
-113
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-borrow v1.3 — Borrow objects from configuration into extension (CFE)
|
# cfe-borrow v1.8 — Borrow objects from configuration into extension (CFE)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,36 @@ XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
|||||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
V8_NS = "http://v8.1c.ru/8.1/data/core"
|
V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||||
|
|
||||||
|
# Form data-binding tags (value = attribute path). A binding survives only if its root
|
||||||
|
# attribute is borrowed into the form's <Attributes>; otherwise it must be stripped or the
|
||||||
|
# platform rejects the form with "Неверный путь к данным" on load.
|
||||||
|
FORM_BINDING_DATA_TAGS = ["DataPath", "TitleDataPath", "FooterDataPath", "HeaderDataPath", "MultipleValueDataPath", "MultipleValuePresentDataPath"]
|
||||||
|
# Picture-path binding tags (value = picture index path, never a data attribute) — always stripped in the skeleton.
|
||||||
|
FORM_BINDING_PICTURE_TAGS = ["RowPictureDataPath", "MultipleValuePictureDataPath"]
|
||||||
|
|
||||||
|
|
||||||
|
def strip_form_bindings(xml, keep_objekt):
|
||||||
|
"""Strip data-binding tags whose root attribute isn't borrowed.
|
||||||
|
keep_objekt=True (BorrowMainAttribute): keep Объект.* data bindings, strip the rest.
|
||||||
|
keep_objekt=False (default skeleton): strip all bindings. Picture-path tags are always stripped."""
|
||||||
|
for tag in FORM_BINDING_DATA_TAGS:
|
||||||
|
if keep_objekt:
|
||||||
|
xml = re.sub(rf'\s*<{tag}>(?!Объект\.)[^<]*</{tag}>', '', xml)
|
||||||
|
else:
|
||||||
|
xml = re.sub(rf'\s*<{tag}>[^<]*</{tag}>', '', xml)
|
||||||
|
for tag in FORM_BINDING_PICTURE_TAGS:
|
||||||
|
xml = re.sub(rf'\s*<{tag}>[^<]*</{tag}>', '', xml)
|
||||||
|
return xml
|
||||||
|
|
||||||
|
|
||||||
|
def decode_numeric_entities(s):
|
||||||
|
"""lxml emits numeric character refs (&#xNNNN;) for non-ASCII in some self-closed
|
||||||
|
elements where the PowerShell port writes literal characters. Normalize numeric refs
|
||||||
|
back to literal so PS↔PY output matches. Named entities (& < ...) are left intact."""
|
||||||
|
s = re.sub(r'&#x([0-9A-Fa-f]+);', lambda m: chr(int(m.group(1), 16)), s)
|
||||||
|
s = re.sub(r'&#(\d+);', lambda m: chr(int(m.group(1))), s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def localname(el):
|
def localname(el):
|
||||||
return etree.QName(el.tag).localname
|
return etree.QName(el.tag).localname
|
||||||
@@ -462,6 +492,13 @@ def main():
|
|||||||
prop_node = props_node.find(f"{{{MD_NS}}}{prop_name}")
|
prop_node = props_node.find(f"{{{MD_NS}}}{prop_name}")
|
||||||
if prop_node is not None:
|
if prop_node is not None:
|
||||||
src_props[prop_name] = (prop_node.text or "").strip()
|
src_props[prop_name] = (prop_node.text or "").strip()
|
||||||
|
# DefinedType: carry the <Type> definition. A type alias is meaningless as a bare shell —
|
||||||
|
# the platform needs its underlying type (e.g. to know a column is a summable Number for totals).
|
||||||
|
if type_name == "DefinedType":
|
||||||
|
type_node = props_node.find(f"{{{MD_NS}}}Type")
|
||||||
|
if type_node is not None:
|
||||||
|
type_xml = etree.tostring(type_node, encoding="unicode")
|
||||||
|
src_props["__TypeXml"] = re.sub(r'\s+xmlns(?::\w+)?="[^"]*"', '', type_xml)
|
||||||
|
|
||||||
return {"Uuid": src_uuid, "Properties": src_props, "Element": src_el}
|
return {"Uuid": src_uuid, "Properties": src_props, "Element": src_el}
|
||||||
|
|
||||||
@@ -533,6 +570,10 @@ def main():
|
|||||||
prop_val = source_props.get(prop_name, "false")
|
prop_val = source_props.get(prop_name, "false")
|
||||||
lines.append(f"\t\t\t<{prop_name}>{prop_val}</{prop_name}>")
|
lines.append(f"\t\t\t<{prop_name}>{prop_val}</{prop_name}>")
|
||||||
|
|
||||||
|
# DefinedType: emit the carried <Type> definition (needed for the alias to resolve, e.g. totals)
|
||||||
|
if type_name == "DefinedType" and "__TypeXml" in source_props:
|
||||||
|
lines.append(f"\t\t\t{source_props['__TypeXml']}")
|
||||||
|
|
||||||
lines.append("\t\t</Properties>")
|
lines.append("\t\t</Properties>")
|
||||||
|
|
||||||
if type_name in TYPES_WITH_CHILD_OBJECTS:
|
if type_name in TYPES_WITH_CHILD_OBJECTS:
|
||||||
@@ -644,7 +685,26 @@ def main():
|
|||||||
first_level = {}
|
first_level = {}
|
||||||
deep_paths = []
|
deep_paths = []
|
||||||
|
|
||||||
for m in re.finditer(r'<DataPath>[^<]*\b\u041e\u0431\u044a\u0435\u043a\u0442\.(\w+(?:\.\w+)*)</DataPath>', content):
|
# Scan every data-binding tag (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*)
|
||||||
|
# for Объект.* references — picture-path tags carry picture indices, not data attributes.
|
||||||
|
for tag in FORM_BINDING_DATA_TAGS:
|
||||||
|
for m in re.finditer(r'<' + tag + r'>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</' + tag + r'>', content):
|
||||||
|
path = m.group(1)
|
||||||
|
segments = path.split(".")
|
||||||
|
seg0 = segments[0]
|
||||||
|
if seg0 in STANDARD_FIELDS:
|
||||||
|
continue
|
||||||
|
first_level[seg0] = True
|
||||||
|
if len(segments) >= 2:
|
||||||
|
seg1 = segments[1]
|
||||||
|
if seg1 in STANDARD_FIELDS:
|
||||||
|
continue
|
||||||
|
seg2 = segments[2] if len(segments) >= 3 else None
|
||||||
|
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2})
|
||||||
|
|
||||||
|
# Also scan <Field>Объект.X</Field> — object attributes referenced by filter/conditional-appearance
|
||||||
|
# fields (and dynamic lists), not via a *DataPath binding (e.g. УдалитьЮрФизЛицо). Designer borrows these too.
|
||||||
|
for m in re.finditer(r'<Field>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</Field>', content):
|
||||||
path = m.group(1)
|
path = m.group(1)
|
||||||
segments = path.split(".")
|
segments = path.split(".")
|
||||||
seg0 = segments[0]
|
seg0 = segments[0]
|
||||||
@@ -655,22 +715,14 @@ def main():
|
|||||||
seg1 = segments[1]
|
seg1 = segments[1]
|
||||||
if seg1 in STANDARD_FIELDS:
|
if seg1 in STANDARD_FIELDS:
|
||||||
continue
|
continue
|
||||||
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1})
|
seg2 = segments[2] if len(segments) >= 3 else None
|
||||||
|
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2})
|
||||||
# Also collect from TitleDataPath
|
|
||||||
for m in re.finditer(r'<TitleDataPath>[^<]*\b\u041e\u0431\u044a\u0435\u043a\u0442\.(\w+(?:\.\w+)*)</TitleDataPath>', content):
|
|
||||||
path = m.group(1)
|
|
||||||
segments = path.split(".")
|
|
||||||
seg0 = segments[0]
|
|
||||||
if seg0 in STANDARD_FIELDS:
|
|
||||||
continue
|
|
||||||
first_level[seg0] = True
|
|
||||||
|
|
||||||
# Deduplicate deep paths
|
# Deduplicate deep paths
|
||||||
seen = set()
|
seen = set()
|
||||||
unique_deep = []
|
unique_deep = []
|
||||||
for dp in deep_paths:
|
for dp in deep_paths:
|
||||||
key = f"{dp['ObjectAttr']}.{dp['SubAttr']}"
|
key = f"{dp['ObjectAttr']}.{dp['SubAttr']}.{dp.get('SubSubAttr')}"
|
||||||
if key not in seen:
|
if key not in seen:
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
unique_deep.append(dp)
|
unique_deep.append(dp)
|
||||||
@@ -941,26 +993,40 @@ def main():
|
|||||||
# Step 3: Build the adopted content and insert into main object XML
|
# Step 3: Build the adopted content and insert into main object XML
|
||||||
obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml")
|
obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml")
|
||||||
|
|
||||||
# Generate full object XML with attributes and TS
|
# Read existing object XML (needed for dedup + enrichment)
|
||||||
content_parts = []
|
|
||||||
for attr in src_attrs:
|
|
||||||
attr_xml = build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t")
|
|
||||||
content_parts.append(attr_xml)
|
|
||||||
for ts in src_ts:
|
|
||||||
ts_xml = build_adopted_tabular_section_xml(ts["Name"], ts["Uuid"], ts["GeneratedTypes"], ts["Attributes"], "\t\t\t")
|
|
||||||
content_parts.append(ts_xml)
|
|
||||||
adopted_content = "\n".join(content_parts).rstrip()
|
|
||||||
|
|
||||||
# Read existing object XML and inject
|
|
||||||
with open(obj_file, "r", encoding="utf-8-sig") as fh:
|
with open(obj_file, "r", encoding="utf-8-sig") as fh:
|
||||||
obj_content = fh.read()
|
obj_content = fh.read()
|
||||||
|
|
||||||
# Inject extra properties after ExtendedConfigurationObject
|
# Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
|
||||||
|
existing_child_names = set()
|
||||||
|
m_co = re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content)
|
||||||
|
if m_co:
|
||||||
|
for nm in re.findall(r'<Name>(\w+)</Name>', m_co.group(1)):
|
||||||
|
existing_child_names.add(nm)
|
||||||
|
insert_attrs = [a for a in src_attrs if a["Name"] not in existing_child_names]
|
||||||
|
insert_ts = [t for t in src_ts if t["Name"] not in existing_child_names]
|
||||||
|
|
||||||
|
# Generate full object XML with attributes and TS
|
||||||
|
content_parts = []
|
||||||
|
for attr in insert_attrs:
|
||||||
|
content_parts.append(build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t"))
|
||||||
|
for ts in insert_ts:
|
||||||
|
content_parts.append(build_adopted_tabular_section_xml(ts["Name"], ts["Uuid"], ts["GeneratedTypes"], ts["Attributes"], "\t\t\t"))
|
||||||
|
adopted_content = "\n".join(content_parts).rstrip()
|
||||||
|
|
||||||
|
# Inject extra properties into the object's OWN Properties only — idempotent and anchored to the
|
||||||
|
# first ExtendedConfigurationObject (the object's). On re-borrow, adopted attributes each have their
|
||||||
|
# own ExtendedConfigurationObject; a global replace would push object props inside every <Attribute>.
|
||||||
if extra_props:
|
if extra_props:
|
||||||
|
m_props = re.search(r'(?s)<Properties>(.*?)</Properties>', obj_content)
|
||||||
|
obj_props_block = m_props.group(1) if m_props else ""
|
||||||
props_xml = ""
|
props_xml = ""
|
||||||
for p_name, p_val in extra_props.items():
|
for p_name, p_val in extra_props.items():
|
||||||
|
if f"<{p_name}>" in obj_props_block:
|
||||||
|
continue
|
||||||
props_xml += f"\r\n\t\t\t<{p_name}>{p_val}</{p_name}>"
|
props_xml += f"\r\n\t\t\t<{p_name}>{p_val}</{p_name}>"
|
||||||
obj_content = obj_content.replace("</ExtendedConfigurationObject>", f"</ExtendedConfigurationObject>{props_xml}")
|
if props_xml:
|
||||||
|
obj_content = obj_content.replace("</ExtendedConfigurationObject>", f"</ExtendedConfigurationObject>{props_xml}", 1)
|
||||||
|
|
||||||
# Replace empty ChildObjects with adopted content
|
# Replace empty ChildObjects with adopted content
|
||||||
if adopted_content:
|
if adopted_content:
|
||||||
@@ -1012,79 +1078,93 @@ def main():
|
|||||||
|
|
||||||
# Step 5: Handle deep paths (Form mode only)
|
# Step 5: Handle deep paths (Form mode only)
|
||||||
if mode == "Form" and deep_paths:
|
if mode == "Form" and deep_paths:
|
||||||
# Filter out deep paths where ObjectAttr is a TabularSection
|
# Top-level ref deep paths: Объект.<Ref>.<Sub> — borrow the ref attribute's catalog with the sub-attribute
|
||||||
real_deep = [dp for dp in deep_paths if dp["ObjectAttr"] not in ts_names]
|
deep_by_attr = {}
|
||||||
|
for dp in deep_paths:
|
||||||
if real_deep:
|
if dp["ObjectAttr"] in ts_names:
|
||||||
info(f" Processing {len(real_deep)} deep path(s)...")
|
continue
|
||||||
|
deep_by_attr.setdefault(dp["ObjectAttr"], [])
|
||||||
# Group by ObjectAttr -> target catalog
|
if dp["SubAttr"] not in deep_by_attr[dp["ObjectAttr"]]:
|
||||||
deep_by_attr = {}
|
|
||||||
for dp in real_deep:
|
|
||||||
if dp["ObjectAttr"] not in deep_by_attr:
|
|
||||||
deep_by_attr[dp["ObjectAttr"]] = []
|
|
||||||
deep_by_attr[dp["ObjectAttr"]].append(dp["SubAttr"])
|
deep_by_attr[dp["ObjectAttr"]].append(dp["SubAttr"])
|
||||||
|
if deep_by_attr:
|
||||||
|
info(f" Processing {len(deep_by_attr)} deep path attribute(s)...")
|
||||||
for attr_name, sub_attr_names in deep_by_attr.items():
|
for attr_name, sub_attr_names in deep_by_attr.items():
|
||||||
# Find the attribute's type to determine target catalog
|
attr_info = next((a for a in src_attrs if a["Name"] == attr_name), None)
|
||||||
attr_info = None
|
|
||||||
for a in src_attrs:
|
|
||||||
if a["Name"] == attr_name:
|
|
||||||
attr_info = a
|
|
||||||
break
|
|
||||||
if not attr_info:
|
if not attr_info:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Extract catalog name from type: cfg:CatalogRef.XXX
|
|
||||||
cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', attr_info["TypeXml"])
|
cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', attr_info["TypeXml"])
|
||||||
if not cat_match:
|
if not cat_match:
|
||||||
continue
|
continue
|
||||||
|
borrow_deep_target_attrs(cat_match.group(1), cat_match.group(2), sub_attr_names)
|
||||||
|
|
||||||
target_type_name = cat_match.group(1)
|
# Tabular-section deep paths: Объект.<ТЧ>.<Колонка>.<Sub> — borrow the column's catalog with the sub-attribute
|
||||||
target_obj_name = cat_match.group(2)
|
ts_deep_by_col = {}
|
||||||
|
for dp in deep_paths:
|
||||||
# Ensure target is borrowed
|
if dp["ObjectAttr"] not in ts_names:
|
||||||
if not test_object_borrowed(target_type_name, target_obj_name):
|
continue
|
||||||
t_src = read_source_object(target_type_name, target_obj_name)
|
if not dp.get("SubSubAttr"):
|
||||||
t_borrowed_xml = build_borrowed_object_xml(target_type_name, target_obj_name, t_src["Uuid"], t_src["Properties"])
|
continue
|
||||||
t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name])
|
if dp["SubSubAttr"] in STANDARD_FIELDS:
|
||||||
os.makedirs(t_target_dir, exist_ok=True)
|
continue
|
||||||
t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml")
|
k = (dp["ObjectAttr"], dp["SubAttr"])
|
||||||
save_text_bom(t_target_file, t_borrowed_xml)
|
ts_deep_by_col.setdefault(k, [])
|
||||||
add_to_child_objects(target_type_name, target_obj_name)
|
if dp["SubSubAttr"] not in ts_deep_by_col[k]:
|
||||||
borrowed_files.append(t_target_file)
|
ts_deep_by_col[k].append(dp["SubSubAttr"])
|
||||||
info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}")
|
if ts_deep_by_col:
|
||||||
|
info(f" Processing {len(ts_deep_by_col)} tabular-section deep path(s)...")
|
||||||
# Resolve sub-attributes in target catalog
|
for (ts_name, col_name), sub_attr_names in ts_deep_by_col.items():
|
||||||
sub_names = {sn: True for sn in sub_attr_names}
|
ts_info = next((t for t in src_ts if t["Name"] == ts_name), None)
|
||||||
sub_resolved = resolve_source_attributes(target_type_name, target_obj_name, sub_names)
|
if not ts_info:
|
||||||
|
continue
|
||||||
if sub_resolved["Attributes"]:
|
col_info = next((c for c in ts_info["Attributes"] if c["Name"] == col_name), None)
|
||||||
merge_attributes_into_object(target_type_name, target_obj_name, sub_resolved["Attributes"])
|
if not col_info:
|
||||||
|
continue
|
||||||
# Collect and borrow ref types from deep attributes
|
cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', col_info["TypeXml"])
|
||||||
sub_type_xmls = [sa["TypeXml"] for sa in sub_resolved["Attributes"]]
|
if not cat_match:
|
||||||
sub_ref_types = collect_reference_types(sub_type_xmls)
|
continue
|
||||||
for srt in sub_ref_types:
|
borrow_deep_target_attrs(cat_match.group(1), cat_match.group(2), sub_attr_names)
|
||||||
if srt["TypeName"] not in CHILD_TYPE_DIR_MAP:
|
|
||||||
continue
|
|
||||||
if test_object_borrowed(srt["TypeName"], srt["ObjName"]):
|
|
||||||
continue
|
|
||||||
s_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]], f"{srt['ObjName']}.xml")
|
|
||||||
if not os.path.isfile(s_src_file):
|
|
||||||
continue
|
|
||||||
s_src = read_source_object(srt["TypeName"], srt["ObjName"])
|
|
||||||
s_borrowed_xml = build_borrowed_object_xml(srt["TypeName"], srt["ObjName"], s_src["Uuid"], s_src["Properties"])
|
|
||||||
s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]])
|
|
||||||
os.makedirs(s_target_dir, exist_ok=True)
|
|
||||||
s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml")
|
|
||||||
save_text_bom(s_target_file, s_borrowed_xml)
|
|
||||||
add_to_child_objects(srt["TypeName"], srt["ObjName"])
|
|
||||||
borrowed_files.append(s_target_file)
|
|
||||||
info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}")
|
|
||||||
|
|
||||||
info(" Main attribute borrowing complete")
|
info(" Main attribute borrowing complete")
|
||||||
|
|
||||||
|
def borrow_deep_target_attrs(target_type_name, target_obj_name, sub_attr_names):
|
||||||
|
# Borrow a deep-path target catalog together with the referenced sub-attributes, for both
|
||||||
|
# Объект.<Ref>.<Sub> and Объект.<ТЧ>.<Колонка>.<Sub>. Mirrors Designer: the referenced catalog
|
||||||
|
# is adopted WITH the sub-attributes the form shows, else the platform rejects the deep DataPath.
|
||||||
|
if not test_object_borrowed(target_type_name, target_obj_name):
|
||||||
|
t_src = read_source_object(target_type_name, target_obj_name)
|
||||||
|
t_borrowed_xml = build_borrowed_object_xml(target_type_name, target_obj_name, t_src["Uuid"], t_src["Properties"])
|
||||||
|
t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name])
|
||||||
|
os.makedirs(t_target_dir, exist_ok=True)
|
||||||
|
t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml")
|
||||||
|
save_text_bom(t_target_file, t_borrowed_xml)
|
||||||
|
add_to_child_objects(target_type_name, target_obj_name)
|
||||||
|
borrowed_files.append(t_target_file)
|
||||||
|
info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}")
|
||||||
|
|
||||||
|
sub_names = {sn: True for sn in sub_attr_names}
|
||||||
|
sub_resolved = resolve_source_attributes(target_type_name, target_obj_name, sub_names)
|
||||||
|
if sub_resolved["Attributes"]:
|
||||||
|
merge_attributes_into_object(target_type_name, target_obj_name, sub_resolved["Attributes"])
|
||||||
|
sub_type_xmls = [sa["TypeXml"] for sa in sub_resolved["Attributes"]]
|
||||||
|
sub_ref_types = collect_reference_types(sub_type_xmls)
|
||||||
|
for srt in sub_ref_types:
|
||||||
|
if srt["TypeName"] not in CHILD_TYPE_DIR_MAP:
|
||||||
|
continue
|
||||||
|
if test_object_borrowed(srt["TypeName"], srt["ObjName"]):
|
||||||
|
continue
|
||||||
|
s_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]], f"{srt['ObjName']}.xml")
|
||||||
|
if not os.path.isfile(s_src_file):
|
||||||
|
continue
|
||||||
|
s_src = read_source_object(srt["TypeName"], srt["ObjName"])
|
||||||
|
s_borrowed_xml = build_borrowed_object_xml(srt["TypeName"], srt["ObjName"], s_src["Uuid"], s_src["Properties"])
|
||||||
|
s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]])
|
||||||
|
os.makedirs(s_target_dir, exist_ok=True)
|
||||||
|
s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml")
|
||||||
|
save_text_bom(s_target_file, s_borrowed_xml)
|
||||||
|
add_to_child_objects(srt["TypeName"], srt["ObjName"])
|
||||||
|
borrowed_files.append(s_target_file)
|
||||||
|
info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}")
|
||||||
|
|
||||||
def borrow_form(type_name, obj_name, form_name, borrow_main_attr=False):
|
def borrow_form(type_name, obj_name, form_name, borrow_main_attr=False):
|
||||||
dir_name = CHILD_TYPE_DIR_MAP[type_name]
|
dir_name = CHILD_TYPE_DIR_MAP[type_name]
|
||||||
|
|
||||||
@@ -1100,8 +1180,22 @@ def main():
|
|||||||
with open(src_form_xml_path, "r", encoding="utf-8-sig") as fh:
|
with open(src_form_xml_path, "r", encoding="utf-8-sig") as fh:
|
||||||
src_form_content = fh.read()
|
src_form_content = fh.read()
|
||||||
|
|
||||||
# 3. Generate form metadata XML
|
# 3. Generate form metadata XML.
|
||||||
new_form_uuid = new_guid()
|
# If the wrapper was already borrowed, reuse its uuid so re-borrow is idempotent
|
||||||
|
# (regenerating it would churn the form's identity on every rerun).
|
||||||
|
existing_wrapper = os.path.join(ext_dir, dir_name, obj_name, "Forms", f"{form_name}.xml")
|
||||||
|
new_form_uuid = ""
|
||||||
|
if os.path.isfile(existing_wrapper):
|
||||||
|
try:
|
||||||
|
existing_root = etree.parse(existing_wrapper).getroot()
|
||||||
|
for c in existing_root:
|
||||||
|
if isinstance(c.tag, str) and localname(c) == "Form":
|
||||||
|
new_form_uuid = c.get("uuid", "") or ""
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
new_form_uuid = ""
|
||||||
|
if not new_form_uuid:
|
||||||
|
new_form_uuid = new_guid()
|
||||||
form_meta_lines = [
|
form_meta_lines = [
|
||||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
f'<MetaDataObject {XMLNS_DECL} version="{format_version}">',
|
f'<MetaDataObject {XMLNS_DECL} version="{format_version}">',
|
||||||
@@ -1131,7 +1225,10 @@ def main():
|
|||||||
src_form_tree = etree.parse(src_form_xml_path, src_form_parser)
|
src_form_tree = etree.parse(src_form_xml_path, src_form_parser)
|
||||||
src_form_el = src_form_tree.getroot()
|
src_form_el = src_form_tree.getroot()
|
||||||
|
|
||||||
form_version = src_form_el.get("version", format_version)
|
# Borrowed form uses the extension's format version (not the source form's) — keeps the
|
||||||
|
# extension uniform; otherwise the platform rejects the import on a version mismatch
|
||||||
|
# (e.g. a 2.13 form inside a 2.17 extension). The platform upgrades the form to the root version.
|
||||||
|
form_version = format_version
|
||||||
|
|
||||||
src_auto_cmd = None
|
src_auto_cmd = None
|
||||||
form_props = []
|
form_props = []
|
||||||
@@ -1149,25 +1246,21 @@ def main():
|
|||||||
continue
|
continue
|
||||||
if not reached_visual:
|
if not reached_visual:
|
||||||
# Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.)
|
# Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.)
|
||||||
form_props.append(etree.tostring(fc, encoding="unicode"))
|
form_props.append(decode_numeric_entities(etree.tostring(fc, encoding="unicode")))
|
||||||
|
|
||||||
ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
|
ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
|
||||||
|
|
||||||
# AutoCommandBar: keep ChildItems (buttons with CommandName->0), Autofill->false
|
# AutoCommandBar: keep ChildItems (buttons with CommandName->0), Autofill->false
|
||||||
auto_cmd_xml = ""
|
auto_cmd_xml = ""
|
||||||
if src_auto_cmd is not None:
|
if src_auto_cmd is not None:
|
||||||
auto_cmd_xml = etree.tostring(src_auto_cmd, encoding="unicode")
|
auto_cmd_xml = decode_numeric_entities(etree.tostring(src_auto_cmd, encoding="unicode"))
|
||||||
auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml)
|
auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml)
|
||||||
auto_cmd_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', auto_cmd_xml)
|
auto_cmd_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', auto_cmd_xml)
|
||||||
auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>')
|
auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>')
|
||||||
# Strip ExcludedCommand (references to standard commands invalid in extension)
|
# Strip ExcludedCommand (references to standard commands invalid in extension)
|
||||||
auto_cmd_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', auto_cmd_xml)
|
auto_cmd_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', auto_cmd_xml)
|
||||||
# Strip DataPath in AutoCommandBar buttons
|
# Strip data-binding tags whose root attribute isn't borrowed
|
||||||
if borrow_main_attr:
|
auto_cmd_xml = strip_form_bindings(auto_cmd_xml, borrow_main_attr)
|
||||||
# Keep only Объект.* DataPaths
|
|
||||||
auto_cmd_xml = re.sub(r'\s*<DataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</DataPath>', '', auto_cmd_xml)
|
|
||||||
else:
|
|
||||||
auto_cmd_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', auto_cmd_xml)
|
|
||||||
|
|
||||||
# ChildItems: copy full tree, clean up base-config references
|
# ChildItems: copy full tree, clean up base-config references
|
||||||
child_items_xml = ""
|
child_items_xml = ""
|
||||||
@@ -1178,20 +1271,12 @@ def main():
|
|||||||
break
|
break
|
||||||
|
|
||||||
if src_child_items is not None:
|
if src_child_items is not None:
|
||||||
child_items_xml = etree.tostring(src_child_items, encoding="unicode")
|
child_items_xml = decode_numeric_entities(etree.tostring(src_child_items, encoding="unicode"))
|
||||||
child_items_xml = ns_strip_pattern.sub("", child_items_xml)
|
child_items_xml = ns_strip_pattern.sub("", child_items_xml)
|
||||||
# Replace all CommandName values with 0
|
# Replace all CommandName values with 0
|
||||||
child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml)
|
child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml)
|
||||||
# Strip DataPath / TitleDataPath / RowPictureDataPath
|
# Strip data-binding tags whose root attribute isn't borrowed
|
||||||
if borrow_main_attr:
|
child_items_xml = strip_form_bindings(child_items_xml, borrow_main_attr)
|
||||||
# Keep only Объект.* DataPaths — strip form-attribute DataPaths (not borrowed)
|
|
||||||
child_items_xml = re.sub(r'\s*<DataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</DataPath>', '', child_items_xml)
|
|
||||||
child_items_xml = re.sub(r'\s*<TitleDataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</TitleDataPath>', '', child_items_xml)
|
|
||||||
child_items_xml = re.sub(r'\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '', child_items_xml)
|
|
||||||
else:
|
|
||||||
child_items_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', child_items_xml)
|
|
||||||
child_items_xml = re.sub(r'\s*<TitleDataPath>[^<]*</TitleDataPath>', '', child_items_xml)
|
|
||||||
child_items_xml = re.sub(r'\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '', child_items_xml)
|
|
||||||
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
|
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
|
||||||
child_items_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', child_items_xml)
|
child_items_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', child_items_xml)
|
||||||
# Strip TypeLink blocks with human-readable DataPath (Items.XXX)
|
# Strip TypeLink blocks with human-readable DataPath (Items.XXX)
|
||||||
@@ -1428,12 +1513,16 @@ def main():
|
|||||||
save_text_bom(form_xml_file, "".join(parts))
|
save_text_bom(form_xml_file, "".join(parts))
|
||||||
info(f" Created: {form_xml_file}")
|
info(f" Created: {form_xml_file}")
|
||||||
|
|
||||||
# 6. Create empty Module.bsl
|
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
|
||||||
|
# not clobber user code added to the form module).
|
||||||
module_dir = os.path.join(form_xml_dir, "Form")
|
module_dir = os.path.join(form_xml_dir, "Form")
|
||||||
os.makedirs(module_dir, exist_ok=True)
|
os.makedirs(module_dir, exist_ok=True)
|
||||||
module_bsl_file = os.path.join(module_dir, "Module.bsl")
|
module_bsl_file = os.path.join(module_dir, "Module.bsl")
|
||||||
save_text_bom(module_bsl_file, "")
|
if os.path.isfile(module_bsl_file):
|
||||||
info(f" Created: {module_bsl_file}")
|
info(" Preserved existing Module.bsl")
|
||||||
|
else:
|
||||||
|
save_text_bom(module_bsl_file, "")
|
||||||
|
info(f" Created: {module_bsl_file}")
|
||||||
|
|
||||||
# 7. Register form in parent object ChildObjects
|
# 7. Register form in parent object ChildObjects
|
||||||
register_form_in_object(type_name, obj_name, form_name)
|
register_form_in_object(type_name, obj_name, form_name)
|
||||||
@@ -1,57 +1,57 @@
|
|||||||
---
|
---
|
||||||
name: cfe-diff
|
name: cfe-diff
|
||||||
description: Анализ расширения конфигурации 1С (CFE) — состав, заимствованные объекты, перехватчики, проверка переноса. Используй когда нужно понять что содержит расширение или проверить перенесены ли вставки в конфигурацию
|
description: Анализ расширения конфигурации 1С (CFE) — состав, заимствованные объекты, перехватчики, проверка переноса. Используй когда нужно понять что содержит расширение или проверить перенесены ли вставки в конфигурацию
|
||||||
argument-hint: -ExtensionPath <path> -ConfigPath <path> [-Mode A|B]
|
argument-hint: -ExtensionPath <path> -ConfigPath <path> [-Mode A|B]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cfe-diff — Анализ расширения конфигурации
|
# /cfe-diff — Анализ расширения конфигурации
|
||||||
|
|
||||||
Анализирует расширение в двух режимах: обзор изменений (Mode A) или проверка переноса (Mode B).
|
Анализирует расширение в двух режимах: обзор изменений (Mode A) или проверка переноса (Mode B).
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
| Параметр | Описание | По умолчанию |
|
| Параметр | Описание | По умолчанию |
|
||||||
|----------|----------|--------------|
|
|----------|----------|--------------|
|
||||||
| `ExtensionPath` | Путь к расширению (обязат.) | — |
|
| `ExtensionPath` | Путь к расширению (обязат.) | — |
|
||||||
| `ConfigPath` | Путь к конфигурации (обязат.) | — |
|
| `ConfigPath` | Путь к конфигурации (обязат.) | — |
|
||||||
| `Mode` | `A` (обзор) / `B` (проверка переноса) | `A` |
|
| `Mode` | `A` (обзор) / `B` (проверка переноса) | `A` |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
|
powershell.exe -NoProfile -File ".opencode/skills/cfe-diff/scripts/cfe-diff.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mode A — обзор расширения
|
## Mode A — обзор расширения
|
||||||
|
|
||||||
Для каждого объекта показывает:
|
Для каждого объекта показывает:
|
||||||
- `[BORROWED]` — заимствованный: перехватчики (`&Перед`, `&После`, `&ИзменениеИКонтроль`, `&Вместо`), собственные реквизиты/ТЧ/формы
|
- `[BORROWED]` — заимствованный: перехватчики (`&Перед`, `&После`, `&ИзменениеИКонтроль`, `&Вместо`), собственные реквизиты/ТЧ/формы
|
||||||
- `[OWN]` — собственный: количество реквизитов, ТЧ, форм
|
- `[OWN]` — собственный: количество реквизитов, ТЧ, форм
|
||||||
|
|
||||||
Для каждой формы заимствованного объекта показывается:
|
Для каждой формы заимствованного объекта показывается:
|
||||||
- `(borrowed)` / `(own)` — заимствованная или собственная форма
|
- `(borrowed)` / `(own)` — заимствованная или собственная форма
|
||||||
- callType-события формы и элементов
|
- callType-события формы и элементов
|
||||||
- callType на командах
|
- callType на командах
|
||||||
|
|
||||||
## Mode B — проверка переноса
|
## Mode B — проверка переноса
|
||||||
|
|
||||||
Для каждого `&ИзменениеИКонтроль` извлекает блоки `#Вставка`/`#КонецВставки` из расширения и ищет их в соответствующем модуле конфигурации.
|
Для каждого `&ИзменениеИКонтроль` извлекает блоки `#Вставка`/`#КонецВставки` из расширения и ищет их в соответствующем модуле конфигурации.
|
||||||
|
|
||||||
Статусы:
|
Статусы:
|
||||||
- `[TRANSFERRED]` — код найден в конфигурации
|
- `[TRANSFERRED]` — код найден в конфигурации
|
||||||
- `[NOT_TRANSFERRED]` — код не найден
|
- `[NOT_TRANSFERRED]` — код не найден
|
||||||
- `[NEEDS_REVIEW]` — нет блоков `#Вставка` или модуль конфигурации не найден
|
- `[NEEDS_REVIEW]` — нет блоков `#Вставка` или модуль конфигурации не найден
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Обзор — что изменено в расширении
|
# Обзор — что изменено в расширении
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
|
||||||
|
|
||||||
# Проверка переноса — все ли #Вставка перенесены
|
# Проверка переноса — все ли #Вставка перенесены
|
||||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode B
|
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode B
|
||||||
```
|
```
|
||||||
+471
-471
@@ -1,471 +1,471 @@
|
|||||||
# cfe-diff v1.0 — Analyze and compare 1C configuration extension (CFE)
|
# cfe-diff v1.0 — Analyze and compare 1C configuration extension (CFE)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string]$ExtensionPath,
|
[string]$ExtensionPath,
|
||||||
|
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string]$ConfigPath,
|
[string]$ConfigPath,
|
||||||
|
|
||||||
[ValidateSet("A","B")]
|
[ValidateSet("A","B")]
|
||||||
[string]$Mode = "A"
|
[string]$Mode = "A"
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve paths ---
|
# --- Resolve paths ---
|
||||||
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
|
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
|
||||||
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
|
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
|
||||||
}
|
}
|
||||||
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
|
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
|
||||||
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
|
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
|
||||||
}
|
}
|
||||||
if (Test-Path $ExtensionPath -PathType Leaf) { $ExtensionPath = Split-Path $ExtensionPath -Parent }
|
if (Test-Path $ExtensionPath -PathType Leaf) { $ExtensionPath = Split-Path $ExtensionPath -Parent }
|
||||||
if (Test-Path $ConfigPath -PathType Leaf) { $ConfigPath = Split-Path $ConfigPath -Parent }
|
if (Test-Path $ConfigPath -PathType Leaf) { $ConfigPath = Split-Path $ConfigPath -Parent }
|
||||||
|
|
||||||
$extCfg = Join-Path $ExtensionPath "Configuration.xml"
|
$extCfg = Join-Path $ExtensionPath "Configuration.xml"
|
||||||
$srcCfg = Join-Path $ConfigPath "Configuration.xml"
|
$srcCfg = Join-Path $ConfigPath "Configuration.xml"
|
||||||
if (-not (Test-Path $extCfg)) { Write-Error "Extension Configuration.xml not found: $extCfg"; exit 1 }
|
if (-not (Test-Path $extCfg)) { Write-Error "Extension Configuration.xml not found: $extCfg"; exit 1 }
|
||||||
if (-not (Test-Path $srcCfg)) { Write-Error "Config Configuration.xml not found: $srcCfg"; exit 1 }
|
if (-not (Test-Path $srcCfg)) { Write-Error "Config Configuration.xml not found: $srcCfg"; exit 1 }
|
||||||
|
|
||||||
# --- Type -> directory mapping ---
|
# --- Type -> directory mapping ---
|
||||||
$childTypeDirMap = @{
|
$childTypeDirMap = @{
|
||||||
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
|
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
|
||||||
"CommonModule"="CommonModules"; "CommonPicture"="CommonPictures"
|
"CommonModule"="CommonModules"; "CommonPicture"="CommonPictures"
|
||||||
"CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates"
|
"CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates"
|
||||||
"ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors"
|
"ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors"
|
||||||
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
|
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
|
||||||
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
|
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
|
||||||
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
|
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
|
||||||
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
|
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
|
||||||
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
|
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
|
||||||
"Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants"
|
"Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants"
|
||||||
"FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes"
|
"FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes"
|
||||||
"FunctionalOptionsParameter"="FunctionalOptionsParameters"
|
"FunctionalOptionsParameter"="FunctionalOptionsParameters"
|
||||||
"CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals"
|
"CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals"
|
||||||
"SessionParameter"="SessionParameters"; "StyleItem"="StyleItems"
|
"SessionParameter"="SessionParameters"; "StyleItem"="StyleItems"
|
||||||
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
|
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
|
||||||
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
|
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
|
||||||
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
|
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
|
||||||
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
|
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
|
||||||
"CommonAttribute"="CommonAttributes"
|
"CommonAttribute"="CommonAttributes"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Parse extension Configuration.xml ---
|
# --- Parse extension Configuration.xml ---
|
||||||
$extDoc = New-Object System.Xml.XmlDocument
|
$extDoc = New-Object System.Xml.XmlDocument
|
||||||
$extDoc.PreserveWhitespace = $false
|
$extDoc.PreserveWhitespace = $false
|
||||||
$extDoc.Load($extCfg)
|
$extDoc.Load($extCfg)
|
||||||
|
|
||||||
$ns = New-Object System.Xml.XmlNamespaceManager($extDoc.NameTable)
|
$ns = New-Object System.Xml.XmlNamespaceManager($extDoc.NameTable)
|
||||||
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
|
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
|
||||||
|
|
||||||
$extProps = $extDoc.SelectSingleNode("//md:Configuration/md:Properties", $ns)
|
$extProps = $extDoc.SelectSingleNode("//md:Configuration/md:Properties", $ns)
|
||||||
$extNameNode = $extProps.SelectSingleNode("md:Name", $ns)
|
$extNameNode = $extProps.SelectSingleNode("md:Name", $ns)
|
||||||
$extName = if ($extNameNode) { $extNameNode.InnerText } else { "?" }
|
$extName = if ($extNameNode) { $extNameNode.InnerText } else { "?" }
|
||||||
$prefixNode = $extProps.SelectSingleNode("md:NamePrefix", $ns)
|
$prefixNode = $extProps.SelectSingleNode("md:NamePrefix", $ns)
|
||||||
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "" }
|
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "" }
|
||||||
$purposeNode = $extProps.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns)
|
$purposeNode = $extProps.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns)
|
||||||
$purpose = if ($purposeNode) { $purposeNode.InnerText } else { "?" }
|
$purpose = if ($purposeNode) { $purposeNode.InnerText } else { "?" }
|
||||||
|
|
||||||
Write-Host "=== cfe-diff Mode ${Mode}: $extName (${purpose}) ==="
|
Write-Host "=== cfe-diff Mode ${Mode}: $extName (${purpose}) ==="
|
||||||
Write-Host " NamePrefix: $namePrefix"
|
Write-Host " NamePrefix: $namePrefix"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
# --- Collect ChildObjects ---
|
# --- Collect ChildObjects ---
|
||||||
$childObjNode = $extDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns)
|
$childObjNode = $extDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns)
|
||||||
if (-not $childObjNode) {
|
if (-not $childObjNode) {
|
||||||
Write-Host "[WARN] No ChildObjects in extension"
|
Write-Host "[WARN] No ChildObjects in extension"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
$objects = @()
|
$objects = @()
|
||||||
foreach ($child in $childObjNode.ChildNodes) {
|
foreach ($child in $childObjNode.ChildNodes) {
|
||||||
if ($child.NodeType -ne 'Element') { continue }
|
if ($child.NodeType -ne 'Element') { continue }
|
||||||
if ($child.LocalName -eq "Language") { continue }
|
if ($child.LocalName -eq "Language") { continue }
|
||||||
$objects += @{ Type = $child.LocalName; Name = $child.InnerText }
|
$objects += @{ Type = $child.LocalName; Name = $child.InnerText }
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($objects.Count -eq 0) {
|
if ($objects.Count -eq 0) {
|
||||||
Write-Host "No objects (besides Language) in extension."
|
Write-Host "No objects (besides Language) in extension."
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Helper: check if object is borrowed ---
|
# --- Helper: check if object is borrowed ---
|
||||||
function Get-ObjectInfo {
|
function Get-ObjectInfo {
|
||||||
param([string]$objType, [string]$objName)
|
param([string]$objType, [string]$objName)
|
||||||
|
|
||||||
if (-not $childTypeDirMap.ContainsKey($objType)) { return $null }
|
if (-not $childTypeDirMap.ContainsKey($objType)) { return $null }
|
||||||
$dirName = $childTypeDirMap[$objType]
|
$dirName = $childTypeDirMap[$objType]
|
||||||
$objFile = Join-Path (Join-Path $ExtensionPath $dirName) "${objName}.xml"
|
$objFile = Join-Path (Join-Path $ExtensionPath $dirName) "${objName}.xml"
|
||||||
|
|
||||||
if (-not (Test-Path $objFile)) { return @{ Borrowed = $false; File = $objFile; Exists = $false } }
|
if (-not (Test-Path $objFile)) { return @{ Borrowed = $false; File = $objFile; Exists = $false } }
|
||||||
|
|
||||||
$doc = New-Object System.Xml.XmlDocument
|
$doc = New-Object System.Xml.XmlDocument
|
||||||
$doc.PreserveWhitespace = $false
|
$doc.PreserveWhitespace = $false
|
||||||
$doc.Load($objFile)
|
$doc.Load($objFile)
|
||||||
|
|
||||||
$objNs = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
$objNs = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
||||||
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
|
||||||
$objEl = $null
|
$objEl = $null
|
||||||
foreach ($c in $doc.DocumentElement.ChildNodes) {
|
foreach ($c in $doc.DocumentElement.ChildNodes) {
|
||||||
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
||||||
}
|
}
|
||||||
if (-not $objEl) { return @{ Borrowed = $false; File = $objFile; Exists = $true } }
|
if (-not $objEl) { return @{ Borrowed = $false; File = $objFile; Exists = $true } }
|
||||||
|
|
||||||
$propsEl = $objEl.SelectSingleNode("md:Properties", $objNs)
|
$propsEl = $objEl.SelectSingleNode("md:Properties", $objNs)
|
||||||
$obNode = if ($propsEl) { $propsEl.SelectSingleNode("md:ObjectBelonging", $objNs) } else { $null }
|
$obNode = if ($propsEl) { $propsEl.SelectSingleNode("md:ObjectBelonging", $objNs) } else { $null }
|
||||||
|
|
||||||
$info = @{
|
$info = @{
|
||||||
Borrowed = ($obNode -and $obNode.InnerText -eq "Adopted")
|
Borrowed = ($obNode -and $obNode.InnerText -eq "Adopted")
|
||||||
File = $objFile
|
File = $objFile
|
||||||
Exists = $true
|
Exists = $true
|
||||||
Type = $objType
|
Type = $objType
|
||||||
Name = $objName
|
Name = $objName
|
||||||
DirName = $dirName
|
DirName = $dirName
|
||||||
ObjElement = $objEl
|
ObjElement = $objEl
|
||||||
ObjNs = $objNs
|
ObjNs = $objNs
|
||||||
}
|
}
|
||||||
return $info
|
return $info
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Helper: find .bsl files for object ---
|
# --- Helper: find .bsl files for object ---
|
||||||
function Get-BslFiles {
|
function Get-BslFiles {
|
||||||
param([string]$objType, [string]$objName)
|
param([string]$objType, [string]$objName)
|
||||||
|
|
||||||
if (-not $childTypeDirMap.ContainsKey($objType)) { return @() }
|
if (-not $childTypeDirMap.ContainsKey($objType)) { return @() }
|
||||||
$dirName = $childTypeDirMap[$objType]
|
$dirName = $childTypeDirMap[$objType]
|
||||||
$objDir = Join-Path (Join-Path $ExtensionPath $dirName) $objName
|
$objDir = Join-Path (Join-Path $ExtensionPath $dirName) $objName
|
||||||
|
|
||||||
if (-not (Test-Path $objDir -PathType Container)) { return @() }
|
if (-not (Test-Path $objDir -PathType Container)) { return @() }
|
||||||
|
|
||||||
$bslFiles = @()
|
$bslFiles = @()
|
||||||
$extDir = Join-Path $objDir "Ext"
|
$extDir = Join-Path $objDir "Ext"
|
||||||
if (Test-Path $extDir) {
|
if (Test-Path $extDir) {
|
||||||
$items = Get-ChildItem -Path $extDir -Filter "*.bsl" -ErrorAction SilentlyContinue
|
$items = Get-ChildItem -Path $extDir -Filter "*.bsl" -ErrorAction SilentlyContinue
|
||||||
foreach ($item in $items) { $bslFiles += $item.FullName }
|
foreach ($item in $items) { $bslFiles += $item.FullName }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Forms
|
# Forms
|
||||||
$formsDir = Join-Path $objDir "Forms"
|
$formsDir = Join-Path $objDir "Forms"
|
||||||
if (Test-Path $formsDir) {
|
if (Test-Path $formsDir) {
|
||||||
$formModules = Get-ChildItem -Path $formsDir -Recurse -Filter "Module.bsl" -ErrorAction SilentlyContinue
|
$formModules = Get-ChildItem -Path $formsDir -Recurse -Filter "Module.bsl" -ErrorAction SilentlyContinue
|
||||||
foreach ($fm in $formModules) { $bslFiles += $fm.FullName }
|
foreach ($fm in $formModules) { $bslFiles += $fm.FullName }
|
||||||
}
|
}
|
||||||
|
|
||||||
return $bslFiles
|
return $bslFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Helper: parse interceptors from .bsl ---
|
# --- Helper: parse interceptors from .bsl ---
|
||||||
function Get-Interceptors {
|
function Get-Interceptors {
|
||||||
param([string]$bslPath)
|
param([string]$bslPath)
|
||||||
|
|
||||||
if (-not (Test-Path $bslPath)) { return @() }
|
if (-not (Test-Path $bslPath)) { return @() }
|
||||||
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
||||||
$interceptors = @()
|
$interceptors = @()
|
||||||
$i = 0
|
$i = 0
|
||||||
while ($i -lt $lines.Count) {
|
while ($i -lt $lines.Count) {
|
||||||
$line = $lines[$i].Trim()
|
$line = $lines[$i].Trim()
|
||||||
if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
|
if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
|
||||||
$type = $Matches[1]
|
$type = $Matches[1]
|
||||||
$method = $Matches[2]
|
$method = $Matches[2]
|
||||||
$interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath }
|
$interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath }
|
||||||
}
|
}
|
||||||
$i++
|
$i++
|
||||||
}
|
}
|
||||||
return $interceptors
|
return $interceptors
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Helper: extract #Вставка blocks from .bsl ---
|
# --- Helper: extract #Вставка blocks from .bsl ---
|
||||||
function Get-InsertionBlocks {
|
function Get-InsertionBlocks {
|
||||||
param([string]$bslPath)
|
param([string]$bslPath)
|
||||||
|
|
||||||
if (-not (Test-Path $bslPath)) { return @() }
|
if (-not (Test-Path $bslPath)) { return @() }
|
||||||
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
||||||
$blocks = @()
|
$blocks = @()
|
||||||
$inBlock = $false
|
$inBlock = $false
|
||||||
$blockLines = @()
|
$blockLines = @()
|
||||||
$startLine = 0
|
$startLine = 0
|
||||||
|
|
||||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||||
$line = $lines[$i].Trim()
|
$line = $lines[$i].Trim()
|
||||||
if ($line -eq "#Вставка") {
|
if ($line -eq "#Вставка") {
|
||||||
$inBlock = $true
|
$inBlock = $true
|
||||||
$blockLines = @()
|
$blockLines = @()
|
||||||
$startLine = $i + 1
|
$startLine = $i + 1
|
||||||
} elseif ($line -eq "#КонецВставки" -and $inBlock) {
|
} elseif ($line -eq "#КонецВставки" -and $inBlock) {
|
||||||
$inBlock = $false
|
$inBlock = $false
|
||||||
$blocks += @{
|
$blocks += @{
|
||||||
StartLine = $startLine
|
StartLine = $startLine
|
||||||
EndLine = $i + 1
|
EndLine = $i + 1
|
||||||
Code = ($blockLines -join "`n").Trim()
|
Code = ($blockLines -join "`n").Trim()
|
||||||
File = $bslPath
|
File = $bslPath
|
||||||
}
|
}
|
||||||
} elseif ($inBlock) {
|
} elseif ($inBlock) {
|
||||||
$blockLines += $lines[$i]
|
$blockLines += $lines[$i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $blocks
|
return $blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Helper: analyze form for callType events and commands ---
|
# --- Helper: analyze form for callType events and commands ---
|
||||||
function Get-FormInterceptors {
|
function Get-FormInterceptors {
|
||||||
param([string]$formXmlPath)
|
param([string]$formXmlPath)
|
||||||
|
|
||||||
if (-not (Test-Path $formXmlPath)) { return $null }
|
if (-not (Test-Path $formXmlPath)) { return $null }
|
||||||
|
|
||||||
$formDoc = New-Object System.Xml.XmlDocument
|
$formDoc = New-Object System.Xml.XmlDocument
|
||||||
$formDoc.PreserveWhitespace = $false
|
$formDoc.PreserveWhitespace = $false
|
||||||
try { $formDoc.Load($formXmlPath) } catch { return $null }
|
try { $formDoc.Load($formXmlPath) } catch { return $null }
|
||||||
|
|
||||||
$fNs = New-Object System.Xml.XmlNamespaceManager($formDoc.NameTable)
|
$fNs = New-Object System.Xml.XmlNamespaceManager($formDoc.NameTable)
|
||||||
$fNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
|
$fNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
|
||||||
|
|
||||||
$fRoot = $formDoc.DocumentElement
|
$fRoot = $formDoc.DocumentElement
|
||||||
$baseForm = $fRoot.SelectSingleNode("f:BaseForm", $fNs)
|
$baseForm = $fRoot.SelectSingleNode("f:BaseForm", $fNs)
|
||||||
$isBorrowed = ($baseForm -ne $null)
|
$isBorrowed = ($baseForm -ne $null)
|
||||||
|
|
||||||
$interceptors = @()
|
$interceptors = @()
|
||||||
|
|
||||||
# Form-level events with callType
|
# Form-level events with callType
|
||||||
$eventsNode = $fRoot.SelectSingleNode("f:Events", $fNs)
|
$eventsNode = $fRoot.SelectSingleNode("f:Events", $fNs)
|
||||||
if ($eventsNode) {
|
if ($eventsNode) {
|
||||||
foreach ($evt in $eventsNode.SelectNodes("f:Event", $fNs)) {
|
foreach ($evt in $eventsNode.SelectNodes("f:Event", $fNs)) {
|
||||||
$ct = $evt.GetAttribute("callType")
|
$ct = $evt.GetAttribute("callType")
|
||||||
if ($ct) {
|
if ($ct) {
|
||||||
$interceptors += "Event:$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
$interceptors += "Event:$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Element-level events with callType (scan all elements recursively)
|
# Element-level events with callType (scan all elements recursively)
|
||||||
$childItems = $fRoot.SelectSingleNode("f:ChildItems", $fNs)
|
$childItems = $fRoot.SelectSingleNode("f:ChildItems", $fNs)
|
||||||
if ($childItems) {
|
if ($childItems) {
|
||||||
foreach ($evtNode in $childItems.SelectNodes(".//*[f:Events/f:Event[@callType]]", $fNs)) {
|
foreach ($evtNode in $childItems.SelectNodes(".//*[f:Events/f:Event[@callType]]", $fNs)) {
|
||||||
$elName = $evtNode.GetAttribute("name")
|
$elName = $evtNode.GetAttribute("name")
|
||||||
foreach ($evt in $evtNode.SelectNodes("f:Events/f:Event[@callType]", $fNs)) {
|
foreach ($evt in $evtNode.SelectNodes("f:Events/f:Event[@callType]", $fNs)) {
|
||||||
$ct = $evt.GetAttribute("callType")
|
$ct = $evt.GetAttribute("callType")
|
||||||
$interceptors += "Element:${elName}.$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
$interceptors += "Element:${elName}.$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Commands with callType on Action
|
# Commands with callType on Action
|
||||||
foreach ($cmd in $fRoot.SelectNodes("f:Commands/f:Command", $fNs)) {
|
foreach ($cmd in $fRoot.SelectNodes("f:Commands/f:Command", $fNs)) {
|
||||||
$cmdName = $cmd.GetAttribute("name")
|
$cmdName = $cmd.GetAttribute("name")
|
||||||
foreach ($action in $cmd.SelectNodes("f:Action[@callType]", $fNs)) {
|
foreach ($action in $cmd.SelectNodes("f:Action[@callType]", $fNs)) {
|
||||||
$ct = $action.GetAttribute("callType")
|
$ct = $action.GetAttribute("callType")
|
||||||
$interceptors += "Command:$cmdName [$ct] -> $($action.InnerText)"
|
$interceptors += "Command:$cmdName [$ct] -> $($action.InnerText)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return @{
|
return @{
|
||||||
IsBorrowed = $isBorrowed
|
IsBorrowed = $isBorrowed
|
||||||
Interceptors = $interceptors
|
Interceptors = $interceptors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# MODE A: Extension overview
|
# MODE A: Extension overview
|
||||||
# ============================================================
|
# ============================================================
|
||||||
if ($Mode -eq "A") {
|
if ($Mode -eq "A") {
|
||||||
$borrowedList = @()
|
$borrowedList = @()
|
||||||
$ownList = @()
|
$ownList = @()
|
||||||
|
|
||||||
foreach ($obj in $objects) {
|
foreach ($obj in $objects) {
|
||||||
$info = Get-ObjectInfo $obj.Type $obj.Name
|
$info = Get-ObjectInfo $obj.Type $obj.Name
|
||||||
if (-not $info) {
|
if (-not $info) {
|
||||||
Write-Host " [?] $($obj.Type).$($obj.Name) — unknown type"
|
Write-Host " [?] $($obj.Type).$($obj.Name) — unknown type"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (-not $info.Exists) {
|
if (-not $info.Exists) {
|
||||||
Write-Host " [?] $($obj.Type).$($obj.Name) — file not found"
|
Write-Host " [?] $($obj.Type).$($obj.Name) — file not found"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($info.Borrowed) {
|
if ($info.Borrowed) {
|
||||||
$borrowedList += $obj
|
$borrowedList += $obj
|
||||||
|
|
||||||
Write-Host " [BORROWED] $($obj.Type).$($obj.Name)"
|
Write-Host " [BORROWED] $($obj.Type).$($obj.Name)"
|
||||||
|
|
||||||
# Find .bsl files and interceptors
|
# Find .bsl files and interceptors
|
||||||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||||||
foreach ($bsl in $bslFiles) {
|
foreach ($bsl in $bslFiles) {
|
||||||
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
||||||
$interceptors = Get-Interceptors $bsl
|
$interceptors = Get-Interceptors $bsl
|
||||||
if ($interceptors.Count -gt 0) {
|
if ($interceptors.Count -gt 0) {
|
||||||
foreach ($ic in $interceptors) {
|
foreach ($ic in $interceptors) {
|
||||||
Write-Host " &$($ic.Type)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
|
Write-Host " &$($ic.Type)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Host " $relPath (no interceptors)"
|
Write-Host " $relPath (no interceptors)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for own attributes/forms in ChildObjects
|
# Check for own attributes/forms in ChildObjects
|
||||||
if ($info.ObjElement) {
|
if ($info.ObjElement) {
|
||||||
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
|
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
|
||||||
if ($childObj) {
|
if ($childObj) {
|
||||||
$ownAttrs = 0
|
$ownAttrs = 0
|
||||||
$ownForms = 0
|
$ownForms = 0
|
||||||
$ownTS = 0
|
$ownTS = 0
|
||||||
$borrowedItems = 0
|
$borrowedItems = 0
|
||||||
$formNames = @()
|
$formNames = @()
|
||||||
foreach ($c in $childObj.ChildNodes) {
|
foreach ($c in $childObj.ChildNodes) {
|
||||||
if ($c.NodeType -ne 'Element') { continue }
|
if ($c.NodeType -ne 'Element') { continue }
|
||||||
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
|
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
|
||||||
if ($cProps) {
|
if ($cProps) {
|
||||||
$cOb = $cProps.SelectSingleNode("md:ObjectBelonging", $info.ObjNs)
|
$cOb = $cProps.SelectSingleNode("md:ObjectBelonging", $info.ObjNs)
|
||||||
if ($cOb -and $cOb.InnerText -eq "Adopted") {
|
if ($cOb -and $cOb.InnerText -eq "Adopted") {
|
||||||
$borrowedItems++
|
$borrowedItems++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch ($c.LocalName) {
|
switch ($c.LocalName) {
|
||||||
"Attribute" { $ownAttrs++ }
|
"Attribute" { $ownAttrs++ }
|
||||||
"TabularSection" { $ownTS++ }
|
"TabularSection" { $ownTS++ }
|
||||||
"Form" { $formNames += $c.InnerText; $ownForms++ }
|
"Form" { $formNames += $c.InnerText; $ownForms++ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$parts = @()
|
$parts = @()
|
||||||
if ($ownAttrs -gt 0) { $parts += "$ownAttrs own attrs" }
|
if ($ownAttrs -gt 0) { $parts += "$ownAttrs own attrs" }
|
||||||
if ($ownTS -gt 0) { $parts += "$ownTS own TS" }
|
if ($ownTS -gt 0) { $parts += "$ownTS own TS" }
|
||||||
if ($ownForms -gt 0) { $parts += "$ownForms own forms" }
|
if ($ownForms -gt 0) { $parts += "$ownForms own forms" }
|
||||||
if ($borrowedItems -gt 0) { $parts += "$borrowedItems borrowed items" }
|
if ($borrowedItems -gt 0) { $parts += "$borrowedItems borrowed items" }
|
||||||
if ($parts.Count -gt 0) {
|
if ($parts.Count -gt 0) {
|
||||||
Write-Host " ChildObjects: $($parts -join ', ')"
|
Write-Host " ChildObjects: $($parts -join ', ')"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Analyze forms
|
# Analyze forms
|
||||||
$borrowedFormCount = 0
|
$borrowedFormCount = 0
|
||||||
$ownFormCount = 0
|
$ownFormCount = 0
|
||||||
foreach ($fn in $formNames) {
|
foreach ($fn in $formNames) {
|
||||||
$formXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $info.DirName) $info.Name) "Forms") $fn) "Ext/Form.xml"
|
$formXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $info.DirName) $info.Name) "Forms") $fn) "Ext/Form.xml"
|
||||||
$fi = Get-FormInterceptors $formXmlPath
|
$fi = Get-FormInterceptors $formXmlPath
|
||||||
if (-not $fi) {
|
if (-not $fi) {
|
||||||
Write-Host " Form.$fn (?)"
|
Write-Host " Form.$fn (?)"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
$formTag = if ($fi.IsBorrowed) { "borrowed"; $borrowedFormCount++ } else { "own"; $ownFormCount++ }
|
$formTag = if ($fi.IsBorrowed) { "borrowed"; $borrowedFormCount++ } else { "own"; $ownFormCount++ }
|
||||||
if ($fi.Interceptors.Count -gt 0) {
|
if ($fi.Interceptors.Count -gt 0) {
|
||||||
Write-Host " Form.$fn ($formTag):"
|
Write-Host " Form.$fn ($formTag):"
|
||||||
foreach ($ic in $fi.Interceptors) {
|
foreach ($ic in $fi.Interceptors) {
|
||||||
Write-Host " $ic"
|
Write-Host " $ic"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Host " Form.$fn ($formTag)"
|
Write-Host " Form.$fn ($formTag)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$ownList += $obj
|
$ownList += $obj
|
||||||
Write-Host " [OWN] $($obj.Type).$($obj.Name)"
|
Write-Host " [OWN] $($obj.Type).$($obj.Name)"
|
||||||
|
|
||||||
# Brief info for own objects
|
# Brief info for own objects
|
||||||
if ($info.ObjElement) {
|
if ($info.ObjElement) {
|
||||||
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
|
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
|
||||||
if ($childObj) {
|
if ($childObj) {
|
||||||
$attrs = 0; $forms = 0; $ts = 0
|
$attrs = 0; $forms = 0; $ts = 0
|
||||||
foreach ($c in $childObj.ChildNodes) {
|
foreach ($c in $childObj.ChildNodes) {
|
||||||
if ($c.NodeType -ne 'Element') { continue }
|
if ($c.NodeType -ne 'Element') { continue }
|
||||||
switch ($c.LocalName) {
|
switch ($c.LocalName) {
|
||||||
"Attribute" { $attrs++ }
|
"Attribute" { $attrs++ }
|
||||||
"TabularSection" { $ts++ }
|
"TabularSection" { $ts++ }
|
||||||
"Form" { $forms++ }
|
"Form" { $forms++ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$parts = @()
|
$parts = @()
|
||||||
if ($attrs -gt 0) { $parts += "$attrs attrs" }
|
if ($attrs -gt 0) { $parts += "$attrs attrs" }
|
||||||
if ($ts -gt 0) { $parts += "$ts TS" }
|
if ($ts -gt 0) { $parts += "$ts TS" }
|
||||||
if ($forms -gt 0) { $parts += "$forms forms" }
|
if ($forms -gt 0) { $parts += "$forms forms" }
|
||||||
if ($parts.Count -gt 0) {
|
if ($parts.Count -gt 0) {
|
||||||
Write-Host " $($parts -join ', ')"
|
Write-Host " $($parts -join ', ')"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "=== Summary: $($borrowedList.Count) borrowed, $($ownList.Count) own objects ==="
|
Write-Host "=== Summary: $($borrowedList.Count) borrowed, $($ownList.Count) own objects ==="
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# MODE B: Transfer check
|
# MODE B: Transfer check
|
||||||
# ============================================================
|
# ============================================================
|
||||||
if ($Mode -eq "B") {
|
if ($Mode -eq "B") {
|
||||||
$transferred = 0
|
$transferred = 0
|
||||||
$notTransferred = 0
|
$notTransferred = 0
|
||||||
$needsReview = 0
|
$needsReview = 0
|
||||||
|
|
||||||
foreach ($obj in $objects) {
|
foreach ($obj in $objects) {
|
||||||
$info = Get-ObjectInfo $obj.Type $obj.Name
|
$info = Get-ObjectInfo $obj.Type $obj.Name
|
||||||
if (-not $info -or -not $info.Exists -or -not $info.Borrowed) { continue }
|
if (-not $info -or -not $info.Exists -or -not $info.Borrowed) { continue }
|
||||||
|
|
||||||
# Find .bsl files with &ИзменениеИКонтроль
|
# Find .bsl files with &ИзменениеИКонтроль
|
||||||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||||||
foreach ($bsl in $bslFiles) {
|
foreach ($bsl in $bslFiles) {
|
||||||
$interceptors = Get-Interceptors $bsl
|
$interceptors = Get-Interceptors $bsl
|
||||||
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
|
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
|
||||||
|
|
||||||
if ($macInterceptors.Count -eq 0) { continue }
|
if ($macInterceptors.Count -eq 0) { continue }
|
||||||
|
|
||||||
foreach ($ic in $macInterceptors) {
|
foreach ($ic in $macInterceptors) {
|
||||||
$methodName = $ic.Method
|
$methodName = $ic.Method
|
||||||
$relBsl = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
$relBsl = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
||||||
|
|
||||||
# Find #Вставка blocks in this file
|
# Find #Вставка blocks in this file
|
||||||
$insertBlocks = Get-InsertionBlocks $bsl
|
$insertBlocks = Get-InsertionBlocks $bsl
|
||||||
|
|
||||||
if ($insertBlocks.Count -eq 0) {
|
if ($insertBlocks.Count -eq 0) {
|
||||||
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — no #Вставка blocks"
|
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — no #Вставка blocks"
|
||||||
$needsReview++
|
$needsReview++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# Find corresponding module in config
|
# Find corresponding module in config
|
||||||
if (-not $childTypeDirMap.ContainsKey($obj.Type)) { continue }
|
if (-not $childTypeDirMap.ContainsKey($obj.Type)) { continue }
|
||||||
$dirName = $childTypeDirMap[$obj.Type]
|
$dirName = $childTypeDirMap[$obj.Type]
|
||||||
$configBsl = $bsl.Replace($ExtensionPath, $ConfigPath)
|
$configBsl = $bsl.Replace($ExtensionPath, $ConfigPath)
|
||||||
|
|
||||||
if (-not (Test-Path $configBsl)) {
|
if (-not (Test-Path $configBsl)) {
|
||||||
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — config module not found"
|
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — config module not found"
|
||||||
$needsReview++
|
$needsReview++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
$configContent = [System.IO.File]::ReadAllText($configBsl, [System.Text.Encoding]::UTF8)
|
$configContent = [System.IO.File]::ReadAllText($configBsl, [System.Text.Encoding]::UTF8)
|
||||||
|
|
||||||
$allTransferred = $true
|
$allTransferred = $true
|
||||||
foreach ($block in $insertBlocks) {
|
foreach ($block in $insertBlocks) {
|
||||||
$code = $block.Code
|
$code = $block.Code
|
||||||
if (-not $code) { continue }
|
if (-not $code) { continue }
|
||||||
|
|
||||||
# Normalize whitespace for comparison
|
# Normalize whitespace for comparison
|
||||||
$codeNorm = $code -replace '\s+', ' '
|
$codeNorm = $code -replace '\s+', ' '
|
||||||
$configNorm = $configContent -replace '\s+', ' '
|
$configNorm = $configContent -replace '\s+', ' '
|
||||||
|
|
||||||
if ($configNorm.Contains($codeNorm)) {
|
if ($configNorm.Contains($codeNorm)) {
|
||||||
# Found in config
|
# Found in config
|
||||||
} else {
|
} else {
|
||||||
$allTransferred = $false
|
$allTransferred = $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($allTransferred) {
|
if ($allTransferred) {
|
||||||
Write-Host " [TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — $($insertBlocks.Count) block(s)"
|
Write-Host " [TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — $($insertBlocks.Count) block(s)"
|
||||||
$transferred++
|
$transferred++
|
||||||
} else {
|
} else {
|
||||||
Write-Host " [NOT_TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — some blocks not found in config"
|
Write-Host " [NOT_TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — some blocks not found in config"
|
||||||
$notTransferred++
|
$notTransferred++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "=== Transfer check: $transferred transferred, $notTransferred not transferred, $needsReview needs review ==="
|
Write-Host "=== Transfer check: $transferred transferred, $notTransferred not transferred, $needsReview needs review ==="
|
||||||
}
|
}
|
||||||
@@ -1,71 +1,71 @@
|
|||||||
---
|
---
|
||||||
name: cfe-init
|
name: cfe-init
|
||||||
description: Создать расширение конфигурации 1С (CFE) — scaffold XML-исходников. Используй когда нужно создать новое расширение для исправления, доработки или дополнения конфигурации
|
description: Создать расширение конфигурации 1С (CFE) — scaffold XML-исходников. Используй когда нужно создать новое расширение для исправления, доработки или дополнения конфигурации
|
||||||
argument-hint: <Name> [-ConfigPath <path>] [-Purpose Patch|Customization|AddOn] [-CompatibilityMode Version8_3_24]
|
argument-hint: <Name> [-ConfigPath <path>] [-Purpose Patch|Customization|AddOn] [-CompatibilityMode Version8_3_24]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cfe-init — Создание расширения конфигурации 1С
|
# /cfe-init — Создание расширения конфигурации 1С
|
||||||
|
|
||||||
Создаёт scaffold расширения: `Configuration.xml`, `Languages/Русский.xml`, опционально `Roles/`.
|
Создаёт scaffold расширения: `Configuration.xml`, `Languages/Русский.xml`, опционально `Roles/`.
|
||||||
|
|
||||||
## Подготовка
|
## Подготовка
|
||||||
|
|
||||||
Если есть выгрузка базовой конфигурации, передай `-ConfigPath` — скрипт автоматически определит `CompatibilityMode` и UUID языка из базовой конфигурации.
|
Если есть выгрузка базовой конфигурации, передай `-ConfigPath` — скрипт автоматически определит `CompatibilityMode` и UUID языка из базовой конфигурации.
|
||||||
|
|
||||||
### Авто-определение ConfigPath
|
### Авто-определение ConfigPath
|
||||||
|
|
||||||
Если пользователь не указал `-ConfigPath` — попробуй определить автоматически:
|
Если пользователь не указал `-ConfigPath` — попробуй определить автоматически:
|
||||||
1. Прочитай `.v8-project.json` из корня проекта
|
1. Прочитай `.v8-project.json` из корня проекта
|
||||||
2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`)
|
2. Разреши целевую базу (по имени, ветке или `default` — алгоритм из `/db-list`)
|
||||||
3. Если у базы есть поле `configSrc` — используй как `-ConfigPath`
|
3. Если у базы есть поле `configSrc` — используй как `-ConfigPath`
|
||||||
4. Если `configSrc` нет — спроси у пользователя
|
4. Если `configSrc` нет — спроси у пользователя
|
||||||
|
|
||||||
Если `.v8-project.json` не найден и `-ConfigPath` не задан — расширение создастся с предупреждением (UUID языка = нули, CompatibilityMode по умолчанию).
|
Если `.v8-project.json` не найден и `-ConfigPath` не задан — расширение создастся с предупреждением (UUID языка = нули, CompatibilityMode по умолчанию).
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
| Параметр | Описание | По умолчанию |
|
| Параметр | Описание | По умолчанию |
|
||||||
|----------|----------|--------------|
|
|----------|----------|--------------|
|
||||||
| `Name` | Имя расширения (обязат.) | — |
|
| `Name` | Имя расширения (обязат.) | — |
|
||||||
| `Synonym` | Синоним | = Name |
|
| `Synonym` | Синоним | = Name |
|
||||||
| `NamePrefix` | Префикс собственных объектов | = Name + "_" |
|
| `NamePrefix` | Префикс собственных объектов | = Name + "_" |
|
||||||
| `OutputDir` | Каталог для создания | `src` |
|
| `OutputDir` | Каталог для создания | `src` |
|
||||||
| `Purpose` | `Patch` (исправление) / `Customization` (доработка) / `AddOn` (дополнение) | `Customization` |
|
| `Purpose` | `Patch` (исправление) / `Customization` (доработка) / `AddOn` (дополнение) | `Customization` |
|
||||||
| `Version` | Версия расширения | — |
|
| `Version` | Версия расширения | — |
|
||||||
| `Vendor` | Поставщик | — |
|
| `Vendor` | Поставщик | — |
|
||||||
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
|
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
|
||||||
| `ConfigPath` | Путь к выгрузке базовой конфигурации (авто-определяет CompatibilityMode и Language UUID) | — |
|
| `ConfigPath` | Путь к выгрузке базовой конфигурации (авто-определяет CompatibilityMode и Language UUID) | — |
|
||||||
| `NoRole` | Без основной роли | false |
|
| `NoRole` | Без основной роли | false |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-init.ps1" -Name "МоёРасширение"
|
powershell.exe -NoProfile -File ".opencode/skills/cfe-init/scripts/cfe-init.ps1" -Name "МоёРасширение"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Расширение для ERP с авто-определением совместимости из базовой конфигурации
|
# Расширение для ERP с авто-определением совместимости из базовой конфигурации
|
||||||
... -Name Расш1 -ConfigPath C:\WS\tasks\cfsrc\erp_8.3.24 -OutputDir src
|
... -Name Расш1 -ConfigPath C:\WS\tasks\cfsrc\erp_8.3.24 -OutputDir src
|
||||||
|
|
||||||
# Расширение-исправление с явным режимом совместимости
|
# Расширение-исправление с явным режимом совместимости
|
||||||
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src
|
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src
|
||||||
|
|
||||||
# Расширение-доработка с версией
|
# Расширение-доработка с версией
|
||||||
... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src
|
... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src
|
||||||
|
|
||||||
# Без роли, с явным префиксом
|
# Без роли, с явным префиксом
|
||||||
... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src
|
... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src
|
||||||
```
|
```
|
||||||
|
|
||||||
## Верификация
|
## Верификация
|
||||||
|
|
||||||
```
|
```
|
||||||
/cfe-validate <OutputDir>
|
/cfe-validate <OutputDir>
|
||||||
```
|
```
|
||||||
|
|
||||||
+279
-270
@@ -1,270 +1,279 @@
|
|||||||
# cfe-init v1.1 — Create 1C configuration extension scaffold (CFE)
|
# cfe-init v1.2 — Create 1C configuration extension scaffold (CFE)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string]$Name,
|
[string]$Name,
|
||||||
[string]$Synonym = $Name,
|
[string]$Synonym = $Name,
|
||||||
[string]$NamePrefix,
|
[string]$NamePrefix,
|
||||||
[string]$OutputDir = "src",
|
[string]$OutputDir = "src",
|
||||||
[ValidateSet("Patch","Customization","AddOn")]
|
[ValidateSet("Patch","Customization","AddOn")]
|
||||||
[string]$Purpose = "Customization",
|
[string]$Purpose = "Customization",
|
||||||
[string]$Version,
|
[string]$Version,
|
||||||
[string]$Vendor,
|
[string]$Vendor,
|
||||||
[string]$CompatibilityMode = "Version8_3_24",
|
[string]$CompatibilityMode = "Version8_3_24",
|
||||||
[string]$ConfigPath,
|
[string]$ConfigPath,
|
||||||
[switch]$NoRole
|
[switch]$NoRole
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Default NamePrefix ---
|
# --- Default NamePrefix ---
|
||||||
if (-not $NamePrefix) {
|
if (-not $NamePrefix) {
|
||||||
$NamePrefix = "${Name}_"
|
$NamePrefix = "${Name}_"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Resolve output dir ---
|
# --- Resolve output dir ---
|
||||||
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
|
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
|
||||||
$OutputDir = Join-Path (Get-Location).Path $OutputDir
|
$OutputDir = Join-Path (Get-Location).Path $OutputDir
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Check existing ---
|
# --- Check existing ---
|
||||||
$cfgFile = Join-Path $OutputDir "Configuration.xml"
|
$cfgFile = Join-Path $OutputDir "Configuration.xml"
|
||||||
if (Test-Path $cfgFile) {
|
if (Test-Path $cfgFile) {
|
||||||
Write-Error "Configuration.xml already exists: $cfgFile"
|
Write-Error "Configuration.xml already exists: $cfgFile"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Resolve ConfigPath ---
|
# MDClasses format version — inherited from the base config so the extension stays uniform
|
||||||
$baseLangUuid = "00000000-0000-0000-0000-000000000000"
|
# with it (a 2.13 base must yield a 2.13 extension, else platform import rejects the mismatch).
|
||||||
if ($ConfigPath) {
|
$formatVersion = "2.17"
|
||||||
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
|
|
||||||
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
|
# --- Resolve ConfigPath ---
|
||||||
}
|
$baseLangUuid = "00000000-0000-0000-0000-000000000000"
|
||||||
if (Test-Path $ConfigPath -PathType Container) {
|
if ($ConfigPath) {
|
||||||
$candidate = Join-Path $ConfigPath "Configuration.xml"
|
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
|
||||||
if (Test-Path $candidate) { $ConfigPath = $candidate }
|
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
|
||||||
else { Write-Error "No Configuration.xml in config directory: $ConfigPath"; exit 1 }
|
}
|
||||||
}
|
if (Test-Path $ConfigPath -PathType Container) {
|
||||||
if (-not (Test-Path $ConfigPath)) { Write-Error "Config file not found: $ConfigPath"; exit 1 }
|
$candidate = Join-Path $ConfigPath "Configuration.xml"
|
||||||
$cfgDir = Split-Path (Resolve-Path $ConfigPath).Path -Parent
|
if (Test-Path $candidate) { $ConfigPath = $candidate }
|
||||||
|
else { Write-Error "No Configuration.xml in config directory: $ConfigPath"; exit 1 }
|
||||||
# 3a. Read Language UUID from base config
|
}
|
||||||
$baseLangFile = Join-Path (Join-Path $cfgDir "Languages") "Русский.xml"
|
if (-not (Test-Path $ConfigPath)) { Write-Error "Config file not found: $ConfigPath"; exit 1 }
|
||||||
if (Test-Path $baseLangFile) {
|
$cfgDir = Split-Path (Resolve-Path $ConfigPath).Path -Parent
|
||||||
$baseLangDoc = New-Object System.Xml.XmlDocument
|
|
||||||
$baseLangDoc.PreserveWhitespace = $false
|
# 3a. Read Language UUID from base config
|
||||||
$baseLangDoc.Load($baseLangFile)
|
$baseLangFile = Join-Path (Join-Path $cfgDir "Languages") "Русский.xml"
|
||||||
$langEl = $null
|
if (Test-Path $baseLangFile) {
|
||||||
foreach ($c in $baseLangDoc.DocumentElement.ChildNodes) {
|
$baseLangDoc = New-Object System.Xml.XmlDocument
|
||||||
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq 'Language') { $langEl = $c; break }
|
$baseLangDoc.PreserveWhitespace = $false
|
||||||
}
|
$baseLangDoc.Load($baseLangFile)
|
||||||
if ($langEl) {
|
$langEl = $null
|
||||||
$baseLangUuid = $langEl.GetAttribute("uuid")
|
foreach ($c in $baseLangDoc.DocumentElement.ChildNodes) {
|
||||||
Write-Host "[INFO] Base config Language UUID: $baseLangUuid"
|
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq 'Language') { $langEl = $c; break }
|
||||||
} else {
|
}
|
||||||
Write-Host "[WARN] No <Language> element in $baseLangFile"
|
if ($langEl) {
|
||||||
}
|
$baseLangUuid = $langEl.GetAttribute("uuid")
|
||||||
} else {
|
Write-Host "[INFO] Base config Language UUID: $baseLangUuid"
|
||||||
Write-Host "[WARN] Base config language not found: $baseLangFile"
|
} else {
|
||||||
}
|
Write-Host "[WARN] No <Language> element in $baseLangFile"
|
||||||
|
}
|
||||||
# 3b. Read CompatibilityMode and InterfaceCompatibilityMode from base config
|
} else {
|
||||||
$baseCfgDoc = New-Object System.Xml.XmlDocument
|
Write-Host "[WARN] Base config language not found: $baseLangFile"
|
||||||
$baseCfgDoc.PreserveWhitespace = $false
|
}
|
||||||
$baseCfgDoc.Load((Resolve-Path $ConfigPath).Path)
|
|
||||||
$baseCfgNs = New-Object System.Xml.XmlNamespaceManager($baseCfgDoc.NameTable)
|
# 3b. Read CompatibilityMode and InterfaceCompatibilityMode from base config
|
||||||
$baseCfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
$baseCfgDoc = New-Object System.Xml.XmlDocument
|
||||||
$compatNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:CompatibilityMode", $baseCfgNs)
|
$baseCfgDoc.PreserveWhitespace = $false
|
||||||
if ($compatNode -and $compatNode.InnerText) {
|
$baseCfgDoc.Load((Resolve-Path $ConfigPath).Path)
|
||||||
$CompatibilityMode = $compatNode.InnerText.Trim()
|
$baseCfgNs = New-Object System.Xml.XmlNamespaceManager($baseCfgDoc.NameTable)
|
||||||
Write-Host "[INFO] Base config CompatibilityMode: $CompatibilityMode"
|
$baseCfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
} else {
|
$fmtVer = $baseCfgDoc.DocumentElement.GetAttribute("version")
|
||||||
Write-Host "[WARN] CompatibilityMode not found in base config, using default: $CompatibilityMode"
|
if ($fmtVer) {
|
||||||
}
|
$formatVersion = $fmtVer
|
||||||
$ifcNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:InterfaceCompatibilityMode", $baseCfgNs)
|
Write-Host "[INFO] Base config format version: $formatVersion"
|
||||||
if ($ifcNode -and $ifcNode.InnerText) {
|
}
|
||||||
$InterfaceCompatibilityMode = $ifcNode.InnerText.Trim()
|
$compatNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:CompatibilityMode", $baseCfgNs)
|
||||||
Write-Host "[INFO] Base config InterfaceCompatibilityMode: $InterfaceCompatibilityMode"
|
if ($compatNode -and $compatNode.InnerText) {
|
||||||
} else {
|
$CompatibilityMode = $compatNode.InnerText.Trim()
|
||||||
$InterfaceCompatibilityMode = "TaxiEnableVersion8_2"
|
Write-Host "[INFO] Base config CompatibilityMode: $CompatibilityMode"
|
||||||
Write-Host "[WARN] InterfaceCompatibilityMode not found in base config, using default: $InterfaceCompatibilityMode"
|
} else {
|
||||||
}
|
Write-Host "[WARN] CompatibilityMode not found in base config, using default: $CompatibilityMode"
|
||||||
} else {
|
}
|
||||||
$InterfaceCompatibilityMode = "TaxiEnableVersion8_2"
|
$ifcNode = $baseCfgDoc.SelectSingleNode("//md:Configuration/md:Properties/md:InterfaceCompatibilityMode", $baseCfgNs)
|
||||||
Write-Host "[WARN] Language ExtendedConfigurationObject set to zeros. Use -ConfigPath to auto-resolve from base config, or fix manually before loading."
|
if ($ifcNode -and $ifcNode.InnerText) {
|
||||||
}
|
$InterfaceCompatibilityMode = $ifcNode.InnerText.Trim()
|
||||||
|
Write-Host "[INFO] Base config InterfaceCompatibilityMode: $InterfaceCompatibilityMode"
|
||||||
# --- Generate UUIDs ---
|
} else {
|
||||||
$uuidCfg = [guid]::NewGuid().ToString()
|
$InterfaceCompatibilityMode = "TaxiEnableVersion8_2"
|
||||||
$uuidLang = [guid]::NewGuid().ToString()
|
Write-Host "[WARN] InterfaceCompatibilityMode not found in base config, using default: $InterfaceCompatibilityMode"
|
||||||
$uuidRole = [guid]::NewGuid().ToString()
|
}
|
||||||
|
} else {
|
||||||
# 7 ContainedObject ObjectIds
|
$InterfaceCompatibilityMode = "TaxiEnableVersion8_2"
|
||||||
$co1 = [guid]::NewGuid().ToString()
|
Write-Host "[WARN] Language ExtendedConfigurationObject set to zeros. Use -ConfigPath to auto-resolve from base config, or fix manually before loading."
|
||||||
$co2 = [guid]::NewGuid().ToString()
|
}
|
||||||
$co3 = [guid]::NewGuid().ToString()
|
|
||||||
$co4 = [guid]::NewGuid().ToString()
|
# --- Generate UUIDs ---
|
||||||
$co5 = [guid]::NewGuid().ToString()
|
$uuidCfg = [guid]::NewGuid().ToString()
|
||||||
$co6 = [guid]::NewGuid().ToString()
|
$uuidLang = [guid]::NewGuid().ToString()
|
||||||
$co7 = [guid]::NewGuid().ToString()
|
$uuidRole = [guid]::NewGuid().ToString()
|
||||||
|
|
||||||
# --- Synonym XML ---
|
# 7 ContainedObject ObjectIds
|
||||||
$synonymXml = ""
|
$co1 = [guid]::NewGuid().ToString()
|
||||||
if ($Synonym) {
|
$co2 = [guid]::NewGuid().ToString()
|
||||||
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
|
$co3 = [guid]::NewGuid().ToString()
|
||||||
}
|
$co4 = [guid]::NewGuid().ToString()
|
||||||
|
$co5 = [guid]::NewGuid().ToString()
|
||||||
# --- Optional properties ---
|
$co6 = [guid]::NewGuid().ToString()
|
||||||
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
|
$co7 = [guid]::NewGuid().ToString()
|
||||||
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
|
|
||||||
|
# --- Synonym XML ---
|
||||||
# --- Role name ---
|
$synonymXml = ""
|
||||||
$roleName = "${NamePrefix}ОсновнаяРоль"
|
if ($Synonym) {
|
||||||
|
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
|
||||||
# --- DefaultRoles XML ---
|
}
|
||||||
$defaultRolesXml = ""
|
|
||||||
if (-not $NoRole) {
|
# --- Optional properties ---
|
||||||
$defaultRolesXml = "`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t"
|
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
|
||||||
}
|
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
|
||||||
|
|
||||||
# --- ChildObjects ---
|
# --- Role name ---
|
||||||
$childObjectsXml = "`r`n`t`t`t<Language>Русский</Language>"
|
$roleName = "${NamePrefix}ОсновнаяРоль"
|
||||||
if (-not $NoRole) {
|
|
||||||
$childObjectsXml += "`r`n`t`t`t<Role>$roleName</Role>"
|
# --- DefaultRoles XML ---
|
||||||
}
|
$defaultRolesXml = ""
|
||||||
$childObjectsXml += "`r`n`t`t"
|
if (-not $NoRole) {
|
||||||
|
$defaultRolesXml = "`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t"
|
||||||
# --- Configuration.xml ---
|
}
|
||||||
$cfgXml = @"
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
# --- ChildObjects ---
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
$childObjectsXml = "`r`n`t`t`t<Language>Русский</Language>"
|
||||||
<Configuration uuid="$uuidCfg">
|
if (-not $NoRole) {
|
||||||
<InternalInfo>
|
$childObjectsXml += "`r`n`t`t`t<Role>$roleName</Role>"
|
||||||
<xr:ContainedObject>
|
}
|
||||||
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
|
$childObjectsXml += "`r`n`t`t"
|
||||||
<xr:ObjectId>$co1</xr:ObjectId>
|
|
||||||
</xr:ContainedObject>
|
# --- Configuration.xml ---
|
||||||
<xr:ContainedObject>
|
$cfgXml = @"
|
||||||
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xr:ObjectId>$co2</xr:ObjectId>
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
||||||
</xr:ContainedObject>
|
<Configuration uuid="$uuidCfg">
|
||||||
<xr:ContainedObject>
|
<InternalInfo>
|
||||||
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
|
<xr:ContainedObject>
|
||||||
<xr:ObjectId>$co3</xr:ObjectId>
|
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
|
||||||
</xr:ContainedObject>
|
<xr:ObjectId>$co1</xr:ObjectId>
|
||||||
<xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
|
<xr:ContainedObject>
|
||||||
<xr:ObjectId>$co4</xr:ObjectId>
|
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
|
||||||
</xr:ContainedObject>
|
<xr:ObjectId>$co2</xr:ObjectId>
|
||||||
<xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
|
<xr:ContainedObject>
|
||||||
<xr:ObjectId>$co5</xr:ObjectId>
|
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
|
||||||
</xr:ContainedObject>
|
<xr:ObjectId>$co3</xr:ObjectId>
|
||||||
<xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
|
<xr:ContainedObject>
|
||||||
<xr:ObjectId>$co6</xr:ObjectId>
|
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
|
||||||
</xr:ContainedObject>
|
<xr:ObjectId>$co4</xr:ObjectId>
|
||||||
<xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
|
<xr:ContainedObject>
|
||||||
<xr:ObjectId>$co7</xr:ObjectId>
|
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
|
||||||
</xr:ContainedObject>
|
<xr:ObjectId>$co5</xr:ObjectId>
|
||||||
</InternalInfo>
|
</xr:ContainedObject>
|
||||||
<Properties>
|
<xr:ContainedObject>
|
||||||
<ObjectBelonging>Adopted</ObjectBelonging>
|
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
|
||||||
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
|
<xr:ObjectId>$co6</xr:ObjectId>
|
||||||
<Synonym>$synonymXml</Synonym>
|
</xr:ContainedObject>
|
||||||
<Comment/>
|
<xr:ContainedObject>
|
||||||
<ConfigurationExtensionPurpose>$Purpose</ConfigurationExtensionPurpose>
|
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
|
||||||
<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
|
<xr:ObjectId>$co7</xr:ObjectId>
|
||||||
<NamePrefix>$([System.Security.SecurityElement]::Escape($NamePrefix))</NamePrefix>
|
</xr:ContainedObject>
|
||||||
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
|
</InternalInfo>
|
||||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
<Properties>
|
||||||
<UsePurposes>
|
<ObjectBelonging>Adopted</ObjectBelonging>
|
||||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
|
||||||
</UsePurposes>
|
<Synonym>$synonymXml</Synonym>
|
||||||
<ScriptVariant>Russian</ScriptVariant>
|
<Comment/>
|
||||||
<DefaultRoles>$defaultRolesXml</DefaultRoles>
|
<ConfigurationExtensionPurpose>$Purpose</ConfigurationExtensionPurpose>
|
||||||
<Vendor>$vendorXml</Vendor>
|
<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
|
||||||
<Version>$versionXml</Version>
|
<NamePrefix>$([System.Security.SecurityElement]::Escape($NamePrefix))</NamePrefix>
|
||||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
|
||||||
<BriefInformation/>
|
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||||
<DetailedInformation/>
|
<UsePurposes>
|
||||||
<Copyright/>
|
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||||
<VendorInformationAddress/>
|
</UsePurposes>
|
||||||
<ConfigurationInformationAddress/>
|
<ScriptVariant>Russian</ScriptVariant>
|
||||||
<InterfaceCompatibilityMode>$InterfaceCompatibilityMode</InterfaceCompatibilityMode>
|
<DefaultRoles>$defaultRolesXml</DefaultRoles>
|
||||||
</Properties>
|
<Vendor>$vendorXml</Vendor>
|
||||||
<ChildObjects>$childObjectsXml</ChildObjects>
|
<Version>$versionXml</Version>
|
||||||
</Configuration>
|
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||||
</MetaDataObject>
|
<BriefInformation/>
|
||||||
"@
|
<DetailedInformation/>
|
||||||
|
<Copyright/>
|
||||||
# --- Languages/Русский.xml (adopted format) ---
|
<VendorInformationAddress/>
|
||||||
$langXml = @"
|
<ConfigurationInformationAddress/>
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<InterfaceCompatibilityMode>$InterfaceCompatibilityMode</InterfaceCompatibilityMode>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
</Properties>
|
||||||
<Language uuid="$uuidLang">
|
<ChildObjects>$childObjectsXml</ChildObjects>
|
||||||
<InternalInfo/>
|
</Configuration>
|
||||||
<Properties>
|
</MetaDataObject>
|
||||||
<ObjectBelonging>Adopted</ObjectBelonging>
|
"@
|
||||||
<Name>Русский</Name>
|
|
||||||
<Comment/>
|
# --- Languages/Русский.xml (adopted format) ---
|
||||||
<ExtendedConfigurationObject>$baseLangUuid</ExtendedConfigurationObject>
|
$langXml = @"
|
||||||
<LanguageCode>ru</LanguageCode>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
</Properties>
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
||||||
</Language>
|
<Language uuid="$uuidLang">
|
||||||
</MetaDataObject>
|
<InternalInfo/>
|
||||||
"@
|
<Properties>
|
||||||
|
<ObjectBelonging>Adopted</ObjectBelonging>
|
||||||
# --- Role XML ---
|
<Name>Русский</Name>
|
||||||
$roleXml = @"
|
<Comment/>
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<ExtendedConfigurationObject>$baseLangUuid</ExtendedConfigurationObject>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<LanguageCode>ru</LanguageCode>
|
||||||
<Role uuid="$uuidRole">
|
</Properties>
|
||||||
<Properties>
|
</Language>
|
||||||
<Name>$([System.Security.SecurityElement]::Escape($roleName))</Name>
|
</MetaDataObject>
|
||||||
<Synonym/>
|
"@
|
||||||
<Comment/>
|
|
||||||
</Properties>
|
# --- Role XML ---
|
||||||
</Role>
|
$roleXml = @"
|
||||||
</MetaDataObject>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
"@
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
||||||
|
<Role uuid="$uuidRole">
|
||||||
# --- Create directories ---
|
<Properties>
|
||||||
if (-not (Test-Path $OutputDir)) {
|
<Name>$([System.Security.SecurityElement]::Escape($roleName))</Name>
|
||||||
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
<Synonym/>
|
||||||
}
|
<Comment/>
|
||||||
$langDir = Join-Path $OutputDir "Languages"
|
</Properties>
|
||||||
if (-not (Test-Path $langDir)) {
|
</Role>
|
||||||
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
|
</MetaDataObject>
|
||||||
}
|
"@
|
||||||
|
|
||||||
# --- Write files with UTF-8 BOM ---
|
# --- Create directories ---
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
if (-not (Test-Path $OutputDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||||
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
|
}
|
||||||
$langFile = Join-Path $langDir "Русский.xml"
|
$langDir = Join-Path $OutputDir "Languages"
|
||||||
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
|
if (-not (Test-Path $langDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
|
||||||
# --- Role ---
|
}
|
||||||
if (-not $NoRole) {
|
|
||||||
$roleDir = Join-Path $OutputDir "Roles"
|
# --- Write files with UTF-8 BOM ---
|
||||||
if (-not (Test-Path $roleDir)) {
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
|
|
||||||
}
|
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
|
||||||
$roleFile = Join-Path $roleDir "$roleName.xml"
|
$langFile = Join-Path $langDir "Русский.xml"
|
||||||
[System.IO.File]::WriteAllText($roleFile, $roleXml, $enc)
|
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
|
||||||
}
|
|
||||||
|
# --- Role ---
|
||||||
# --- Output ---
|
if (-not $NoRole) {
|
||||||
Write-Host "[OK] Создано расширение: $Name"
|
$roleDir = Join-Path $OutputDir "Roles"
|
||||||
Write-Host " Каталог: $OutputDir"
|
if (-not (Test-Path $roleDir)) {
|
||||||
Write-Host " Назначение: $Purpose"
|
New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
|
||||||
Write-Host " Префикс: $NamePrefix"
|
}
|
||||||
Write-Host " Совместимость: $CompatibilityMode"
|
$roleFile = Join-Path $roleDir "$roleName.xml"
|
||||||
Write-Host " Configuration.xml: $cfgFile"
|
[System.IO.File]::WriteAllText($roleFile, $roleXml, $enc)
|
||||||
Write-Host " Languages: $langFile"
|
}
|
||||||
if (-not $NoRole) {
|
|
||||||
Write-Host " Role: $roleFile"
|
# --- Output ---
|
||||||
}
|
Write-Host "[OK] Создано расширение: $Name"
|
||||||
|
Write-Host " Каталог: $OutputDir"
|
||||||
|
Write-Host " Назначение: $Purpose"
|
||||||
|
Write-Host " Префикс: $NamePrefix"
|
||||||
|
Write-Host " Совместимость: $CompatibilityMode"
|
||||||
|
Write-Host " Configuration.xml: $cfgFile"
|
||||||
|
Write-Host " Languages: $langFile"
|
||||||
|
if (-not $NoRole) {
|
||||||
|
Write-Host " Role: $roleFile"
|
||||||
|
}
|
||||||
+12
-4
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-init v1.1 — Create 1C configuration extension scaffold (CFE)
|
# cfe-init v1.2 — Create 1C configuration extension scaffold (CFE)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Generates minimal XML source files for a 1C configuration extension."""
|
"""Generates minimal XML source files for a 1C configuration extension."""
|
||||||
import sys, os, argparse, uuid
|
import sys, os, argparse, uuid
|
||||||
@@ -50,6 +50,10 @@ def main():
|
|||||||
print(f"Configuration.xml already exists: {cfg_file}", file=sys.stderr)
|
print(f"Configuration.xml already exists: {cfg_file}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# MDClasses format version — inherited from the base config so the extension stays uniform
|
||||||
|
# with it (a 2.13 base must yield a 2.13 extension, else platform import rejects the mismatch).
|
||||||
|
format_version = "2.17"
|
||||||
|
|
||||||
# --- Resolve ConfigPath ---
|
# --- Resolve ConfigPath ---
|
||||||
base_lang_uuid = "00000000-0000-0000-0000-000000000000"
|
base_lang_uuid = "00000000-0000-0000-0000-000000000000"
|
||||||
if args.ConfigPath:
|
if args.ConfigPath:
|
||||||
@@ -88,6 +92,10 @@ def main():
|
|||||||
try:
|
try:
|
||||||
base_cfg_tree = ET.parse(os.path.abspath(config_path))
|
base_cfg_tree = ET.parse(os.path.abspath(config_path))
|
||||||
base_cfg_root = base_cfg_tree.getroot()
|
base_cfg_root = base_cfg_tree.getroot()
|
||||||
|
fmt_ver = base_cfg_root.get("version")
|
||||||
|
if fmt_ver:
|
||||||
|
format_version = fmt_ver
|
||||||
|
print(f"[INFO] Base config format version: {format_version}")
|
||||||
ns = {'md': 'http://v8.1c.ru/8.3/MDClasses'}
|
ns = {'md': 'http://v8.1c.ru/8.3/MDClasses'}
|
||||||
compat_node = base_cfg_root.find('.//md:Configuration/md:Properties/md:CompatibilityMode', ns)
|
compat_node = base_cfg_root.find('.//md:Configuration/md:Properties/md:CompatibilityMode', ns)
|
||||||
if compat_node is not None and compat_node.text:
|
if compat_node is not None and compat_node.text:
|
||||||
@@ -155,7 +163,7 @@ def main():
|
|||||||
\t\t\t</xr:ContainedObject>\n"""
|
\t\t\t</xr:ContainedObject>\n"""
|
||||||
|
|
||||||
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
|
||||||
\t<Configuration uuid="{uuid_cfg}">
|
\t<Configuration uuid="{uuid_cfg}">
|
||||||
\t\t<InternalInfo>
|
\t\t<InternalInfo>
|
||||||
{contained_objects}\t\t</InternalInfo>
|
{contained_objects}\t\t</InternalInfo>
|
||||||
@@ -190,7 +198,7 @@ def main():
|
|||||||
|
|
||||||
# --- Languages/Русский.xml (adopted format) ---
|
# --- Languages/Русский.xml (adopted format) ---
|
||||||
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
|
||||||
\t<Language uuid="{uuid_lang}">
|
\t<Language uuid="{uuid_lang}">
|
||||||
\t\t<InternalInfo/>
|
\t\t<InternalInfo/>
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
@@ -205,7 +213,7 @@ def main():
|
|||||||
|
|
||||||
# --- Role XML ---
|
# --- Role XML ---
|
||||||
role_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
role_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
|
||||||
\t<Role uuid="{uuid_role}">
|
\t<Role uuid="{uuid_role}">
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
\t\t\t<Name>{esc_xml(role_name)}</Name>
|
\t\t\t<Name>{esc_xml(role_name)}</Name>
|
||||||
+78
-78
@@ -1,78 +1,78 @@
|
|||||||
---
|
---
|
||||||
name: cfe-patch-method
|
name: cfe-patch-method
|
||||||
description: Генерация перехватчика метода в расширении 1С (CFE). Используй когда нужно перехватить метод заимствованного объекта — вставить код до, после или вместо оригинального
|
description: Генерация перехватчика метода в расширении 1С (CFE). Используй когда нужно перехватить метод заимствованного объекта — вставить код до, после или вместо оригинального
|
||||||
argument-hint: -ExtensionPath <path> -ModulePath "Catalog.X.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
argument-hint: -ExtensionPath <path> -ModulePath "Catalog.X.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cfe-patch-method — Генерация перехватчика метода
|
# /cfe-patch-method — Генерация перехватчика метода
|
||||||
|
|
||||||
Генерирует `.bsl` файл с декоратором перехвата для заимствованного объекта расширения. Создаёт файл или дописывает в существующий.
|
Генерирует `.bsl` файл с декоратором перехвата для заимствованного объекта расширения. Создаёт файл или дописывает в существующий.
|
||||||
|
|
||||||
## Предусловие
|
## Предусловие
|
||||||
|
|
||||||
Объект должен быть заимствован в расширение (`/cfe-borrow`). Скрипт читает `NamePrefix` из `Configuration.xml` расширения для формирования имени процедуры.
|
Объект должен быть заимствован в расширение (`/cfe-borrow`). Скрипт читает `NamePrefix` из `Configuration.xml` расширения для формирования имени процедуры.
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
| Параметр | Описание | По умолчанию |
|
| Параметр | Описание | По умолчанию |
|
||||||
|----------|----------|--------------|
|
|----------|----------|--------------|
|
||||||
| `ExtensionPath` | Путь к расширению (обязат.) | — |
|
| `ExtensionPath` | Путь к расширению (обязат.) | — |
|
||||||
| `ModulePath` | Путь к модулю (обязат.) | — |
|
| `ModulePath` | Путь к модулю (обязат.) | — |
|
||||||
| `MethodName` | Имя перехватываемого метода (обязат.) | — |
|
| `MethodName` | Имя перехватываемого метода (обязат.) | — |
|
||||||
| `InterceptorType` | `Before` / `After` / `ModificationAndControl` (обязат.) | — |
|
| `InterceptorType` | `Before` / `After` / `ModificationAndControl` (обязат.) | — |
|
||||||
| `Context` | Директива контекста | `НаСервере` |
|
| `Context` | Директива контекста | `НаСервере` |
|
||||||
| `IsFunction` | Метод — функция (добавит `Возврат`) | false |
|
| `IsFunction` | Метод — функция (добавит `Возврат`) | false |
|
||||||
|
|
||||||
## Формат ModulePath
|
## Формат ModulePath
|
||||||
|
|
||||||
| ModulePath | Файл |
|
| ModulePath | Файл |
|
||||||
|------------|------|
|
|------------|------|
|
||||||
| `Catalog.X.ObjectModule` | `Catalogs/X/Ext/ObjectModule.bsl` |
|
| `Catalog.X.ObjectModule` | `Catalogs/X/Ext/ObjectModule.bsl` |
|
||||||
| `Catalog.X.ManagerModule` | `Catalogs/X/Ext/ManagerModule.bsl` |
|
| `Catalog.X.ManagerModule` | `Catalogs/X/Ext/ManagerModule.bsl` |
|
||||||
| `Catalog.X.Form.Y` | `Catalogs/X/Forms/Y/Ext/Form/Module.bsl` |
|
| `Catalog.X.Form.Y` | `Catalogs/X/Forms/Y/Ext/Form/Module.bsl` |
|
||||||
| `CommonModule.X` | `CommonModules/X/Ext/Module.bsl` |
|
| `CommonModule.X` | `CommonModules/X/Ext/Module.bsl` |
|
||||||
| `Document.X.ObjectModule` | `Documents/X/Ext/ObjectModule.bsl` |
|
| `Document.X.ObjectModule` | `Documents/X/Ext/ObjectModule.bsl` |
|
||||||
| `Document.X.Form.Y` | `Documents/X/Forms/Y/Ext/Form/Module.bsl` |
|
| `Document.X.Form.Y` | `Documents/X/Forms/Y/Ext/Form/Module.bsl` |
|
||||||
|
|
||||||
Аналогично для Report, DataProcessor, InformationRegister и других типов.
|
Аналогично для Report, DataProcessor, InformationRegister и других типов.
|
||||||
|
|
||||||
## Типы перехвата
|
## Типы перехвата
|
||||||
|
|
||||||
| InterceptorType | Декоратор | Назначение |
|
| InterceptorType | Декоратор | Назначение |
|
||||||
|-----------------|-----------|------------|
|
|-----------------|-----------|------------|
|
||||||
| `Before` | `&Перед` | Код до вызова оригинального метода |
|
| `Before` | `&Перед` | Код до вызова оригинального метода |
|
||||||
| `After` | `&После` | Код после вызова оригинального метода |
|
| `After` | `&После` | Код после вызова оригинального метода |
|
||||||
| `ModificationAndControl` | `&ИзменениеИКонтроль` | Копия тела метода с маркерами `#Вставка`/`#Удаление` |
|
| `ModificationAndControl` | `&ИзменениеИКонтроль` | Копия тела метода с маркерами `#Вставка`/`#Удаление` |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.ps1" -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
powershell.exe -NoProfile -File ".opencode/skills/cfe-patch-method/scripts/cfe-patch-method.ps1" -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||||
```
|
```
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Перехват &Перед на сервере
|
# Перехват &Перед на сервере
|
||||||
... -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
... -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||||
|
|
||||||
# Перехват &После на клиенте
|
# Перехват &После на клиенте
|
||||||
... -ExtensionPath src -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After -Context "НаКлиенте"
|
... -ExtensionPath src -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After -Context "НаКлиенте"
|
||||||
|
|
||||||
# ИзменениеИКонтроль для функции
|
# ИзменениеИКонтроль для функции
|
||||||
... -ExtensionPath src -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType ModificationAndControl -IsFunction
|
... -ExtensionPath src -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType ModificationAndControl -IsFunction
|
||||||
```
|
```
|
||||||
|
|
||||||
## Генерируемый код (Before)
|
## Генерируемый код (Before)
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
&НаСервере
|
&НаСервере
|
||||||
&Перед("ПриЗаписи")
|
&Перед("ПриЗаписи")
|
||||||
Процедура Расш1_ПриЗаписи()
|
Процедура Расш1_ПриЗаписи()
|
||||||
// TODO: код перед вызовом оригинального метода
|
// TODO: код перед вызовом оригинального метода
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
@@ -1,29 +1,29 @@
|
|||||||
---
|
---
|
||||||
name: cfe-validate
|
name: cfe-validate
|
||||||
description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности
|
description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности
|
||||||
argument-hint: <ExtensionPath> [-Detailed] [-MaxErrors 30]
|
argument-hint: <ExtensionPath> [-Detailed] [-MaxErrors 30]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /cfe-validate — валидация расширения конфигурации (CFE)
|
# /cfe-validate — валидация расширения конфигурации (CFE)
|
||||||
|
|
||||||
Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты. Аналог `/cf-validate`, но для расширений.
|
Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты. Аналог `/cf-validate`, но для расширений.
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
| Параметр | Обяз. | Умолч. | Описание |
|
| Параметр | Обяз. | Умолч. | Описание |
|
||||||
|---------------|:-----:|---------|-------------------------------------------------|
|
|---------------|:-----:|---------|-------------------------------------------------|
|
||||||
| ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения |
|
| ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения |
|
||||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||||
| OutFile | нет | — | Записать результат в файл |
|
| OutFile | нет | — | Записать результат в файл |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src"
|
powershell.exe -NoProfile -File ".opencode/skills/cfe-validate/scripts/cfe-validate.ps1" -ExtensionPath "src"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src/Configuration.xml"
|
powershell.exe -NoProfile -File ".opencode/skills/cfe-validate/scripts/cfe-validate.ps1" -ExtensionPath "src/Configuration.xml"
|
||||||
```
|
```
|
||||||
+939
-939
File diff suppressed because it is too large
Load Diff
@@ -1,78 +1,70 @@
|
|||||||
---
|
---
|
||||||
name: db-create
|
name: db-create
|
||||||
description: Создание информационной базы 1С. Используй когда нужно создать базу, новую ИБ, пустую базу
|
description: Создание информационной базы 1С. Используй когда нужно создать базу, новую ИБ, пустую базу
|
||||||
argument-hint: <path|name>
|
argument-hint: <path|name>
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Write
|
- Write
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-create — Создание информационной базы
|
# /db-create — Создание информационной базы
|
||||||
|
|
||||||
Создаёт новую информационную базу 1С (файловую или серверную) и предлагает зарегистрировать в `.v8-project.json`.
|
Создаёт новую информационную базу 1С (файловую или серверную) и предлагает зарегистрировать в `.v8-project.json`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-create <path> — файловая база по указанному пути
|
/db-create <path> — файловая база по указанному пути
|
||||||
/db-create <server>/<name> — серверная база
|
/db-create <server>/<name> — серверная база
|
||||||
/db-create — интерактивно
|
/db-create — интерактивно
|
||||||
```
|
```
|
||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта для `v8path` (путь к платформе).
|
Прочитай `.v8-project.json` из корня проекта для `v8path` (путь к платформе).
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
После создания базы предложи зарегистрировать через `/db-list add`.
|
После создания базы предложи зарегистрировать через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Путь к файловой базе |
|
| `-InfoBasePath <путь>` | * | Путь к файловой базе |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UseTemplate <файл>` | нет | Создать из шаблона (.cf или .dt) |
|
| `-UseTemplate <файл>` | нет | Создать из шаблона (.cf или .dt) |
|
||||||
| `-AddToList` | нет | Добавить в список баз 1С |
|
| `-AddToList` | нет | Добавить в список баз 1С |
|
||||||
| `-ListName <имя>` | нет | Имя базы в списке |
|
| `-ListName <имя>` | нет | Имя базы в списке |
|
||||||
|
|
||||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
## Коды возврата
|
## После создания
|
||||||
|
|
||||||
| Код | Описание |
|
Предложи зарегистрировать базу в `.v8-project.json` (через `/db-list add`)
|
||||||
|-----|----------|
|
3. Если указан шаблон `/UseTemplate` — предупреди что конфигурация будет загружена из шаблона
|
||||||
| 0 | Успешно |
|
|
||||||
| 1 | Ошибка (см. лог) |
|
## Примеры
|
||||||
|
|
||||||
## После создания
|
```powershell
|
||||||
|
# Создать файловую базу
|
||||||
1. Прочитай лог-файл и покажи результат
|
powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB"
|
||||||
2. Предложи зарегистрировать базу в `.v8-project.json` (через `/db-list add`)
|
|
||||||
3. Если указан шаблон `/UseTemplate` — предупреди что конфигурация будет загружена из шаблона
|
# Создать серверную базу
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
|
||||||
## Примеры
|
|
||||||
|
# Создать из шаблона CF
|
||||||
```powershell
|
powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf"
|
||||||
# Создать файловую базу
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB"
|
# Создать и добавить в список баз
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-create/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база"
|
||||||
# Создать серверную базу
|
```
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
|
|
||||||
|
|
||||||
# Создать из шаблона CF
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf"
|
|
||||||
|
|
||||||
# Создать и добавить в список баз
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-create.ps1" -InfoBasePath "C:\Bases\NewDB" -AddToList -ListName "Новая база"
|
|
||||||
```
|
|
||||||
+220
-163
@@ -1,163 +1,220 @@
|
|||||||
# db-create v1.0 — Create 1C information base
|
# db-create v1.4 — Create 1C information base
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Создание информационной базы 1С
|
Создание информационной базы 1С
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Создаёт новую информационную базу 1С (файловую или серверную).
|
Создаёт новую информационную базу 1С (файловую или серверную).
|
||||||
Поддерживает создание из шаблона и добавление в список баз.
|
Поддерживает создание из шаблона и добавление в список баз.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UseTemplate
|
.PARAMETER UseTemplate
|
||||||
Путь к файлу шаблона (.cf или .dt)
|
Путь к файлу шаблона (.cf или .dt)
|
||||||
|
|
||||||
.PARAMETER AddToList
|
.PARAMETER AddToList
|
||||||
Добавить в список баз 1С
|
Добавить в список баз 1С
|
||||||
|
|
||||||
.PARAMETER ListName
|
.PARAMETER ListName
|
||||||
Имя базы в списке
|
Имя базы в списке
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-create.ps1 -InfoBasePath "C:\Bases\NewDB"
|
.\db-create.ps1 -InfoBasePath "C:\Bases\NewDB"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-create.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
|
.\db-create.ps1 -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-create.ps1 -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf" -AddToList -ListName "Новая база"
|
.\db-create.ps1 -InfoBasePath "C:\Bases\NewDB" -UseTemplate "C:\Templates\config.cf" -AddToList -ListName "Новая база"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UseTemplate,
|
[string]$UseTemplate,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$AddToList,
|
[switch]$AddToList,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$ListName
|
[string]$ListName
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Validate connection ---
|
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
if (-not $V8Path) {
|
||||||
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
$V8Path = Find-ProjectV8Path
|
||||||
exit 1
|
}
|
||||||
}
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
# --- Validate template ---
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
if ($UseTemplate -and -not (Test-Path $UseTemplate)) {
|
Select-Object -First 1
|
||||||
Write-Host "Error: template file not found: $UseTemplate" -ForegroundColor Red
|
if ($found) {
|
||||||
exit 1
|
$V8Path = $found.FullName
|
||||||
}
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
# --- Temp dir ---
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
$tempDir = Join-Path $env:TEMP "db_create_$(Get-Random)"
|
exit 1
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
}
|
||||||
|
}
|
||||||
try {
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
# --- Build arguments ---
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
$arguments = @("CREATEINFOBASE")
|
}
|
||||||
|
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
if (-not (Test-Path $V8Path)) {
|
||||||
$arguments += "Srvr=`"$InfoBaseServer`";Ref=`"$InfoBaseRef`""
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
} else {
|
exit 1
|
||||||
$arguments += "File=`"$InfoBasePath`""
|
}
|
||||||
}
|
|
||||||
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
# --- Template ---
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
if ($UseTemplate) {
|
|
||||||
$arguments += "/UseTemplate", "`"$UseTemplate`""
|
# --- Validate connection ---
|
||||||
}
|
if ($engine -eq "ibcmd") {
|
||||||
|
if (-not $InfoBasePath) {
|
||||||
# --- Add to list ---
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
if ($AddToList) {
|
exit 1
|
||||||
if ($ListName) {
|
}
|
||||||
$arguments += "/AddToList", "`"$ListName`""
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
} else {
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
$arguments += "/AddToList"
|
exit 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
# --- Validate template ---
|
||||||
# --- Output ---
|
if ($UseTemplate -and -not (Test-Path $UseTemplate)) {
|
||||||
$outFile = Join-Path $tempDir "create_log.txt"
|
Write-Host "Error: template file not found: $UseTemplate" -ForegroundColor Red
|
||||||
$arguments += "/Out", "`"$outFile`""
|
exit 1
|
||||||
$arguments += "/DisableStartupDialogs"
|
}
|
||||||
|
|
||||||
# --- Execute ---
|
# --- Temp dir ---
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
$tempDir = Join-Path $env:TEMP "db_create_$(Get-Random)"
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
$exitCode = $process.ExitCode
|
|
||||||
|
try {
|
||||||
# --- Result ---
|
if ($engine -eq "ibcmd") {
|
||||||
if ($exitCode -eq 0) {
|
# --- ibcmd branch (file infobase only) ---
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
$arguments = @("infobase", "create", "--db-path=$InfoBasePath", "--create-database")
|
||||||
Write-Host "Information base created successfully: $InfoBaseServer/$InfoBaseRef" -ForegroundColor Green
|
if ($UseTemplate) {
|
||||||
} else {
|
if ([System.IO.Path]::GetExtension($UseTemplate) -ieq ".dt") {
|
||||||
Write-Host "Information base created successfully: $InfoBasePath" -ForegroundColor Green
|
$arguments += "--restore=$UseTemplate"
|
||||||
}
|
} else {
|
||||||
} else {
|
$arguments += "--load=$UseTemplate", "--apply"
|
||||||
Write-Host "Error creating information base (code: $exitCode)" -ForegroundColor Red
|
}
|
||||||
}
|
}
|
||||||
|
$arguments += "--data=$tempDir"
|
||||||
if (Test-Path $outFile) {
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
$output = & $V8Path @arguments 2>&1
|
||||||
if ($logContent) {
|
$exitCode = $LASTEXITCODE
|
||||||
Write-Host "--- Log ---"
|
if ($exitCode -eq 0) {
|
||||||
Write-Host $logContent
|
Write-Host "Information base created successfully: $InfoBasePath" -ForegroundColor Green
|
||||||
Write-Host "--- End ---"
|
} else {
|
||||||
}
|
Write-Host "Error creating information base (code: $exitCode)" -ForegroundColor Red
|
||||||
}
|
}
|
||||||
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
exit $exitCode
|
exit $exitCode
|
||||||
|
}
|
||||||
} finally {
|
|
||||||
if (Test-Path $tempDir) {
|
# --- 1cv8 branch ---
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
# --- Build arguments ---
|
||||||
}
|
$arguments = @("CREATEINFOBASE")
|
||||||
}
|
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
$arguments += "Srvr=`"$InfoBaseServer`";Ref=`"$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
|
$arguments += "File=`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Template ---
|
||||||
|
if ($UseTemplate) {
|
||||||
|
$arguments += "/UseTemplate", "`"$UseTemplate`""
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Add to list ---
|
||||||
|
if ($AddToList) {
|
||||||
|
if ($ListName) {
|
||||||
|
$arguments += "/AddToList", "`"$ListName`""
|
||||||
|
} else {
|
||||||
|
$arguments += "/AddToList"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "create_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
Write-Host "Information base created successfully: $InfoBaseServer/$InfoBaseRef" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Information base created successfully: $InfoBasePath" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "Error creating information base (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
-9
@@ -1,29 +1,65 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-create v1.0 — Create 1C information base
|
# db-create v1.4 — Create 1C information base
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
found = sorted(glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe"))
|
v8path = _find_project_v8path()
|
||||||
if found:
|
if not v8path:
|
||||||
return found[-1]
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
|
if candidates:
|
||||||
|
v8path = max(candidates, key=_version_key)
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -47,9 +83,14 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Validate connection ---
|
# --- Validate connection ---
|
||||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -58,6 +99,29 @@ def main():
|
|||||||
print(f"Error: template file not found: {args.UseTemplate}", file=sys.stderr)
|
print(f"Error: template file not found: {args.UseTemplate}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# --- ibcmd branch (file infobase only) ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
arguments = ["infobase", "create", f"--db-path={args.InfoBasePath}", "--create-database"]
|
||||||
|
if args.UseTemplate:
|
||||||
|
if os.path.splitext(args.UseTemplate)[1].lower() == ".dt":
|
||||||
|
arguments.append(f"--restore={args.UseTemplate}")
|
||||||
|
else:
|
||||||
|
arguments.extend([f"--load={args.UseTemplate}", "--apply"])
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"Information base created successfully: {args.InfoBasePath}")
|
||||||
|
else:
|
||||||
|
print(f"Error creating information base (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
# --- Temp dir ---
|
# --- Temp dir ---
|
||||||
temp_dir = os.path.join(tempfile.gettempdir(), f"db_create_{random.randint(0, 999999)}")
|
temp_dir = os.path.join(tempfile.gettempdir(), f"db_create_{random.randint(0, 999999)}")
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
@@ -67,9 +131,11 @@ def main():
|
|||||||
arguments = ["CREATEINFOBASE"]
|
arguments = ["CREATEINFOBASE"]
|
||||||
|
|
||||||
if args.InfoBaseServer and args.InfoBaseRef:
|
if args.InfoBaseServer and args.InfoBaseRef:
|
||||||
arguments.append(f'Srvr="{args.InfoBaseServer}";Ref="{args.InfoBaseRef}"')
|
# No embedded quotes: subprocess quotes the whole token; 1C's argv parser
|
||||||
|
# strips outer quotes. Inner quotes get escaped by list2cmdline and break parsing.
|
||||||
|
arguments.append(f'Srvr={args.InfoBaseServer};Ref={args.InfoBaseRef}')
|
||||||
else:
|
else:
|
||||||
arguments.append(f'File="{args.InfoBasePath}"')
|
arguments.append(f'File={args.InfoBasePath}')
|
||||||
|
|
||||||
# --- Template ---
|
# --- Template ---
|
||||||
if args.UseTemplate:
|
if args.UseTemplate:
|
||||||
@@ -1,79 +1,68 @@
|
|||||||
---
|
---
|
||||||
name: db-dump-cf
|
name: db-dump-cf
|
||||||
description: Выгрузка конфигурации 1С в CF-файл. Используй когда нужно выгрузить конфигурацию в CF, сохранить конфигурацию, сделать бэкап CF
|
description: Выгрузка конфигурации 1С в CF-файл. Используй когда нужно выгрузить конфигурацию в CF, сохранить конфигурацию, сделать бэкап CF
|
||||||
argument-hint: "[database] [output.cf]"
|
argument-hint: "[database] [output.cf]"
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-dump-cf — Выгрузка конфигурации в CF-файл
|
# /db-dump-cf — Выгрузка конфигурации в CF-файл
|
||||||
|
|
||||||
Выгружает конфигурацию информационной базы в бинарный CF-файл.
|
Выгружает конфигурацию информационной базы в бинарный CF-файл.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-dump-cf [database] [output.cf]
|
/db-dump-cf [database] [output.cf]
|
||||||
/db-dump-cf dev config.cf
|
/db-dump-cf dev config.cf
|
||||||
/db-dump-cf — база по умолчанию, файл config.cf
|
/db-dump-cf — база по умолчанию, файл config.cf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-cf/scripts/db-dump-cf.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-OutputFile <путь>` | да | Путь к выходному CF-файлу |
|
| `-OutputFile <путь>` | да | Путь к выходному CF-файлу |
|
||||||
| `-Extension <имя>` | нет | Выгрузить расширение |
|
| `-Extension <имя>` | нет | Выгрузить расширение |
|
||||||
| `-AllExtensions` | нет | Выгрузить все расширения |
|
| `-AllExtensions` | нет | Выгрузить все расширения |
|
||||||
|
|
||||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
## Коды возврата
|
## Примеры
|
||||||
|
|
||||||
| Код | Описание |
|
```powershell
|
||||||
|-----|----------|
|
# Выгрузка конфигурации (файловая база)
|
||||||
| 0 | Успешно |
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-cf/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf"
|
||||||
| 1 | Ошибка (см. лог) |
|
|
||||||
|
# Серверная база
|
||||||
## После выполнения
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-cf/scripts/db-dump-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf"
|
||||||
|
|
||||||
Прочитай лог-файл и покажи результат. Если есть ошибки — покажи содержимое лога.
|
# Выгрузка расширения
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-cf/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение"
|
||||||
## Примеры
|
```
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Выгрузка конфигурации (файловая база)
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\config.cf"
|
|
||||||
|
|
||||||
# Серверная база
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "config.cf"
|
|
||||||
|
|
||||||
# Выгрузка расширения
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "ext.cfe" -Extension "МоёРасширение"
|
|
||||||
```
|
|
||||||
+224
-166
@@ -1,166 +1,224 @@
|
|||||||
# db-dump-cf v1.0 — Dump 1C configuration to CF file
|
# db-dump-cf v1.4 — Dump 1C configuration to CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Выгрузка конфигурации 1С в CF-файл
|
Выгрузка конфигурации 1С в CF-файл
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Выгружает конфигурацию информационной базы в бинарный CF-файл.
|
Выгружает конфигурацию информационной базы в бинарный CF-файл.
|
||||||
Поддерживает выгрузку расширений.
|
Поддерживает выгрузку расширений.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER OutputFile
|
.PARAMETER OutputFile
|
||||||
Путь к выходному CF-файлу
|
Путь к выходному CF-файлу
|
||||||
|
|
||||||
.PARAMETER Extension
|
.PARAMETER Extension
|
||||||
Имя расширения для выгрузки
|
Имя расширения для выгрузки
|
||||||
|
|
||||||
.PARAMETER AllExtensions
|
.PARAMETER AllExtensions
|
||||||
Выгрузить все расширения
|
Выгрузить все расширения
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "config.cf"
|
.\db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "config.cf"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "ext.cfe" -Extension "МоёРасширение"
|
.\db-dump-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "ext.cfe" -Extension "МоёРасширение"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$OutputFile,
|
[string]$OutputFile,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Extension,
|
[string]$Extension,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$AllExtensions
|
[switch]$AllExtensions
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Validate connection ---
|
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
if (-not $V8Path) {
|
||||||
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
$V8Path = Find-ProjectV8Path
|
||||||
exit 1
|
}
|
||||||
}
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
# --- Ensure output directory exists ---
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
$outDir = Split-Path $OutputFile -Parent
|
Select-Object -First 1
|
||||||
if ($outDir -and -not (Test-Path $outDir)) {
|
if ($found) {
|
||||||
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
$V8Path = $found.FullName
|
||||||
}
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
# --- Temp dir ---
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
$tempDir = Join-Path $env:TEMP "db_dump_cf_$(Get-Random)"
|
exit 1
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
}
|
||||||
|
}
|
||||||
try {
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
# --- Build arguments ---
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
$arguments = @("DESIGNER")
|
}
|
||||||
|
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
if (-not (Test-Path $V8Path)) {
|
||||||
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
} else {
|
exit 1
|
||||||
$arguments += "/F", "`"$InfoBasePath`""
|
}
|
||||||
}
|
|
||||||
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
if ($Password) { $arguments += "/P`"$Password`"" }
|
|
||||||
|
# --- Validate connection ---
|
||||||
$arguments += "/DumpCfg", "`"$OutputFile`""
|
if ($engine -eq "ibcmd") {
|
||||||
|
if (-not $InfoBasePath) {
|
||||||
# --- Extensions ---
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
if ($Extension) {
|
exit 1
|
||||||
$arguments += "-Extension", "`"$Extension`""
|
}
|
||||||
} elseif ($AllExtensions) {
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
$arguments += "-AllExtensions"
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
}
|
exit 1
|
||||||
|
}
|
||||||
# --- Output ---
|
|
||||||
$outFile = Join-Path $tempDir "dump_cf_log.txt"
|
# --- Ensure output directory exists ---
|
||||||
$arguments += "/Out", "`"$outFile`""
|
$outDir = Split-Path $OutputFile -Parent
|
||||||
$arguments += "/DisableStartupDialogs"
|
if ($outDir -and -not (Test-Path $outDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
||||||
# --- Execute ---
|
}
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
# --- Temp dir ---
|
||||||
$exitCode = $process.ExitCode
|
$tempDir = Join-Path $env:TEMP "db_dump_cf_$(Get-Random)"
|
||||||
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
# --- Result ---
|
|
||||||
if ($exitCode -eq 0) {
|
try {
|
||||||
Write-Host "Configuration dumped successfully to: $OutputFile" -ForegroundColor Green
|
if ($engine -eq "ibcmd") {
|
||||||
} else {
|
# --- ibcmd branch (file infobase only) ---
|
||||||
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
|
if ($AllExtensions) {
|
||||||
}
|
Write-Host "Error: ibcmd config save does not support -AllExtensions (use -Extension)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
if (Test-Path $outFile) {
|
}
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
$arguments = @("infobase", "config", "save", "--db-path=$InfoBasePath")
|
||||||
if ($logContent) {
|
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||||
Write-Host "--- Log ---"
|
$arguments += "$OutputFile"
|
||||||
Write-Host $logContent
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
Write-Host "--- End ---"
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
}
|
$arguments += "--data=$tempDir"
|
||||||
}
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
|
$output = & $V8Path @arguments 2>&1
|
||||||
exit $exitCode
|
$exitCode = $LASTEXITCODE
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
} finally {
|
Write-Host "Configuration dumped successfully to: $OutputFile" -ForegroundColor Green
|
||||||
if (Test-Path $tempDir) {
|
} else {
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
}
|
}
|
||||||
}
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
|
exit $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1cv8 branch ---
|
||||||
|
# --- Build arguments ---
|
||||||
|
$arguments = @("DESIGNER")
|
||||||
|
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
|
|
||||||
|
$arguments += "/DumpCfg", "`"$OutputFile`""
|
||||||
|
|
||||||
|
# --- Extensions ---
|
||||||
|
if ($Extension) {
|
||||||
|
$arguments += "-Extension", "`"$Extension`""
|
||||||
|
} elseif ($AllExtensions) {
|
||||||
|
$arguments += "-AllExtensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "dump_cf_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Configuration dumped successfully to: $OutputFile" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
-7
@@ -1,29 +1,65 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-dump-cf v1.0 — Dump 1C configuration to CF file
|
# db-dump-cf v1.4 — Dump 1C configuration to CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
found = sorted(glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe"))
|
v8path = _find_project_v8path()
|
||||||
if found:
|
if not v8path:
|
||||||
return found[-1]
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
|
if candidates:
|
||||||
|
v8path = max(candidates, key=_version_key)
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -49,9 +85,14 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Validate connection ---
|
# --- Validate connection ---
|
||||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -60,6 +101,34 @@ def main():
|
|||||||
if out_dir and not os.path.isdir(out_dir):
|
if out_dir and not os.path.isdir(out_dir):
|
||||||
os.makedirs(out_dir, exist_ok=True)
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# --- ibcmd branch (file infobase only) ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if args.AllExtensions:
|
||||||
|
print("Error: ibcmd config save does not support -AllExtensions (use -Extension)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
arguments = ["infobase", "config", "save", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if args.Extension:
|
||||||
|
arguments.append(f"--extension={args.Extension}")
|
||||||
|
arguments.append(args.OutputFile)
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"Configuration dumped successfully to: {args.OutputFile}")
|
||||||
|
else:
|
||||||
|
print(f"Error dumping configuration (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
# --- Temp dir ---
|
# --- Temp dir ---
|
||||||
temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_cf_{random.randint(0, 999999)}")
|
temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_cf_{random.randint(0, 999999)}")
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
name: db-dump-dt
|
||||||
|
description: Выгрузка информационной базы 1С в DT-файл (вся база — конфигурация + данные). Используй когда нужно выгрузить информационную базу, выгрузить архив базы, сделать бэкап, выгрузить dt
|
||||||
|
argument-hint: "[database] [output.dt]"
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
- Glob
|
||||||
|
- AskUserQuestion
|
||||||
|
---
|
||||||
|
|
||||||
|
# /db-dump-dt — Выгрузка информационной базы в DT-файл
|
||||||
|
|
||||||
|
Выгружает информационную базу целиком (конфигурация **+ данные**) в DT-файл — полный снимок ИБ.
|
||||||
|
|
||||||
|
> В отличие от `/db-dump-cf` (только конфигурация), `.dt` содержит **всю базу**: данные,
|
||||||
|
> настройки, пользователей. Это бэкап/точка отката, а не выгрузка метаданных.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/db-dump-dt [database] [output.dt]
|
||||||
|
/db-dump-dt dev backup.dt
|
||||||
|
/db-dump-dt — база по умолчанию, имя файла по базе и дате
|
||||||
|
```
|
||||||
|
|
||||||
|
## Параметры подключения
|
||||||
|
|
||||||
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
|
4. Если ветка не совпала — используй `default`
|
||||||
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
|
Если файла нет — предложи `/db-list add`.
|
||||||
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
|
## Команда
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-dt/scripts/db-dump-dt.ps1" <параметры>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Параметры скрипта
|
||||||
|
|
||||||
|
| Параметр | Обязательный | Описание |
|
||||||
|
|----------|:------------:|----------|
|
||||||
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
|
| `-OutputFile <путь>` | да | Путь к выходному DT-файлу |
|
||||||
|
|
||||||
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
|
## Примеры
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Выгрузка ИБ (файловая база)
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-dt/scripts/db-dump-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -OutputFile "C:\backup\base.dt"
|
||||||
|
|
||||||
|
# Серверная база
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-dt/scripts/db-dump-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -OutputFile "base.dt"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Связанные навыки
|
||||||
|
|
||||||
|
- `/db-load-dt` — загрузка ИБ из DT (обратная операция)
|
||||||
|
- `/db-dump-cf` — выгрузка только конфигурации (без данных)
|
||||||
|
- `/db-create` — создать новую базу (в т.ч. из DT-шаблона)
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# db-dump-dt v1.3 — Dump 1C information base to DT file
|
||||||
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Выгрузка информационной базы 1С в DT-файл
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Выгружает информационную базу целиком (конфигурация + данные) в DT-файл.
|
||||||
|
|
||||||
|
.PARAMETER V8Path
|
||||||
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
|
.PARAMETER InfoBasePath
|
||||||
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
|
.PARAMETER InfoBaseServer
|
||||||
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
|
.PARAMETER InfoBaseRef
|
||||||
|
Имя базы на сервере
|
||||||
|
|
||||||
|
.PARAMETER UserName
|
||||||
|
Имя пользователя 1С
|
||||||
|
|
||||||
|
.PARAMETER Password
|
||||||
|
Пароль пользователя
|
||||||
|
|
||||||
|
.PARAMETER OutputFile
|
||||||
|
Путь к выходному DT-файлу
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\db-dump-dt.ps1 -InfoBasePath "C:\Bases\MyDB" -OutputFile "backup.dt"
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$V8Path,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$UserName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Password,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$OutputFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
# --- Resolve V8Path ---
|
||||||
|
function Find-ProjectV8Path {
|
||||||
|
$dir = (Get-Location).Path
|
||||||
|
while ($dir) {
|
||||||
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
|
if (Test-Path $pf) {
|
||||||
|
try {
|
||||||
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
|
} catch {}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
|
$dir = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $V8Path) {
|
||||||
|
$V8Path = Find-ProjectV8Path
|
||||||
|
}
|
||||||
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($found) {
|
||||||
|
$V8Path = $found.FullName
|
||||||
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $V8Path)) {
|
||||||
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
|
|
||||||
|
# --- Validate connection ---
|
||||||
|
if ($engine -eq "ibcmd") {
|
||||||
|
if (-not $InfoBasePath) {
|
||||||
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Ensure output directory exists ---
|
||||||
|
$outDir = Split-Path $OutputFile -Parent
|
||||||
|
if ($outDir -and -not (Test-Path $outDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Temp dir ---
|
||||||
|
$tempDir = Join-Path $env:TEMP "db_dump_dt_$(Get-Random)"
|
||||||
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($engine -eq "ibcmd") {
|
||||||
|
# --- ibcmd branch (file infobase only) ---
|
||||||
|
$arguments = @("infobase", "dump", "--db-path=$InfoBasePath")
|
||||||
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
|
$arguments += "$OutputFile"
|
||||||
|
|
||||||
|
$arguments += "--data=$tempDir"
|
||||||
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
|
$output = & $V8Path @arguments 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Information base dumped successfully to: $OutputFile" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error dumping information base (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
|
exit $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1cv8 branch ---
|
||||||
|
# --- Build arguments ---
|
||||||
|
$arguments = @("DESIGNER")
|
||||||
|
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
|
|
||||||
|
$arguments += "/DumpIB", "`"$OutputFile`""
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "dump_dt_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Information base dumped successfully to: $OutputFile" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error dumping information base (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# db-dump-dt v1.3 — Dump 1C information base to DT file
|
||||||
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import atexit
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_v8path(v8path):
|
||||||
|
"""Resolve path to 1cv8.exe."""
|
||||||
|
if not v8path:
|
||||||
|
v8path = _find_project_v8path()
|
||||||
|
if not v8path:
|
||||||
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
|
if candidates:
|
||||||
|
v8path = max(candidates, key=_version_key)
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
|
else:
|
||||||
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if os.path.isdir(v8path):
|
||||||
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
if not os.path.isfile(v8path):
|
||||||
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
return v8path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Dump 1C information base to DT file",
|
||||||
|
allow_abbrev=False,
|
||||||
|
)
|
||||||
|
parser.add_argument("-V8Path", default="")
|
||||||
|
parser.add_argument("-InfoBasePath", default="")
|
||||||
|
parser.add_argument("-InfoBaseServer", default="")
|
||||||
|
parser.add_argument("-InfoBaseRef", default="")
|
||||||
|
parser.add_argument("-UserName", default="")
|
||||||
|
parser.add_argument("-Password", default="")
|
||||||
|
parser.add_argument("-OutputFile", required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
|
# --- Validate connection ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# --- Ensure output directory exists ---
|
||||||
|
out_dir = os.path.dirname(args.OutputFile)
|
||||||
|
if out_dir and not os.path.isdir(out_dir):
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# --- ibcmd branch (file infobase only) ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
arguments = ["infobase", "dump", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(args.OutputFile)
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"Information base dumped successfully to: {args.OutputFile}")
|
||||||
|
else:
|
||||||
|
print(f"Error dumping information base (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
|
# --- Temp dir ---
|
||||||
|
temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_dt_{random.randint(0, 999999)}")
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# --- Build arguments ---
|
||||||
|
arguments = ["DESIGNER"]
|
||||||
|
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef:
|
||||||
|
arguments.extend(["/S", f"{args.InfoBaseServer}/{args.InfoBaseRef}"])
|
||||||
|
else:
|
||||||
|
arguments.extend(["/F", args.InfoBasePath])
|
||||||
|
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"/N{args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"/P{args.Password}")
|
||||||
|
|
||||||
|
arguments.extend(["/DumpIB", args.OutputFile])
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
out_file = os.path.join(temp_dir, "dump_dt_log.txt")
|
||||||
|
arguments.extend(["/Out", out_file])
|
||||||
|
arguments.append("/DisableStartupDialogs")
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
print(f"Running: 1cv8.exe {' '.join(arguments)}")
|
||||||
|
result = subprocess.run(
|
||||||
|
[v8path] + arguments,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
exit_code = result.returncode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if exit_code == 0:
|
||||||
|
print(f"Information base dumped successfully to: {args.OutputFile}")
|
||||||
|
else:
|
||||||
|
print(f"Error dumping information base (code: {exit_code})", file=sys.stderr)
|
||||||
|
|
||||||
|
if os.path.isfile(out_file):
|
||||||
|
try:
|
||||||
|
with open(out_file, "r", encoding="utf-8-sig") as f:
|
||||||
|
log_content = f.read()
|
||||||
|
if log_content:
|
||||||
|
print("--- Log ---")
|
||||||
|
print(log_content)
|
||||||
|
print("--- End ---")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if os.path.isdir(temp_dir):
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,97 +1,90 @@
|
|||||||
---
|
---
|
||||||
name: db-dump-xml
|
name: db-dump-xml
|
||||||
description: Выгрузка конфигурации 1С в XML-файлы. Используй когда нужно выгрузить конфигурацию в файлы, XML, исходники, DumpConfigToFiles
|
description: Выгрузка конфигурации 1С в XML-файлы. Используй когда нужно выгрузить конфигурацию в файлы, XML, исходники, DumpConfigToFiles
|
||||||
argument-hint: "[database] [outputDir]"
|
argument-hint: "[database] [outputDir]"
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-dump-xml — Выгрузка конфигурации в XML
|
# /db-dump-xml — Выгрузка конфигурации в XML
|
||||||
|
|
||||||
Выгружает конфигурацию информационной базы в XML-файлы (исходники). Поддерживает полную, инкрементальную, частичную выгрузку и обновление ConfigDumpInfo.
|
Выгружает конфигурацию информационной базы в XML-файлы (исходники). Поддерживает полную, инкрементальную, частичную выгрузку и обновление ConfigDumpInfo.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-dump-xml [database] [outputDir]
|
/db-dump-xml [database] [outputDir]
|
||||||
/db-dump-xml dev src/config
|
/db-dump-xml dev src/config
|
||||||
/db-dump-xml dev src/config -Mode Full
|
/db-dump-xml dev src/config -Mode Full
|
||||||
/db-dump-xml dev src/config -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
/db-dump-xml dev src/config -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
Если в записи базы указан `configSrc` — используй как каталог выгрузки по умолчанию.
|
Если в записи базы указан `configSrc` — используй как каталог выгрузки по умолчанию.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-ConfigDir <путь>` | да | Каталог для выгрузки |
|
| `-ConfigDir <путь>` | да | Каталог для выгрузки |
|
||||||
| `-Mode <режим>` | нет | `Full` / `Changes` (по умолч.) / `Partial` / `UpdateInfo` |
|
| `-Mode <режим>` | нет | `Full` / `Changes` (по умолч.) / `Partial` / `UpdateInfo` |
|
||||||
| `-Objects <список>` | для Partial | Имена объектов через запятую |
|
| `-Objects <список>` | для Partial | Имена объектов через запятую |
|
||||||
| `-Extension <имя>` | нет | Выгрузить расширение |
|
| `-Extension <имя>` | нет | Выгрузить расширение |
|
||||||
| `-AllExtensions` | нет | Выгрузить все расширения |
|
| `-AllExtensions` | нет | Выгрузить все расширения |
|
||||||
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
||||||
|
|
||||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
### Режимы выгрузки
|
### Режимы выгрузки
|
||||||
|
|
||||||
| Режим | Описание |
|
| Режим | Описание |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| `Full` | Полная выгрузка — все объекты конфигурации |
|
| `Full` | Полная выгрузка — все объекты конфигурации |
|
||||||
| `Changes` | Инкрементальная — только изменённые с последней выгрузки (использует ConfigDumpInfo.xml) |
|
| `Changes` | Инкрементальная — только изменённые с последней выгрузки (использует ConfigDumpInfo.xml) |
|
||||||
| `Partial` | Частичная — выбранные объекты из параметра `-Objects` |
|
| `Partial` | Частичная — выбранные объекты из параметра `-Objects` |
|
||||||
| `UpdateInfo` | Обновить только ConfigDumpInfo.xml без выгрузки файлов |
|
| `UpdateInfo` | Обновить только ConfigDumpInfo.xml без выгрузки файлов |
|
||||||
|
|
||||||
## Коды возврата
|
> Если пользователь просит выгрузить конкретные объекты — используй `-Mode Partial` с `-Objects`.
|
||||||
|
|
||||||
| Код | Описание |
|
## Примеры
|
||||||
|-----|----------|
|
|
||||||
| 0 | Успешно |
|
```powershell
|
||||||
| 1 | Ошибка (см. лог) |
|
# Полная выгрузка (файловая база)
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||||
> Если пользователь просит выгрузить конкретные объекты — используй `-Mode Partial` с `-Objects`.
|
|
||||||
|
# Инкрементальная выгрузка
|
||||||
## Примеры
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Changes
|
||||||
|
|
||||||
```powershell
|
# Частичная выгрузка
|
||||||
# Полная выгрузка (файловая база)
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
|
||||||
|
# Серверная база
|
||||||
# Инкрементальная выгрузка
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -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\cfsrc" -Mode Changes
|
|
||||||
|
# Выгрузка расширения
|
||||||
# Частичная выгрузка
|
powershell.exe -NoProfile -File ".opencode/skills/db-dump-xml/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
```
|
||||||
|
|
||||||
# Серверная база
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Password "secret" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
|
||||||
|
|
||||||
# Выгрузка расширения
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
|
||||||
```
|
|
||||||
+293
-223
@@ -1,224 +1,294 @@
|
|||||||
# db-dump-xml v1.0 — Dump 1C configuration to XML files
|
# db-dump-xml v1.6 — Dump 1C configuration to XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Выгрузка конфигурации 1С в XML-файлы
|
Выгрузка конфигурации 1С в XML-файлы
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Выполняет выгрузку конфигурации 1С в файлы в четырёх режимах:
|
Выполняет выгрузку конфигурации 1С в файлы в четырёх режимах:
|
||||||
- Full: полная выгрузка всей конфигурации
|
- Full: полная выгрузка всей конфигурации
|
||||||
- Changes: инкрементальная выгрузка изменённых объектов
|
- Changes: инкрементальная выгрузка изменённых объектов
|
||||||
- Partial: выгрузка конкретных объектов из списка
|
- Partial: выгрузка конкретных объектов из списка
|
||||||
- UpdateInfo: обновление только ConfigDumpInfo.xml
|
- UpdateInfo: обновление только ConfigDumpInfo.xml
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER ConfigDir
|
.PARAMETER ConfigDir
|
||||||
Каталог для выгрузки конфигурации
|
Каталог для выгрузки конфигурации
|
||||||
|
|
||||||
.PARAMETER Mode
|
.PARAMETER Mode
|
||||||
Режим выгрузки: Full, Changes, Partial, UpdateInfo (по умолчанию Changes)
|
Режим выгрузки: Full, Changes, Partial, UpdateInfo (по умолчанию Changes)
|
||||||
|
|
||||||
.PARAMETER Objects
|
.PARAMETER Objects
|
||||||
Имена объектов метаданных через запятую (для режима Partial)
|
Имена объектов метаданных через запятую (для режима Partial)
|
||||||
|
|
||||||
.PARAMETER Extension
|
.PARAMETER Extension
|
||||||
Имя расширения для выгрузки
|
Имя расширения для выгрузки
|
||||||
|
|
||||||
.PARAMETER AllExtensions
|
.PARAMETER AllExtensions
|
||||||
Выгрузить все расширения
|
Выгрузить все расширения
|
||||||
|
|
||||||
.PARAMETER Format
|
.PARAMETER Format
|
||||||
Формат выгрузки: Hierarchical или Plain (по умолчанию Hierarchical)
|
Формат выгрузки: Hierarchical или Plain (по умолчанию Hierarchical)
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Full
|
.\db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Full
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
.\db-dump-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Partial -Objects "Справочник.Номенклатура,Документ.Заказ"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$ConfigDir,
|
[string]$ConfigDir,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[ValidateSet("Full", "Changes", "Partial", "UpdateInfo")]
|
[ValidateSet("Full", "Changes", "Partial", "UpdateInfo")]
|
||||||
[string]$Mode = "Changes",
|
[string]$Mode = "Changes",
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Objects,
|
[string]$Objects,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Extension,
|
[string]$Extension,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$AllExtensions,
|
[switch]$AllExtensions,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[ValidateSet("Hierarchical", "Plain")]
|
[ValidateSet("Hierarchical", "Plain")]
|
||||||
[string]$Format = "Hierarchical"
|
[string]$Format = "Hierarchical"
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Validate connection ---
|
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
if (-not $V8Path) {
|
||||||
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
$V8Path = Find-ProjectV8Path
|
||||||
exit 1
|
}
|
||||||
}
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
# --- Validate Partial mode ---
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
if ($Mode -eq "Partial" -and -not $Objects) {
|
Select-Object -First 1
|
||||||
Write-Host "Error: -Objects required for Partial mode" -ForegroundColor Red
|
if ($found) {
|
||||||
exit 1
|
$V8Path = $found.FullName
|
||||||
}
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
# --- Create output dir if needed ---
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
if (-not (Test-Path $ConfigDir)) {
|
exit 1
|
||||||
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
|
}
|
||||||
Write-Host "Created output directory: $ConfigDir"
|
}
|
||||||
}
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
# --- Temp dir ---
|
}
|
||||||
$tempDir = Join-Path $env:TEMP "db_dump_xml_$(Get-Random)"
|
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
if (-not (Test-Path $V8Path)) {
|
||||||
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
try {
|
exit 1
|
||||||
# --- Build arguments ---
|
}
|
||||||
$arguments = @("DESIGNER")
|
|
||||||
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
|
||||||
} else {
|
# --- Validate connection ---
|
||||||
$arguments += "/F", "`"$InfoBasePath`""
|
if ($engine -eq "ibcmd") {
|
||||||
}
|
if (-not $InfoBasePath) {
|
||||||
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
exit 1
|
||||||
if ($Password) { $arguments += "/P`"$Password`"" }
|
}
|
||||||
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
$arguments += "/DumpConfigToFiles", "`"$ConfigDir`""
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
$arguments += "-Format", $Format
|
exit 1
|
||||||
|
}
|
||||||
switch ($Mode) {
|
|
||||||
"Full" {
|
# --- Validate Partial mode ---
|
||||||
Write-Host "Executing full configuration dump..."
|
if ($Mode -eq "Partial" -and -not $Objects) {
|
||||||
}
|
Write-Host "Error: -Objects required for Partial mode" -ForegroundColor Red
|
||||||
"Changes" {
|
exit 1
|
||||||
Write-Host "Executing incremental configuration dump..."
|
}
|
||||||
$arguments += "-update"
|
|
||||||
$arguments += "-force"
|
# --- Create output dir if needed ---
|
||||||
}
|
if (-not (Test-Path $ConfigDir)) {
|
||||||
"Partial" {
|
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
|
||||||
Write-Host "Executing partial configuration dump..."
|
Write-Host "Created output directory: $ConfigDir"
|
||||||
$objectList = $Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
}
|
||||||
|
|
||||||
$listFile = Join-Path $tempDir "dump_list.txt"
|
# --- Temp dir ---
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
$tempDir = Join-Path $env:TEMP "db_dump_xml_$(Get-Random)"
|
||||||
[System.IO.File]::WriteAllLines($listFile, $objectList, $utf8Bom)
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
|
|
||||||
$arguments += "-listFile", "`"$listFile`""
|
try {
|
||||||
Write-Host "Objects to dump: $($objectList.Count)"
|
if ($engine -eq "ibcmd") {
|
||||||
foreach ($obj in $objectList) { Write-Host " $obj" }
|
# --- ibcmd branch (file infobase only; hierarchical Full/Changes) ---
|
||||||
}
|
if ($Format -eq "Plain") {
|
||||||
"UpdateInfo" {
|
Write-Host "Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
|
||||||
Write-Host "Updating ConfigDumpInfo.xml..."
|
exit 1
|
||||||
$arguments += "-configDumpInfoOnly"
|
}
|
||||||
}
|
if ($AllExtensions) {
|
||||||
}
|
$arguments = @("infobase", "config", "export", "all-extensions", "$ConfigDir", "--db-path=$InfoBasePath")
|
||||||
|
} elseif ($Mode -eq "UpdateInfo") {
|
||||||
# --- Extensions ---
|
Write-Host "Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8" -ForegroundColor Red
|
||||||
if ($Extension) {
|
exit 1
|
||||||
$arguments += "-Extension", "`"$Extension`""
|
} elseif ($Mode -eq "Partial") {
|
||||||
} elseif ($AllExtensions) {
|
$objList = @($Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||||
$arguments += "-AllExtensions"
|
$arguments = @("infobase", "config", "export", "objects") + $objList
|
||||||
}
|
$arguments += "--out=$ConfigDir", "--db-path=$InfoBasePath"
|
||||||
|
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||||
# --- Output ---
|
} else {
|
||||||
$outFile = Join-Path $tempDir "dump_log.txt"
|
$arguments = @("infobase", "config", "export", "--db-path=$InfoBasePath")
|
||||||
$arguments += "/Out", "`"$outFile`""
|
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||||
$arguments += "/DisableStartupDialogs"
|
$arguments += "$ConfigDir"
|
||||||
|
}
|
||||||
# --- Execute ---
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
$arguments += "--data=$tempDir"
|
||||||
$exitCode = $process.ExitCode
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
|
$output = & $V8Path @arguments 2>&1
|
||||||
# --- Result ---
|
$exitCode = $LASTEXITCODE
|
||||||
if ($exitCode -eq 0) {
|
if ($exitCode -eq 0) {
|
||||||
Write-Host "Dump completed successfully" -ForegroundColor Green
|
Write-Host "Configuration exported successfully to: $ConfigDir" -ForegroundColor Green
|
||||||
Write-Host "Configuration dumped to: $ConfigDir"
|
} else {
|
||||||
} else {
|
Write-Host "Error exporting configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
|
}
|
||||||
}
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
|
exit $exitCode
|
||||||
if (Test-Path $outFile) {
|
}
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
|
||||||
if ($logContent) {
|
# --- 1cv8 branch ---
|
||||||
Write-Host "--- Log ---"
|
# --- Build arguments ---
|
||||||
Write-Host $logContent
|
$arguments = @("DESIGNER")
|
||||||
Write-Host "--- End ---"
|
|
||||||
}
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
}
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
exit $exitCode
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
} finally {
|
|
||||||
if (Test-Path $tempDir) {
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
}
|
|
||||||
}
|
$arguments += "/DumpConfigToFiles", "`"$ConfigDir`""
|
||||||
|
$arguments += "-Format", $Format
|
||||||
|
|
||||||
|
switch ($Mode) {
|
||||||
|
"Full" {
|
||||||
|
Write-Host "Executing full configuration dump..."
|
||||||
|
}
|
||||||
|
"Changes" {
|
||||||
|
Write-Host "Executing incremental configuration dump..."
|
||||||
|
$arguments += "-update"
|
||||||
|
$arguments += "-force"
|
||||||
|
}
|
||||||
|
"Partial" {
|
||||||
|
Write-Host "Executing partial configuration dump..."
|
||||||
|
$objectList = $Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
||||||
|
|
||||||
|
$listFile = Join-Path $tempDir "dump_list.txt"
|
||||||
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
[System.IO.File]::WriteAllLines($listFile, $objectList, $utf8Bom)
|
||||||
|
|
||||||
|
$arguments += "-listFile", "`"$listFile`""
|
||||||
|
Write-Host "Objects to dump: $($objectList.Count)"
|
||||||
|
foreach ($obj in $objectList) { Write-Host " $obj" }
|
||||||
|
}
|
||||||
|
"UpdateInfo" {
|
||||||
|
Write-Host "Updating ConfigDumpInfo.xml..."
|
||||||
|
$arguments += "-configDumpInfoOnly"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Extensions ---
|
||||||
|
if ($Extension) {
|
||||||
|
$arguments += "-Extension", "`"$Extension`""
|
||||||
|
} elseif ($AllExtensions) {
|
||||||
|
$arguments += "-AllExtensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "dump_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Dump completed successfully" -ForegroundColor Green
|
||||||
|
Write-Host "Configuration dumped to: $ConfigDir"
|
||||||
|
} else {
|
||||||
|
Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
-8
@@ -1,34 +1,68 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-dump-xml v1.0 — Dump 1C configuration to XML files
|
# db-dump-xml v1.6 — Dump 1C configuration to XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
candidates = glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
v8path = _find_project_v8path()
|
||||||
|
if not v8path:
|
||||||
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates.sort()
|
v8path = max(candidates, key=_version_key)
|
||||||
return candidates[-1]
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return v8path
|
return v8path
|
||||||
|
|
||||||
|
|
||||||
@@ -65,9 +99,14 @@ def main():
|
|||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Validate connection ---
|
# --- Validate connection ---
|
||||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -81,6 +120,46 @@ def main():
|
|||||||
os.makedirs(args.ConfigDir, exist_ok=True)
|
os.makedirs(args.ConfigDir, exist_ok=True)
|
||||||
print(f"Created output directory: {args.ConfigDir}")
|
print(f"Created output directory: {args.ConfigDir}")
|
||||||
|
|
||||||
|
# --- ibcmd branch (file infobase only; hierarchical Full/Changes) ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if args.Format == "Plain":
|
||||||
|
print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if args.AllExtensions:
|
||||||
|
arguments = ["infobase", "config", "export", "all-extensions", args.ConfigDir, f"--db-path={args.InfoBasePath}"]
|
||||||
|
elif args.Mode == "UpdateInfo":
|
||||||
|
print("Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif args.Mode == "Partial":
|
||||||
|
obj_list = [o.strip() for o in args.Objects.split(",") if o.strip()]
|
||||||
|
arguments = ["infobase", "config", "export", "objects"] + obj_list
|
||||||
|
arguments += [f"--out={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if args.Extension:
|
||||||
|
arguments.append(f"--extension={args.Extension}")
|
||||||
|
else:
|
||||||
|
arguments = ["infobase", "config", "export", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if args.Extension:
|
||||||
|
arguments.append(f"--extension={args.Extension}")
|
||||||
|
arguments.append(args.ConfigDir)
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"Configuration exported successfully to: {args.ConfigDir}")
|
||||||
|
else:
|
||||||
|
print(f"Error exporting configuration (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
# --- Temp dir ---
|
# --- Temp dir ---
|
||||||
temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_xml_{random.randint(0, 999999)}")
|
temp_dir = os.path.join(tempfile.gettempdir(), f"db_dump_xml_{random.randint(0, 999999)}")
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
@@ -1,158 +1,158 @@
|
|||||||
---
|
---
|
||||||
name: db-list
|
name: db-list
|
||||||
description: Управление реестром баз данных 1С (.v8-project.json). Используй когда нужно работать с реестром баз — список баз, зарегистрировать базу в реестре, какие базы есть
|
description: Управление реестром баз данных 1С (.v8-project.json). Используй когда нужно работать с реестром баз — список баз, зарегистрировать базу в реестре, какие базы есть
|
||||||
argument-hint: "[add|remove|show]"
|
argument-hint: "[add|remove|show]"
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Read
|
- Read
|
||||||
- Write
|
- Write
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-list — Управление реестром баз данных
|
# /db-list — Управление реестром баз данных
|
||||||
|
|
||||||
Управляет файлом `.v8-project.json` — реестром информационных баз проекта. Файл хранит параметры подключения, алиасы, привязку к веткам Git.
|
Управляет файлом `.v8-project.json` — реестром информационных баз проекта. Файл хранит параметры подключения, алиасы, привязку к веткам Git.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-list — показать список баз
|
/db-list — показать список баз
|
||||||
/db-list add — добавить базу (интерактивно)
|
/db-list add — добавить базу (интерактивно)
|
||||||
/db-list remove <id> — удалить базу из реестра
|
/db-list remove <id> — удалить базу из реестра
|
||||||
/db-list show <id|alias> — подробности по базе
|
/db-list show <id|alias> — подробности по базе
|
||||||
```
|
```
|
||||||
|
|
||||||
## Формат `.v8-project.json`
|
## Формат `.v8-project.json`
|
||||||
|
|
||||||
Файл размещается в корне проекта (рядом с `.git/`).
|
Файл размещается в корне проекта (рядом с `.git/`).
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"v8path": "C:\\Program Files\\1cv8\\8.3.25.1257\\bin",
|
"v8path": "C:\\Program Files\\1cv8\\8.3.25.1257\\bin",
|
||||||
"databases": [
|
"databases": [
|
||||||
{
|
{
|
||||||
"id": "dev",
|
"id": "dev",
|
||||||
"name": "Разработка",
|
"name": "Разработка",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"path": "C:\\Bases\\MyApp_Dev",
|
"path": "C:\\Bases\\MyApp_Dev",
|
||||||
"user": "Admin",
|
"user": "Admin",
|
||||||
"password": "",
|
"password": "",
|
||||||
"aliases": ["dev", "разработка"],
|
"aliases": ["dev", "разработка"],
|
||||||
"branches": ["dev", "develop", "feature/*"],
|
"branches": ["dev", "develop", "feature/*"],
|
||||||
"configSrc": "C:\\WS\\myapp\\cfsrc"
|
"configSrc": "C:\\WS\\myapp\\cfsrc"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "test",
|
"id": "test",
|
||||||
"name": "Тестовая",
|
"name": "Тестовая",
|
||||||
"type": "server",
|
"type": "server",
|
||||||
"server": "srv01",
|
"server": "srv01",
|
||||||
"ref": "MyApp_Test",
|
"ref": "MyApp_Test",
|
||||||
"user": "Admin",
|
"user": "Admin",
|
||||||
"password": "123",
|
"password": "123",
|
||||||
"aliases": ["test", "тест"]
|
"aliases": ["test", "тест"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default": "dev"
|
"default": "dev"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Поля корневого объекта
|
### Поля корневого объекта
|
||||||
|
|
||||||
| Поле | Тип | Описание |
|
| Поле | Тип | Описание |
|
||||||
|------|-----|----------|
|
|------|-----|----------|
|
||||||
| `v8path` | string | Каталог bin платформы 1С. Необязательный — если не задан, автоопределение |
|
| `v8path` | string | Каталог bin платформы 1С. Необязательный — если не задан, автоопределение |
|
||||||
| `databases` | array | Массив баз данных |
|
| `databases` | array | Массив баз данных |
|
||||||
| `default` | string | id базы по умолчанию |
|
| `default` | string | id базы по умолчанию |
|
||||||
|
|
||||||
### Поля объекта базы данных
|
### Поля объекта базы данных
|
||||||
|
|
||||||
| Поле | Тип | Обязательное | Описание |
|
| Поле | Тип | Обязательное | Описание |
|
||||||
|------|-----|:------------:|----------|
|
|------|-----|:------------:|----------|
|
||||||
| `id` | string | да | Уникальный идентификатор (латиница, без пробелов) |
|
| `id` | string | да | Уникальный идентификатор (латиница, без пробелов) |
|
||||||
| `name` | string | да | Человекочитаемое имя |
|
| `name` | string | да | Человекочитаемое имя |
|
||||||
| `type` | `"file"` / `"server"` | да | Тип подключения |
|
| `type` | `"file"` / `"server"` | да | Тип подключения |
|
||||||
| `path` | string | для file | Путь к каталогу файловой базы |
|
| `path` | string | для file | Путь к каталогу файловой базы |
|
||||||
| `server` | string | для server | Адрес сервера 1С |
|
| `server` | string | для server | Адрес сервера 1С |
|
||||||
| `ref` | string | для server | Имя базы на сервере |
|
| `ref` | string | для server | Имя базы на сервере |
|
||||||
| `user` | string | нет | Имя пользователя 1С |
|
| `user` | string | нет | Имя пользователя 1С |
|
||||||
| `password` | string | нет | Пароль |
|
| `password` | string | нет | Пароль |
|
||||||
| `aliases` | string[] | нет | Альтернативные имена для быстрого доступа |
|
| `aliases` | string[] | нет | Альтернативные имена для быстрого доступа |
|
||||||
| `branches` | string[] | нет | Git-ветки или glob-паттерны (`release/*`, `feature/*`), привязанные к этой базе |
|
| `branches` | string[] | нет | Git-ветки или glob-паттерны (`release/*`, `feature/*`), привязанные к этой базе |
|
||||||
| `configSrc` | string | нет | Каталог XML-выгрузки конфигурации |
|
| `configSrc` | string | нет | Каталог XML-выгрузки конфигурации |
|
||||||
|
|
||||||
## Алгоритм разрешения базы данных
|
## Алгоритм разрешения базы данных
|
||||||
|
|
||||||
Этот алгоритм используется ВСЕМИ навыками (`db-*`, `epf-build`, `epf-dump`, `erf-build`, `erf-dump`) для определения целевой базы.
|
Этот алгоритм используется ВСЕМИ навыками (`db-*`, `epf-build`, `epf-dump`, `erf-build`, `erf-dump`) для определения целевой базы.
|
||||||
|
|
||||||
1. Если пользователь указал **параметры подключения** (путь, сервер) — используй напрямую
|
1. Если пользователь указал **параметры подключения** (путь, сервер) — используй напрямую
|
||||||
2. Если пользователь указал **базу по имени** — ищи совпадение в таком порядке:
|
2. Если пользователь указал **базу по имени** — ищи совпадение в таком порядке:
|
||||||
1. По `id` (точное совпадение)
|
1. По `id` (точное совпадение)
|
||||||
2. По `aliases` (совпадение в массиве с учётом морфологии: «тестовую» = «тестовая» = «тестовой»)
|
2. По `aliases` (совпадение в массиве с учётом морфологии: «тестовую» = «тестовая» = «тестовой»)
|
||||||
3. По `name` (нечёткое совпадение с учётом морфологии и регистра)
|
3. По `name` (нечёткое совпадение с учётом морфологии и регистра)
|
||||||
3. Если пользователь **не указал** базу — сопоставь текущую ветку Git с `databases[].branches`:
|
3. Если пользователь **не указал** базу — сопоставь текущую ветку Git с `databases[].branches`:
|
||||||
- Точное совпадение: ветка `dev` → `"branches": ["dev"]`
|
- Точное совпадение: ветка `dev` → `"branches": ["dev"]`
|
||||||
- Glob-паттерн: ветка `release/2.1` → `"branches": ["release/*"]`
|
- Glob-паттерн: ветка `release/2.1` → `"branches": ["release/*"]`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
5. Если не найдено или неоднозначно — спроси пользователя
|
5. Если не найдено или неоднозначно — спроси пользователя
|
||||||
6. Если файл `.v8-project.json` не найден — спроси параметры подключения и предложи создать файл
|
6. Если файл `.v8-project.json` не найден — спроси параметры подключения и предложи создать файл
|
||||||
|
|
||||||
После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`.
|
После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
### Автоопределение платформы
|
### Автоопределение платформы
|
||||||
|
|
||||||
Если `v8path` не задан в конфиге:
|
Если `v8path` не задан в конфиге:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$v8 = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort-Object -Descending | Select-Object -First 1
|
$v8 = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort-Object -Descending | Select-Object -First 1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Операции
|
## Операции
|
||||||
|
|
||||||
### Показать список баз
|
### Показать список баз
|
||||||
|
|
||||||
Прочитай `.v8-project.json`, выведи таблицу:
|
Прочитай `.v8-project.json`, выведи таблицу:
|
||||||
|
|
||||||
```
|
```
|
||||||
ID Имя Тип Путь/Сервер По умолч.
|
ID Имя Тип Путь/Сервер По умолч.
|
||||||
dev Разработка file C:\Bases\MyApp_Dev ✓
|
dev Разработка file C:\Bases\MyApp_Dev ✓
|
||||||
test Тестовая server srv01/MyApp_Test
|
test Тестовая server srv01/MyApp_Test
|
||||||
```
|
```
|
||||||
|
|
||||||
### Добавить базу
|
### Добавить базу
|
||||||
|
|
||||||
Спроси у пользователя через AskUserQuestion:
|
Спроси у пользователя через AskUserQuestion:
|
||||||
- id, name, type (file/server)
|
- id, name, type (file/server)
|
||||||
- path (для file) или server + ref (для server)
|
- path (для file) или server + ref (для server)
|
||||||
- user, password (необязательно)
|
- user, password (необязательно)
|
||||||
- aliases, branches (необязательно)
|
- aliases, branches (необязательно)
|
||||||
|
|
||||||
Добавь в массив `databases`. Если это первая база — установи как `default`.
|
Добавь в массив `databases`. Если это первая база — установи как `default`.
|
||||||
|
|
||||||
### Удалить базу
|
### Удалить базу
|
||||||
|
|
||||||
Удали из массива `databases` по id. Если удаляемая была `default` — спросить новый default.
|
Удали из массива `databases` по id. Если удаляемая была `default` — спросить новый default.
|
||||||
|
|
||||||
### Подробности по базе
|
### Подробности по базе
|
||||||
|
|
||||||
Выведи все поля конкретной базы.
|
Выведи все поля конкретной базы.
|
||||||
|
|
||||||
## Формирование строки подключения
|
## Формирование строки подключения
|
||||||
|
|
||||||
Для использования в шаблонах команд других навыков:
|
Для использования в шаблонах команд других навыков:
|
||||||
|
|
||||||
**Файловая база:**
|
**Файловая база:**
|
||||||
```
|
```
|
||||||
/F "<path>"
|
/F "<path>"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Серверная база:**
|
**Серверная база:**
|
||||||
```
|
```
|
||||||
/S "<server>/<ref>"
|
/S "<server>/<ref>"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Аутентификация** (добавляется если user задан):
|
**Аутентификация** (добавляется если user задан):
|
||||||
```
|
```
|
||||||
/N"<user>" /P"<password>"
|
/N"<user>" /P"<password>"
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Важно**: между `/N` и именем пробела нет. Между `/P` и паролем пробела нет. Если пароль пустой — опусти `/P` целиком.
|
> **Важно**: между `/N` и именем пробела нет. Между `/P` и паролем пробела нет. Если пароль пустой — опусти `/P` целиком.
|
||||||
@@ -1,81 +1,73 @@
|
|||||||
---
|
---
|
||||||
name: db-load-cf
|
name: db-load-cf
|
||||||
description: Загрузка конфигурации 1С из CF-файла. Используй когда нужно загрузить конфигурацию из CF, восстановить из бэкапа CF
|
description: Загрузка конфигурации 1С из CF-файла. Используй когда нужно загрузить конфигурацию из CF, восстановить из бэкапа CF
|
||||||
argument-hint: <input.cf> [database]
|
argument-hint: <input.cf> [database]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-load-cf — Загрузка конфигурации из CF-файла
|
# /db-load-cf — Загрузка конфигурации из CF-файла
|
||||||
|
|
||||||
Загружает конфигурацию из бинарного CF-файла в информационную базу.
|
Загружает конфигурацию из бинарного CF-файла в информационную базу.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-load-cf <input.cf> [database]
|
/db-load-cf <input.cf> [database]
|
||||||
/db-load-cf config.cf dev
|
/db-load-cf config.cf dev
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Внимание**: загрузка CF **полностью заменяет** конфигурацию в базе. Перед выполнением запроси подтверждение у пользователя.
|
> **Внимание**: загрузка CF **полностью заменяет** конфигурацию в базе. Перед выполнением запроси подтверждение у пользователя.
|
||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-cf/scripts/db-load-cf.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-InputFile <путь>` | да | Путь к CF-файлу |
|
| `-InputFile <путь>` | да | Путь к CF-файлу |
|
||||||
| `-Extension <имя>` | нет | Загрузить как расширение |
|
| `-Extension <имя>` | нет | Загрузить как расширение |
|
||||||
| `-AllExtensions` | нет | Загрузить все расширения из архива |
|
| `-AllExtensions` | нет | Загрузить все расширения из архива |
|
||||||
|
|
||||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
## Коды возврата
|
## После выполнения
|
||||||
|
|
||||||
| Код | Описание |
|
**Предложи выполнить `/db-update`** — загрузка CF обновляет только «основную» конфигурацию конфигуратора, для применения к БД нужен `/UpdateDBCfg`
|
||||||
|-----|----------|
|
|
||||||
| 0 | Успешно |
|
## Примеры
|
||||||
| 1 | Ошибка (см. лог) |
|
|
||||||
|
```powershell
|
||||||
## После выполнения
|
# Файловая база
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-cf/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf"
|
||||||
1. Прочитай лог-файл и покажи результат
|
|
||||||
2. **Предложи выполнить `/db-update`** — загрузка CF обновляет только «основную» конфигурацию конфигуратора, для применения к БД нужен `/UpdateDBCfg`
|
# Серверная база
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-cf/scripts/db-load-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf"
|
||||||
## Примеры
|
|
||||||
|
# Загрузка расширения
|
||||||
```powershell
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-cf/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение"
|
||||||
# Файловая база
|
```
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\config.cf"
|
|
||||||
|
|
||||||
# Серверная база
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "config.cf"
|
|
||||||
|
|
||||||
# Загрузка расширения
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-cf.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "ext.cfe" -Extension "МоёРасширение"
|
|
||||||
```
|
|
||||||
+224
-166
@@ -1,166 +1,224 @@
|
|||||||
# db-load-cf v1.0 — Load 1C configuration from CF file
|
# db-load-cf v1.4 — Load 1C configuration from CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Загрузка конфигурации 1С из CF-файла
|
Загрузка конфигурации 1С из CF-файла
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Загружает конфигурацию из бинарного CF-файла в информационную базу.
|
Загружает конфигурацию из бинарного CF-файла в информационную базу.
|
||||||
Поддерживает загрузку расширений.
|
Поддерживает загрузку расширений.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER InputFile
|
.PARAMETER InputFile
|
||||||
Путь к CF-файлу для загрузки
|
Путь к CF-файлу для загрузки
|
||||||
|
|
||||||
.PARAMETER Extension
|
.PARAMETER Extension
|
||||||
Загрузить как расширение
|
Загрузить как расширение
|
||||||
|
|
||||||
.PARAMETER AllExtensions
|
.PARAMETER AllExtensions
|
||||||
Загрузить все расширения из архива
|
Загрузить все расширения из архива
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "config.cf"
|
.\db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "config.cf"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "ext.cfe" -Extension "МоёРасширение"
|
.\db-load-cf.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "ext.cfe" -Extension "МоёРасширение"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$InputFile,
|
[string]$InputFile,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Extension,
|
[string]$Extension,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$AllExtensions
|
[switch]$AllExtensions
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Validate connection ---
|
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
if (-not $V8Path) {
|
||||||
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
$V8Path = Find-ProjectV8Path
|
||||||
exit 1
|
}
|
||||||
}
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
# --- Validate input file ---
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
if (-not (Test-Path $InputFile)) {
|
Select-Object -First 1
|
||||||
Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red
|
if ($found) {
|
||||||
exit 1
|
$V8Path = $found.FullName
|
||||||
}
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
# --- Temp dir ---
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
$tempDir = Join-Path $env:TEMP "db_load_cf_$(Get-Random)"
|
exit 1
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
}
|
||||||
|
}
|
||||||
try {
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
# --- Build arguments ---
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
$arguments = @("DESIGNER")
|
}
|
||||||
|
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
if (-not (Test-Path $V8Path)) {
|
||||||
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
} else {
|
exit 1
|
||||||
$arguments += "/F", "`"$InfoBasePath`""
|
}
|
||||||
}
|
|
||||||
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
if ($Password) { $arguments += "/P`"$Password`"" }
|
|
||||||
|
# --- Validate connection ---
|
||||||
$arguments += "/LoadCfg", "`"$InputFile`""
|
if ($engine -eq "ibcmd") {
|
||||||
|
if (-not $InfoBasePath) {
|
||||||
# --- Extensions ---
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
if ($Extension) {
|
exit 1
|
||||||
$arguments += "-Extension", "`"$Extension`""
|
}
|
||||||
} elseif ($AllExtensions) {
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
$arguments += "-AllExtensions"
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
}
|
exit 1
|
||||||
|
}
|
||||||
# --- Output ---
|
|
||||||
$outFile = Join-Path $tempDir "load_cf_log.txt"
|
# --- Validate input file ---
|
||||||
$arguments += "/Out", "`"$outFile`""
|
if (-not (Test-Path $InputFile)) {
|
||||||
$arguments += "/DisableStartupDialogs"
|
Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
# --- Execute ---
|
}
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
# --- Temp dir ---
|
||||||
$exitCode = $process.ExitCode
|
$tempDir = Join-Path $env:TEMP "db_load_cf_$(Get-Random)"
|
||||||
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
# --- Result ---
|
|
||||||
if ($exitCode -eq 0) {
|
try {
|
||||||
Write-Host "Configuration loaded successfully from: $InputFile" -ForegroundColor Green
|
if ($engine -eq "ibcmd") {
|
||||||
} else {
|
# --- ibcmd branch (file infobase only) ---
|
||||||
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
if ($AllExtensions) {
|
||||||
}
|
Write-Host "Error: ibcmd config load does not support -AllExtensions (use -Extension)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
if (Test-Path $outFile) {
|
}
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
$arguments = @("infobase", "config", "load", "--db-path=$InfoBasePath")
|
||||||
if ($logContent) {
|
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||||
Write-Host "--- Log ---"
|
$arguments += "$InputFile"
|
||||||
Write-Host $logContent
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
Write-Host "--- End ---"
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
}
|
$arguments += "--data=$tempDir"
|
||||||
}
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
|
$output = & $V8Path @arguments 2>&1
|
||||||
exit $exitCode
|
$exitCode = $LASTEXITCODE
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
} finally {
|
Write-Host "Configuration loaded successfully from: $InputFile" -ForegroundColor Green
|
||||||
if (Test-Path $tempDir) {
|
} else {
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
}
|
}
|
||||||
}
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
|
exit $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1cv8 branch ---
|
||||||
|
# --- Build arguments ---
|
||||||
|
$arguments = @("DESIGNER")
|
||||||
|
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
|
|
||||||
|
$arguments += "/LoadCfg", "`"$InputFile`""
|
||||||
|
|
||||||
|
# --- Extensions ---
|
||||||
|
if ($Extension) {
|
||||||
|
$arguments += "-Extension", "`"$Extension`""
|
||||||
|
} elseif ($AllExtensions) {
|
||||||
|
$arguments += "-AllExtensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "load_cf_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Configuration loaded successfully from: $InputFile" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
-7
@@ -1,29 +1,65 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-cf v1.0 — Load 1C configuration from CF file
|
# db-load-cf v1.4 — Load 1C configuration from CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
found = sorted(glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe"))
|
v8path = _find_project_v8path()
|
||||||
if found:
|
if not v8path:
|
||||||
return found[-1]
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
|
if candidates:
|
||||||
|
v8path = max(candidates, key=_version_key)
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -49,9 +85,14 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Validate connection ---
|
# --- Validate connection ---
|
||||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -60,6 +101,34 @@ def main():
|
|||||||
print(f"Error: input file not found: {args.InputFile}", file=sys.stderr)
|
print(f"Error: input file not found: {args.InputFile}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# --- ibcmd branch (file infobase only) ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if args.AllExtensions:
|
||||||
|
print("Error: ibcmd config load does not support -AllExtensions (use -Extension)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
arguments = ["infobase", "config", "load", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if args.Extension:
|
||||||
|
arguments.append(f"--extension={args.Extension}")
|
||||||
|
arguments.append(args.InputFile)
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"Configuration loaded successfully from: {args.InputFile}")
|
||||||
|
else:
|
||||||
|
print(f"Error loading configuration (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
# --- Temp dir ---
|
# --- Temp dir ---
|
||||||
temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_cf_{random.randint(0, 999999)}")
|
temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_cf_{random.randint(0, 999999)}")
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
name: db-load-dt
|
||||||
|
description: Загрузка информационной базы 1С из DT-файла — полная перезапись базы (конфигурация + данные). Используй когда нужно загрузить архив информационной базы, восстановить базу, загрузить dt
|
||||||
|
disable-model-invocation: true
|
||||||
|
argument-hint: <input.dt> [database]
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
- Glob
|
||||||
|
- AskUserQuestion
|
||||||
|
---
|
||||||
|
|
||||||
|
# /db-load-dt — Загрузка информационной базы из DT-файла
|
||||||
|
|
||||||
|
Восстанавливает информационную базу целиком (конфигурация **+ данные**) из DT-файла.
|
||||||
|
|
||||||
|
> ⚠️ **Необратимая операция.** Загрузка `.dt` **полностью перезаписывает базу** — и
|
||||||
|
> конфигурацию, и все данные. Текущее содержимое базы будет потеряно. После загрузки
|
||||||
|
> `/db-update` **не нужен** — конфигурация БД уже синхронна внутри снимка.
|
||||||
|
|
||||||
|
## Когда НЕ использовать
|
||||||
|
|
||||||
|
- Нужно создать **новую** базу из `.dt` → используй `/db-create` (из DT-шаблона), а не загрузку
|
||||||
|
в существующую.
|
||||||
|
- Нужно обновить только конфигурацию (без данных) → `/db-load-cf` или `/db-load-xml`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/db-load-dt <input.dt> [database]
|
||||||
|
/db-load-dt backup.dt dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Порядок действий перед загрузкой
|
||||||
|
|
||||||
|
1. Предложи пользователю сначала сделать `/db-dump-dt` текущего состояния базы — это точка
|
||||||
|
отката (восстановиться будет нечем, если не сохранить).
|
||||||
|
2. Запроси **явное подтверждение**: вся база (данные + конфигурация) будет перезаписана.
|
||||||
|
3. Только после подтверждения выполняй загрузку.
|
||||||
|
|
||||||
|
## Параметры подключения
|
||||||
|
|
||||||
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
|
4. Если ветка не совпала — используй `default`
|
||||||
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
|
Если файла нет — предложи `/db-list add`.
|
||||||
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
|
## Команда
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-dt/scripts/db-load-dt.ps1" <параметры>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Параметры скрипта
|
||||||
|
|
||||||
|
| Параметр | Обязательный | Описание |
|
||||||
|
|----------|:------------:|----------|
|
||||||
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
|
| `-InputFile <путь>` | да | Путь к DT-файлу |
|
||||||
|
| `-JobsCount <N>` | нет | Число фоновых заданий загрузки (0 = по числу процессоров) |
|
||||||
|
| `-UnlockCode <код>` | нет | Код разблокировки (`/UC`), если заблокировано начало сеансов |
|
||||||
|
|
||||||
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
|
## После выполнения
|
||||||
|
|
||||||
|
Если база занята (активные сеансы), загрузка не выполнится — для серверной базы можно
|
||||||
|
передать `-UnlockCode`; иначе освободи базу и повтори.
|
||||||
|
|
||||||
|
## Примеры
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Файловая база
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-dt/scripts/db-load-dt.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -InputFile "C:\backup\base.dt"
|
||||||
|
|
||||||
|
# Серверная база с ускорением загрузки
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-dt/scripts/db-load-dt.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Test" -UserName "Admin" -Password "secret" -InputFile "base.dt" -JobsCount 4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Связанные навыки
|
||||||
|
|
||||||
|
- `/db-dump-dt` — выгрузка ИБ в DT (обратная операция, точка отката перед загрузкой)
|
||||||
|
- `/db-create` — создать новую базу (в т.ч. из DT-шаблона)
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# db-load-dt v1.3 — Load 1C information base from DT file
|
||||||
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Загрузка информационной базы 1С из DT-файла
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Загружает информационную базу целиком (конфигурация + данные) из DT-файла.
|
||||||
|
ВНИМАНИЕ: операция полностью перезаписывает базу.
|
||||||
|
|
||||||
|
.PARAMETER V8Path
|
||||||
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
|
.PARAMETER InfoBasePath
|
||||||
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
|
.PARAMETER InfoBaseServer
|
||||||
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
|
.PARAMETER InfoBaseRef
|
||||||
|
Имя базы на сервере
|
||||||
|
|
||||||
|
.PARAMETER UserName
|
||||||
|
Имя пользователя 1С
|
||||||
|
|
||||||
|
.PARAMETER Password
|
||||||
|
Пароль пользователя
|
||||||
|
|
||||||
|
.PARAMETER InputFile
|
||||||
|
Путь к DT-файлу для загрузки
|
||||||
|
|
||||||
|
.PARAMETER JobsCount
|
||||||
|
Количество фоновых заданий для загрузки (0 = по числу процессоров)
|
||||||
|
|
||||||
|
.PARAMETER UnlockCode
|
||||||
|
Код разблокировки базы (/UC) — если заблокировано начало сеансов
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\db-load-dt.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "backup.dt"
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$V8Path,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$UserName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Password,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$InputFile,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[int]$JobsCount = 0,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$UnlockCode
|
||||||
|
)
|
||||||
|
|
||||||
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
# --- Resolve V8Path ---
|
||||||
|
function Find-ProjectV8Path {
|
||||||
|
$dir = (Get-Location).Path
|
||||||
|
while ($dir) {
|
||||||
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
|
if (Test-Path $pf) {
|
||||||
|
try {
|
||||||
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
|
} catch {}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
|
$dir = $parent
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $V8Path) {
|
||||||
|
$V8Path = Find-ProjectV8Path
|
||||||
|
}
|
||||||
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($found) {
|
||||||
|
$V8Path = $found.FullName
|
||||||
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $V8Path)) {
|
||||||
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
|
|
||||||
|
# --- Validate connection ---
|
||||||
|
if ($engine -eq "ibcmd") {
|
||||||
|
if (-not $InfoBasePath) {
|
||||||
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Validate input file ---
|
||||||
|
if (-not (Test-Path $InputFile)) {
|
||||||
|
Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Temp dir ---
|
||||||
|
$tempDir = Join-Path $env:TEMP "db_load_dt_$(Get-Random)"
|
||||||
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($engine -eq "ibcmd") {
|
||||||
|
# --- ibcmd branch (file infobase only) ---
|
||||||
|
$arguments = @("infobase", "restore", "--db-path=$InfoBasePath")
|
||||||
|
if (-not (Test-Path (Join-Path $InfoBasePath "1Cv8.1CD"))) { $arguments += "--create-database" }
|
||||||
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
|
$arguments += "$InputFile"
|
||||||
|
|
||||||
|
$arguments += "--data=$tempDir"
|
||||||
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
|
$output = & $V8Path @arguments 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Information base restored successfully from: $InputFile" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error restoring information base (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
|
exit $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1cv8 branch ---
|
||||||
|
# --- Build arguments ---
|
||||||
|
$arguments = @("DESIGNER")
|
||||||
|
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
|
if ($UnlockCode) { $arguments += "/UC`"$UnlockCode`"" }
|
||||||
|
|
||||||
|
$arguments += "/RestoreIB", "`"$InputFile`""
|
||||||
|
if ($JobsCount -gt 0) { $arguments += "-JobsCount", "$JobsCount" }
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "load_dt_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Information base restored successfully from: $InputFile" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error restoring information base (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# db-load-dt v1.3 — Load 1C information base from DT file
|
||||||
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import atexit
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_v8path(v8path):
|
||||||
|
"""Resolve path to 1cv8.exe."""
|
||||||
|
if not v8path:
|
||||||
|
v8path = _find_project_v8path()
|
||||||
|
if not v8path:
|
||||||
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
|
if candidates:
|
||||||
|
v8path = max(candidates, key=_version_key)
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
|
else:
|
||||||
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if os.path.isdir(v8path):
|
||||||
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
if not os.path.isfile(v8path):
|
||||||
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
return v8path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Load 1C information base from DT file",
|
||||||
|
allow_abbrev=False,
|
||||||
|
)
|
||||||
|
parser.add_argument("-V8Path", default="")
|
||||||
|
parser.add_argument("-InfoBasePath", default="")
|
||||||
|
parser.add_argument("-InfoBaseServer", default="")
|
||||||
|
parser.add_argument("-InfoBaseRef", default="")
|
||||||
|
parser.add_argument("-UserName", default="")
|
||||||
|
parser.add_argument("-Password", default="")
|
||||||
|
parser.add_argument("-InputFile", required=True)
|
||||||
|
parser.add_argument("-JobsCount", type=int, default=0)
|
||||||
|
parser.add_argument("-UnlockCode", default="")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
|
# --- Validate connection ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# --- Validate input file ---
|
||||||
|
if not os.path.isfile(args.InputFile):
|
||||||
|
print(f"Error: input file not found: {args.InputFile}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# --- ibcmd branch (file infobase only) ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
arguments = ["infobase", "restore", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if not os.path.isfile(os.path.join(args.InfoBasePath, "1Cv8.1CD")):
|
||||||
|
arguments.append("--create-database")
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(args.InputFile)
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"Information base restored successfully from: {args.InputFile}")
|
||||||
|
else:
|
||||||
|
print(f"Error restoring information base (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
|
# --- Temp dir ---
|
||||||
|
temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_dt_{random.randint(0, 999999)}")
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# --- Build arguments ---
|
||||||
|
arguments = ["DESIGNER"]
|
||||||
|
|
||||||
|
if args.InfoBaseServer and args.InfoBaseRef:
|
||||||
|
arguments.extend(["/S", f"{args.InfoBaseServer}/{args.InfoBaseRef}"])
|
||||||
|
else:
|
||||||
|
arguments.extend(["/F", args.InfoBasePath])
|
||||||
|
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"/N{args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"/P{args.Password}")
|
||||||
|
if args.UnlockCode:
|
||||||
|
arguments.append(f"/UC{args.UnlockCode}")
|
||||||
|
|
||||||
|
arguments.extend(["/RestoreIB", args.InputFile])
|
||||||
|
if args.JobsCount > 0:
|
||||||
|
arguments.extend(["-JobsCount", str(args.JobsCount)])
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
out_file = os.path.join(temp_dir, "load_dt_log.txt")
|
||||||
|
arguments.extend(["/Out", out_file])
|
||||||
|
arguments.append("/DisableStartupDialogs")
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
print(f"Running: 1cv8.exe {' '.join(arguments)}")
|
||||||
|
result = subprocess.run(
|
||||||
|
[v8path] + arguments,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
exit_code = result.returncode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if exit_code == 0:
|
||||||
|
print(f"Information base restored successfully from: {args.InputFile}")
|
||||||
|
else:
|
||||||
|
print(f"Error restoring information base (code: {exit_code})", file=sys.stderr)
|
||||||
|
|
||||||
|
if os.path.isfile(out_file):
|
||||||
|
try:
|
||||||
|
with open(out_file, "r", encoding="utf-8-sig") as f:
|
||||||
|
log_content = f.read()
|
||||||
|
if log_content:
|
||||||
|
print("--- Log ---")
|
||||||
|
print(log_content)
|
||||||
|
print("--- End ---")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if os.path.isdir(temp_dir):
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,78 +1,77 @@
|
|||||||
---
|
---
|
||||||
name: db-load-git
|
name: db-load-git
|
||||||
description: Загрузка изменений из Git в базу 1С. Используй когда нужно загрузить изменения из гита, обновить базу из репозитория, partial load из коммита
|
description: Загрузка изменений из Git в базу 1С. Используй когда нужно загрузить изменения из гита, обновить базу из репозитория, partial load из коммита
|
||||||
argument-hint: "[database] [source]"
|
argument-hint: "[database] [source]"
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-load-git — Загрузка изменений из Git
|
# /db-load-git — Загрузка изменений из Git
|
||||||
|
|
||||||
Определяет изменённые файлы конфигурации по данным Git и выполняет частичную загрузку в информационную базу.
|
Определяет изменённые файлы конфигурации по данным Git и выполняет частичную загрузку в информационную базу.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-load-git [database]
|
/db-load-git [database]
|
||||||
/db-load-git dev — все незафиксированные изменения
|
/db-load-git dev — все незафиксированные изменения
|
||||||
/db-load-git dev -Source Staged — только staged
|
/db-load-git dev -Source Staged — только staged
|
||||||
/db-load-git dev -Source Commit -CommitRange "HEAD~3..HEAD"
|
/db-load-git dev -Source Commit -CommitRange "HEAD~3..HEAD"
|
||||||
/db-load-git dev -DryRun — только показать что будет загружено
|
/db-load-git dev -DryRun — только показать что будет загружено
|
||||||
```
|
```
|
||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
Если в записи базы указан `configSrc` — используй как каталог конфигурации.
|
Если в записи базы указан `configSrc` — используй как каталог конфигурации.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-git/scripts/db-load-git.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-ConfigDir <путь>` | да | Каталог XML-выгрузки (git-репозиторий) |
|
| `-ConfigDir <путь>` | да | Каталог XML-выгрузки (git-репозиторий) |
|
||||||
| `-Source <источник>` | нет | `All` (по умолч.) / `Staged` / `Unstaged` / `Commit` |
|
| `-Source <источник>` | нет | `All` (по умолч.) / `Staged` / `Unstaged` / `Commit` |
|
||||||
| `-CommitRange <range>` | для Commit | Диапазон коммитов (напр. `HEAD~3..HEAD`) |
|
| `-CommitRange <range>` | для Commit | Диапазон коммитов (напр. `HEAD~3..HEAD`) |
|
||||||
| `-Extension <имя>` | нет | Загрузить в расширение |
|
| `-Extension <имя>` | нет | Загрузить в расширение |
|
||||||
| `-AllExtensions` | нет | Загрузить все расширения |
|
| `-AllExtensions` | нет | Загрузить все расширения |
|
||||||
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
||||||
| `-DryRun` | нет | Только показать что будет загружено (без загрузки) |
|
| `-DryRun` | нет | Только показать что будет загружено (без загрузки) |
|
||||||
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
|
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
|
||||||
|
|
||||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
## После выполнения
|
## После выполнения
|
||||||
|
|
||||||
1. Показать список загруженных файлов и результат из лога
|
Если `-UpdateDB` не был указан — **предложить `/db-update`** для применения изменений к БД
|
||||||
2. Если `-UpdateDB` не был указан — **предложить `/db-update`** для применения изменений к БД
|
|
||||||
|
## Примеры
|
||||||
## Примеры
|
|
||||||
|
```powershell
|
||||||
```powershell
|
# Все незафиксированные изменения
|
||||||
# Все незафиксированные изменения
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-git/scripts/db-load-git.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-git.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source All -UpdateDB
|
|
||||||
|
# Из диапазона коммитов
|
||||||
# Из диапазона коммитов
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-git/scripts/db-load-git.ps1" -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\WS\cfsrc" -Source Commit -CommitRange "HEAD~3..HEAD"
|
||||||
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"
|
```
|
||||||
```
|
|
||||||
+445
-359
@@ -1,359 +1,445 @@
|
|||||||
# db-load-git v1.3 — Load Git changes into 1C database
|
# db-load-git v1.8 — Load Git changes into 1C database
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Загрузка изменений из Git в базу 1С
|
Загрузка изменений из Git в базу 1С
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Определяет изменённые файлы конфигурации по данным Git и выполняет
|
Определяет изменённые файлы конфигурации по данным Git и выполняет
|
||||||
частичную загрузку в информационную базу.
|
частичную загрузку в информационную базу.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER ConfigDir
|
.PARAMETER ConfigDir
|
||||||
Каталог XML-выгрузки конфигурации (git-репозиторий)
|
Каталог XML-выгрузки конфигурации (git-репозиторий)
|
||||||
|
|
||||||
.PARAMETER Source
|
.PARAMETER Source
|
||||||
Источник изменений: All, Staged, Unstaged, Commit (по умолчанию All)
|
Источник изменений: All, Staged, Unstaged, Commit (по умолчанию All)
|
||||||
|
|
||||||
.PARAMETER CommitRange
|
.PARAMETER CommitRange
|
||||||
Диапазон коммитов (для Source=Commit), напр. HEAD~3..HEAD
|
Диапазон коммитов (для Source=Commit), напр. HEAD~3..HEAD
|
||||||
|
|
||||||
.PARAMETER Extension
|
.PARAMETER Extension
|
||||||
Имя расширения для загрузки
|
Имя расширения для загрузки
|
||||||
|
|
||||||
.PARAMETER AllExtensions
|
.PARAMETER AllExtensions
|
||||||
Загрузить все расширения
|
Загрузить все расширения
|
||||||
|
|
||||||
.PARAMETER Format
|
.PARAMETER Format
|
||||||
Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical)
|
Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical)
|
||||||
|
|
||||||
.PARAMETER DryRun
|
.PARAMETER DryRun
|
||||||
Только показать что будет загружено (без загрузки)
|
Только показать что будет загружено (без загрузки)
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Source All
|
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Source All
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Source Commit -CommitRange "HEAD~3..HEAD"
|
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Source Commit -CommitRange "HEAD~3..HEAD"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -DryRun
|
.\db-load-git.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -DryRun
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$ConfigDir,
|
[string]$ConfigDir,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[ValidateSet("All", "Staged", "Unstaged", "Commit")]
|
[ValidateSet("All", "Staged", "Unstaged", "Commit")]
|
||||||
[string]$Source = "All",
|
[string]$Source = "All",
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$CommitRange,
|
[string]$CommitRange,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Extension,
|
[string]$Extension,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$AllExtensions,
|
[switch]$AllExtensions,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[ValidateSet("Hierarchical", "Plain")]
|
[ValidateSet("Hierarchical", "Plain")]
|
||||||
[string]$Format = "Hierarchical",
|
[string]$Format = "Hierarchical",
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$DryRun,
|
[switch]$DryRun,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$UpdateDB
|
[switch]$UpdateDB
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Helper: map sub-file path (BSL, HTML, etc.) to object XML ---
|
# --- Helper: map sub-file path (BSL, HTML, etc.) to object XML ---
|
||||||
function Get-ObjectXmlFromSubFile {
|
function Get-ObjectXmlFromSubFile {
|
||||||
param([string]$RelativePath)
|
param([string]$RelativePath)
|
||||||
|
|
||||||
$parts = $RelativePath -split '[\\/]'
|
$parts = $RelativePath -split '[\\/]'
|
||||||
if ($parts.Count -ge 2) {
|
if ($parts.Count -ge 2) {
|
||||||
return "$($parts[0])/$($parts[1]).xml"
|
return "$($parts[0])/$($parts[1]).xml"
|
||||||
}
|
}
|
||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Resolve V8Path (skip if DryRun) ---
|
# --- Resolve V8Path (skip if DryRun) ---
|
||||||
if (-not $DryRun) {
|
if (-not $DryRun) {
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Validate connection (skip if DryRun) ---
|
if (-not $V8Path) {
|
||||||
if (-not $DryRun) {
|
$V8Path = Find-ProjectV8Path
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
}
|
||||||
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
if (-not $V8Path) {
|
||||||
exit 1
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
}
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
}
|
Select-Object -First 1
|
||||||
|
if ($found) {
|
||||||
# --- Validate config dir ---
|
$V8Path = $found.FullName
|
||||||
if (-not (Test-Path $ConfigDir)) {
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
Write-Host "Error: config directory not found: $ConfigDir" -ForegroundColor Red
|
} else {
|
||||||
exit 1
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
}
|
exit 1
|
||||||
|
}
|
||||||
# --- Validate Commit mode ---
|
}
|
||||||
if ($Source -eq "Commit" -and -not $CommitRange) {
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
Write-Host "Error: -CommitRange required for Source=Commit" -ForegroundColor Red
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
exit 1
|
}
|
||||||
}
|
|
||||||
|
if (-not (Test-Path $V8Path)) {
|
||||||
# --- Check git ---
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
try {
|
exit 1
|
||||||
$null = git --version 2>&1
|
}
|
||||||
} catch {
|
}
|
||||||
Write-Host "Error: git not found in PATH" -ForegroundColor Red
|
|
||||||
exit 1
|
# --- Detect engine + validate connection (skip if DryRun) ---
|
||||||
}
|
$engine = "1cv8"
|
||||||
|
if (-not $DryRun) {
|
||||||
# --- Get changed files from Git ---
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
$changedFiles = @()
|
if ($engine -eq "ibcmd") {
|
||||||
$ConfigDir = (Resolve-Path $ConfigDir).Path.TrimEnd('\')
|
if (-not $InfoBasePath) {
|
||||||
$configDirNormalized = $ConfigDir.Replace('\', '/')
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
Push-Location $ConfigDir
|
}
|
||||||
try {
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
switch ($Source) {
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
"Staged" {
|
exit 1
|
||||||
Write-Host "Getting staged changes..."
|
}
|
||||||
$raw = git diff --cached --name-only --relative 2>&1
|
}
|
||||||
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
|
||||||
}
|
# --- Validate config dir ---
|
||||||
"Unstaged" {
|
if (-not (Test-Path $ConfigDir)) {
|
||||||
Write-Host "Getting unstaged changes..."
|
Write-Host "Error: config directory not found: $ConfigDir" -ForegroundColor Red
|
||||||
$raw = git diff --name-only --relative 2>&1
|
exit 1
|
||||||
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
}
|
||||||
$raw = git ls-files --others --exclude-standard 2>&1
|
|
||||||
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
# --- Validate Commit mode ---
|
||||||
}
|
if ($Source -eq "Commit" -and -not $CommitRange) {
|
||||||
"Commit" {
|
Write-Host "Error: -CommitRange required for Source=Commit" -ForegroundColor Red
|
||||||
Write-Host "Getting changes from $CommitRange..."
|
exit 1
|
||||||
$raw = git diff --name-only --relative $CommitRange 2>&1
|
}
|
||||||
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
|
||||||
}
|
# --- Check git ---
|
||||||
"All" {
|
try {
|
||||||
Write-Host "Getting all uncommitted changes..."
|
$null = git --version 2>&1
|
||||||
$raw = git diff --cached --name-only --relative 2>&1
|
} catch {
|
||||||
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
Write-Host "Error: git not found in PATH" -ForegroundColor Red
|
||||||
$raw = git diff --name-only --relative 2>&1
|
exit 1
|
||||||
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
}
|
||||||
$raw = git ls-files --others --exclude-standard 2>&1
|
|
||||||
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
# --- Get changed files from Git ---
|
||||||
}
|
$changedFiles = @()
|
||||||
}
|
$ConfigDir = (Resolve-Path $ConfigDir).Path.TrimEnd('\')
|
||||||
} finally {
|
$configDirNormalized = $ConfigDir.Replace('\', '/')
|
||||||
Pop-Location
|
|
||||||
}
|
Push-Location $ConfigDir
|
||||||
|
try {
|
||||||
$changedFiles = $changedFiles | Where-Object { $_ -is [string] -and -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
|
switch ($Source) {
|
||||||
|
"Staged" {
|
||||||
if ($changedFiles.Count -eq 0) {
|
Write-Host "Getting staged changes..."
|
||||||
Write-Host "No changes found"
|
$raw = git diff --cached --name-only --relative 2>&1
|
||||||
exit 0
|
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
||||||
}
|
}
|
||||||
|
"Unstaged" {
|
||||||
Write-Host "Git changes detected: $($changedFiles.Count) files"
|
Write-Host "Getting unstaged changes..."
|
||||||
|
$raw = git diff --name-only --relative 2>&1
|
||||||
# --- Filter and map to config files ---
|
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
||||||
$configFiles = @()
|
$raw = git ls-files --others --exclude-standard 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
||||||
foreach ($file in $changedFiles) {
|
}
|
||||||
$file = $file.Trim().Replace('\', '/')
|
"Commit" {
|
||||||
if ([string]::IsNullOrWhiteSpace($file)) { continue }
|
Write-Host "Getting changes from $CommitRange..."
|
||||||
|
$raw = git diff --name-only --relative $CommitRange 2>&1
|
||||||
# Skip service files
|
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
||||||
if ($file -eq "ConfigDumpInfo.xml") { continue }
|
}
|
||||||
|
"All" {
|
||||||
$fullPath = Join-Path $ConfigDir $file
|
Write-Host "Getting all uncommitted changes..."
|
||||||
|
$raw = git diff --cached --name-only --relative 2>&1
|
||||||
if ($file -match '\.xml$') {
|
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
||||||
# XML file — add directly if exists
|
$raw = git diff --name-only --relative 2>&1
|
||||||
if (Test-Path $fullPath) {
|
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
||||||
if ($configFiles -notcontains $file) {
|
$raw = git ls-files --others --exclude-standard 2>&1
|
||||||
$configFiles += $file
|
if ($LASTEXITCODE -eq 0) { $changedFiles += $raw }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} finally {
|
||||||
else {
|
Pop-Location
|
||||||
# Non-XML (BSL, HTML, etc.) — map to parent object XML + include all Ext/ files
|
}
|
||||||
$objectXml = Get-ObjectXmlFromSubFile -RelativePath $file
|
|
||||||
if ($objectXml) {
|
$changedFiles = $changedFiles | Where-Object { $_ -is [string] -and -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
|
||||||
$fullXmlPath = Join-Path $ConfigDir $objectXml
|
|
||||||
if (Test-Path $fullXmlPath) {
|
if ($changedFiles.Count -eq 0) {
|
||||||
if ($configFiles -notcontains $objectXml) {
|
Write-Host "No changes found"
|
||||||
$configFiles += $objectXml
|
exit 0
|
||||||
}
|
}
|
||||||
if ((Test-Path $fullPath) -and $configFiles -notcontains $file) {
|
|
||||||
$configFiles += $file
|
Write-Host "Git changes detected: $($changedFiles.Count) files"
|
||||||
}
|
|
||||||
|
# --- Filter and map to config files ---
|
||||||
# Add all files from Ext/ directory of the object
|
$configFiles = @()
|
||||||
$parts = $file -split '[\\/]'
|
$supportSkipped = @()
|
||||||
if ($parts.Count -ge 2) {
|
|
||||||
$extDir = Join-Path (Join-Path $ConfigDir $parts[0]) "$($parts[1])\Ext"
|
foreach ($file in $changedFiles) {
|
||||||
if (Test-Path $extDir) {
|
$file = $file.Trim().Replace('\', '/')
|
||||||
Get-ChildItem -Path $extDir -Recurse -File | ForEach-Object {
|
if ([string]::IsNullOrWhiteSpace($file)) { continue }
|
||||||
$extRelPath = $_.FullName.Replace("$ConfigDir\", '').Replace('\', '/')
|
|
||||||
if ($configFiles -notcontains $extRelPath) {
|
# Skip service files (not partially loadable). Support-state files are tracked
|
||||||
$configFiles += $extRelPath
|
# to warn the user: support changes apply only via a full load.
|
||||||
}
|
if ($file -match 'ParentConfigurations\.bin$') { $supportSkipped += $file; continue }
|
||||||
}
|
if ($file -eq "ConfigDumpInfo.xml" -or $file -match '(^|/)ConfigDumpInfo\.xml$') { continue }
|
||||||
}
|
|
||||||
}
|
$fullPath = Join-Path $ConfigDir $file
|
||||||
}
|
|
||||||
}
|
if ($file -match '\.xml$') {
|
||||||
}
|
# XML file — add directly if exists
|
||||||
}
|
if (Test-Path $fullPath) {
|
||||||
|
if ($configFiles -notcontains $file) {
|
||||||
if ($configFiles.Count -eq 0) {
|
$configFiles += $file
|
||||||
Write-Host "No configuration files found in changes"
|
}
|
||||||
exit 0
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
Write-Host "Files for loading: $($configFiles.Count)"
|
# Non-XML (BSL, HTML, etc.) — map to parent object XML + include all Ext/ files
|
||||||
foreach ($f in $configFiles) { Write-Host " $f" }
|
$objectXml = Get-ObjectXmlFromSubFile -RelativePath $file
|
||||||
|
if ($objectXml) {
|
||||||
# --- DryRun: stop here ---
|
$fullXmlPath = Join-Path $ConfigDir $objectXml
|
||||||
if ($DryRun) {
|
if (Test-Path $fullXmlPath) {
|
||||||
Write-Host ""
|
if ($configFiles -notcontains $objectXml) {
|
||||||
Write-Host "DryRun mode - no changes applied"
|
$configFiles += $objectXml
|
||||||
exit 0
|
}
|
||||||
}
|
if ((Test-Path $fullPath) -and $configFiles -notcontains $file) {
|
||||||
|
$configFiles += $file
|
||||||
# --- Temp dir ---
|
}
|
||||||
$tempDir = Join-Path $env:TEMP "db_load_git_$(Get-Random)"
|
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
# Add all files from Ext/ directory of the object
|
||||||
|
$parts = $file -split '[\\/]'
|
||||||
try {
|
if ($parts.Count -ge 2) {
|
||||||
# --- Write list file (UTF-8 with BOM) ---
|
$extDir = Join-Path (Join-Path $ConfigDir $parts[0]) "$($parts[1])\Ext"
|
||||||
$listFile = Join-Path $tempDir "load_list.txt"
|
if (Test-Path $extDir) {
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
Get-ChildItem -Path $extDir -Recurse -File | ForEach-Object {
|
||||||
[System.IO.File]::WriteAllLines($listFile, $configFiles, $utf8Bom)
|
$extRelPath = $_.FullName.Replace("$ConfigDir\", '').Replace('\', '/')
|
||||||
|
if ($configFiles -notcontains $extRelPath) {
|
||||||
# --- Build arguments ---
|
$configFiles += $extRelPath
|
||||||
$arguments = @("DESIGNER")
|
}
|
||||||
|
}
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
}
|
||||||
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
}
|
||||||
} else {
|
}
|
||||||
$arguments += "/F", "`"$InfoBasePath`""
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
|
||||||
if ($Password) { $arguments += "/P`"$Password`"" }
|
if ($supportSkipped.Count -gt 0) {
|
||||||
|
Write-Host "[ВНИМАНИЕ] Состояние поддержки изменено в коммите, но частично не загружается (исключено):" -ForegroundColor Yellow
|
||||||
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
|
foreach ($sf in $supportSkipped) { Write-Host " - $sf" -ForegroundColor Yellow }
|
||||||
$arguments += "-listFile", "`"$listFile`""
|
Write-Host " Смена состояния поддержки применяется только полной загрузкой (db-load-xml -Mode Full)." -ForegroundColor Yellow
|
||||||
$arguments += "-Format", $Format
|
}
|
||||||
$arguments += "-partial"
|
|
||||||
$arguments += "-updateConfigDumpInfo"
|
if ($configFiles.Count -eq 0) {
|
||||||
|
Write-Host "No configuration files found in changes"
|
||||||
# --- Extensions ---
|
exit 0
|
||||||
if ($Extension) {
|
}
|
||||||
$arguments += "-Extension", "`"$Extension`""
|
|
||||||
} elseif ($AllExtensions) {
|
Write-Host "Files for loading: $($configFiles.Count)"
|
||||||
$arguments += "-AllExtensions"
|
foreach ($f in $configFiles) { Write-Host " $f" }
|
||||||
}
|
|
||||||
|
# --- DryRun: stop here ---
|
||||||
# --- UpdateDB ---
|
if ($DryRun) {
|
||||||
if ($UpdateDB) {
|
Write-Host ""
|
||||||
$arguments += "/UpdateDBCfg"
|
Write-Host "DryRun mode - no changes applied"
|
||||||
}
|
exit 0
|
||||||
|
}
|
||||||
# --- Output ---
|
|
||||||
$outFile = Join-Path $tempDir "load_log.txt"
|
# --- Temp dir ---
|
||||||
$arguments += "/Out", "`"$outFile`""
|
$tempDir = Join-Path $env:TEMP "db_load_git_$(Get-Random)"
|
||||||
$arguments += "/DisableStartupDialogs"
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
|
|
||||||
# --- Execute ---
|
try {
|
||||||
Write-Host ""
|
if ($engine -eq "ibcmd") {
|
||||||
Write-Host "Executing partial configuration load..."
|
# --- ibcmd branch (file infobase only; import specific files) ---
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
if ($Format -eq "Plain") {
|
||||||
|
Write-Host "Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
exit 1
|
||||||
$exitCode = $process.ExitCode
|
}
|
||||||
|
if ($AllExtensions) {
|
||||||
# --- Result ---
|
Write-Host "Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)" -ForegroundColor Red
|
||||||
Write-Host ""
|
exit 1
|
||||||
if ($exitCode -eq 0) {
|
}
|
||||||
Write-Host "Load completed successfully" -ForegroundColor Green
|
$arguments = @("infobase", "config", "import", "files") + $configFiles
|
||||||
} else {
|
$arguments += "--base-dir=$ConfigDir", "--db-path=$InfoBasePath"
|
||||||
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||||
}
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
if (Test-Path $outFile) {
|
$arguments += "--data=$tempDir"
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
if ($logContent) {
|
$output = & $V8Path @arguments 2>&1
|
||||||
Write-Host "--- Log ---"
|
$exitCode = $LASTEXITCODE
|
||||||
Write-Host $logContent
|
if ($exitCode -ne 0) {
|
||||||
Write-Host "--- End ---"
|
Write-Host "Error loading changes (code: $exitCode)" -ForegroundColor Red
|
||||||
}
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
}
|
exit $exitCode
|
||||||
|
}
|
||||||
exit $exitCode
|
Write-Host "Changes loaded successfully ($($configFiles.Count) files)" -ForegroundColor Green
|
||||||
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
} finally {
|
if ($UpdateDB) {
|
||||||
if (Test-Path $tempDir) {
|
$applyArgs = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force")
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
if ($UserName) { $applyArgs += "--user=$UserName" }
|
||||||
}
|
if ($Password) { $applyArgs += "--password=$Password" }
|
||||||
}
|
$applyArgs += "--data=$tempDir"
|
||||||
|
Write-Host "Running: ibcmd $($applyArgs -join ' ')"
|
||||||
|
$applyOut = & $V8Path @applyArgs 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Database configuration updated successfully" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
if ($applyOut) { Write-Host ($applyOut | Out-String) }
|
||||||
|
}
|
||||||
|
exit $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1cv8 branch ---
|
||||||
|
# --- Write list file (UTF-8 with BOM) ---
|
||||||
|
$listFile = Join-Path $tempDir "load_list.txt"
|
||||||
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
[System.IO.File]::WriteAllLines($listFile, $configFiles, $utf8Bom)
|
||||||
|
|
||||||
|
# --- Build arguments ---
|
||||||
|
$arguments = @("DESIGNER")
|
||||||
|
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
|
|
||||||
|
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
|
||||||
|
$arguments += "-listFile", "`"$listFile`""
|
||||||
|
$arguments += "-Format", $Format
|
||||||
|
$arguments += "-partial"
|
||||||
|
$arguments += "-updateConfigDumpInfo"
|
||||||
|
|
||||||
|
# --- Extensions ---
|
||||||
|
if ($Extension) {
|
||||||
|
$arguments += "-Extension", "`"$Extension`""
|
||||||
|
} elseif ($AllExtensions) {
|
||||||
|
$arguments += "-AllExtensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- UpdateDB ---
|
||||||
|
if ($UpdateDB) {
|
||||||
|
$arguments += "/UpdateDBCfg"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "load_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Executing partial configuration load..."
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
Write-Host ""
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Load completed successfully" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+113
-11
@@ -1,9 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-git v1.3 — Load Git changes into 1C database
|
# db-load-git v1.8 — Load Git changes into 1C database
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
@@ -13,23 +15,54 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
candidates = glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
v8path = _find_project_v8path()
|
||||||
|
if not v8path:
|
||||||
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates.sort()
|
v8path = max(candidates, key=_version_key)
|
||||||
return candidates[-1]
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return v8path
|
return v8path
|
||||||
|
|
||||||
|
|
||||||
@@ -93,9 +126,15 @@ def main():
|
|||||||
if not args.DryRun:
|
if not args.DryRun:
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
|
||||||
# --- Validate connection (skip if DryRun) ---
|
# --- Detect engine + validate connection (skip if DryRun) ---
|
||||||
|
engine = "1cv8"
|
||||||
if not args.DryRun:
|
if not args.DryRun:
|
||||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -146,14 +185,19 @@ def main():
|
|||||||
|
|
||||||
# --- Filter and map to config files ---
|
# --- Filter and map to config files ---
|
||||||
config_files = []
|
config_files = []
|
||||||
|
support_skipped = []
|
||||||
|
|
||||||
for file in changed_files:
|
for file in changed_files:
|
||||||
file = file.strip().replace("\\", "/")
|
file = file.strip().replace("\\", "/")
|
||||||
if not file:
|
if not file:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Skip service files
|
# Skip service files (not partially loadable). Support-state files are
|
||||||
if file == "ConfigDumpInfo.xml":
|
# tracked to warn: support changes apply only via a full load.
|
||||||
|
if file.endswith("ParentConfigurations.bin"):
|
||||||
|
support_skipped.append(file)
|
||||||
|
continue
|
||||||
|
if file == "ConfigDumpInfo.xml" or file.endswith("/ConfigDumpInfo.xml"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
full_path = os.path.join(args.ConfigDir, file)
|
full_path = os.path.join(args.ConfigDir, file)
|
||||||
@@ -186,6 +230,12 @@ def main():
|
|||||||
if rel_path not in config_files:
|
if rel_path not in config_files:
|
||||||
config_files.append(rel_path)
|
config_files.append(rel_path)
|
||||||
|
|
||||||
|
if support_skipped:
|
||||||
|
print("[ВНИМАНИЕ] Состояние поддержки изменено в коммите, но частично не загружается (исключено):", file=sys.stderr)
|
||||||
|
for sf in support_skipped:
|
||||||
|
print(f" - {sf}", file=sys.stderr)
|
||||||
|
print(" Смена состояния поддержки применяется только полной загрузкой (db-load-xml -Mode Full).", file=sys.stderr)
|
||||||
|
|
||||||
if len(config_files) == 0:
|
if len(config_files) == 0:
|
||||||
print("No configuration files found in changes")
|
print("No configuration files found in changes")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -205,6 +255,58 @@ def main():
|
|||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if engine == "ibcmd":
|
||||||
|
# --- ibcmd branch (file infobase only; import specific files) ---
|
||||||
|
if args.Format == "Plain":
|
||||||
|
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if args.AllExtensions:
|
||||||
|
print("Error: ibcmd config import does not support -AllExtensions (use -Extension or 1cv8)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
arguments = ["infobase", "config", "import", "files"] + config_files
|
||||||
|
arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if args.Extension:
|
||||||
|
arguments.append(f"--extension={args.Extension}")
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Error loading changes (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
print(f"Changes loaded successfully ({len(config_files)} files)")
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
exit_code = 0
|
||||||
|
if args.UpdateDB:
|
||||||
|
apply_args = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"]
|
||||||
|
if args.UserName:
|
||||||
|
apply_args.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
apply_args.append(f"--password={args.Password}")
|
||||||
|
apply_args.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(apply_args)}")
|
||||||
|
ar = subprocess.run([v8path] + apply_args, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
exit_code = ar.returncode
|
||||||
|
if exit_code == 0:
|
||||||
|
print("Database configuration updated successfully")
|
||||||
|
else:
|
||||||
|
print(f"Error updating database configuration (code: {exit_code})", file=sys.stderr)
|
||||||
|
if ar.stdout:
|
||||||
|
print(ar.stdout)
|
||||||
|
if ar.stderr:
|
||||||
|
print(ar.stderr, file=sys.stderr)
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
# --- Write list file (UTF-8 with BOM) ---
|
# --- Write list file (UTF-8 with BOM) ---
|
||||||
list_file = os.path.join(temp_dir, "load_list.txt")
|
list_file = os.path.join(temp_dir, "load_list.txt")
|
||||||
with open(list_file, "w", encoding="utf-8-sig") as f:
|
with open(list_file, "w", encoding="utf-8-sig") as f:
|
||||||
@@ -1,109 +1,101 @@
|
|||||||
---
|
---
|
||||||
name: db-load-xml
|
name: db-load-xml
|
||||||
description: Загрузка конфигурации 1С из XML-файлов. Используй когда нужно загрузить конфигурацию из файлов, XML, исходников, LoadConfigFromFiles
|
description: Загрузка конфигурации 1С из XML-файлов. Используй когда нужно загрузить конфигурацию из файлов, XML, исходников, LoadConfigFromFiles
|
||||||
argument-hint: <configDir> [database]
|
argument-hint: <configDir> [database]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-load-xml — Загрузка конфигурации из XML
|
# /db-load-xml — Загрузка конфигурации из XML
|
||||||
|
|
||||||
Загружает конфигурацию в информационную базу из XML-файлов (исходников). Поддерживает полную и частичную загрузку.
|
Загружает конфигурацию в информационную базу из XML-файлов (исходников). Поддерживает полную и частичную загрузку.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-load-xml <configDir> [database]
|
/db-load-xml <configDir> [database]
|
||||||
/db-load-xml src/config dev
|
/db-load-xml src/config dev
|
||||||
/db-load-xml src/config dev -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
/db-load-xml src/config dev -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Внимание**: полная загрузка **заменяет всю конфигурацию** в базе. Перед выполнением запроси подтверждение у пользователя.
|
> **Внимание**: полная загрузка **заменяет всю конфигурацию** в базе. Перед выполнением запроси подтверждение у пользователя.
|
||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
Если в записи базы указан `configSrc` — используй как каталог загрузки по умолчанию.
|
Если в записи базы указан `configSrc` — используй как каталог загрузки по умолчанию.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-ConfigDir <путь>` | да | Каталог XML-исходников |
|
| `-ConfigDir <путь>` | да | Каталог XML-исходников |
|
||||||
| `-Mode <режим>` | нет | `Full` (по умолч.) / `Partial` |
|
| `-Mode <режим>` | нет | `Full` (по умолч.) / `Partial` |
|
||||||
| `-Files <список>` | для Partial | Относительные пути файлов через запятую |
|
| `-Files <список>` | для Partial | Относительные пути файлов через запятую |
|
||||||
| `-ListFile <путь>` | для Partial | Путь к файлу со списком (альтернатива `-Files`) |
|
| `-ListFile <путь>` | для Partial | Путь к файлу со списком (альтернатива `-Files`) |
|
||||||
| `-Extension <имя>` | нет | Загрузить в расширение |
|
| `-Extension <имя>` | нет | Загрузить в расширение |
|
||||||
| `-AllExtensions` | нет | Загрузить все расширения |
|
| `-AllExtensions` | нет | Загрузить все расширения |
|
||||||
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
||||||
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
|
| `-UpdateDB` | нет | После загрузки сразу обновить конфигурацию БД (`/UpdateDBCfg`) |
|
||||||
|
|
||||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
### Режимы загрузки
|
### Режимы загрузки
|
||||||
|
|
||||||
| Режим | Описание |
|
| Режим | Описание |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| `Full` | Полная загрузка — замена всей конфигурации из каталога XML |
|
| `Full` | Полная загрузка — замена всей конфигурации из каталога XML |
|
||||||
| `Partial` | Частичная — загрузка выбранных файлов (с `-partial -updateConfigDumpInfo`) |
|
| `Partial` | Частичная — загрузка выбранных файлов (с `-partial -updateConfigDumpInfo`) |
|
||||||
|
|
||||||
### Формат файла списка (listFile)
|
### Формат файла списка (listFile)
|
||||||
|
|
||||||
Файл содержит **относительные пути к файлам** в каталоге выгрузки (один на строку), кодировка **UTF-8 с BOM**:
|
Файл содержит **относительные пути к файлам** в каталоге выгрузки (один на строку), кодировка **UTF-8 с BOM**:
|
||||||
|
|
||||||
```
|
```
|
||||||
Catalogs/Номенклатура.xml
|
Catalogs/Номенклатура.xml
|
||||||
Catalogs/Номенклатура/Ext/ObjectModule.bsl
|
Catalogs/Номенклатура/Ext/ObjectModule.bsl
|
||||||
Documents/Заказ.xml
|
Documents/Заказ.xml
|
||||||
Documents/Заказ/Forms/ФормаДокумента.xml
|
Documents/Заказ/Forms/ФормаДокумента.xml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Коды возврата
|
## После выполнения
|
||||||
|
|
||||||
| Код | Описание |
|
Если `-UpdateDB` не был указан — **предложи выполнить `/db-update`** для применения изменений к БД
|
||||||
|-----|----------|
|
|
||||||
| 0 | Успешно |
|
## Примеры
|
||||||
| 1 | Ошибка (см. лог) |
|
|
||||||
|
```powershell
|
||||||
## После выполнения
|
# Полная загрузка
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
||||||
1. Прочитай лог и покажи результат
|
|
||||||
2. Если `-UpdateDB` не был указан — **предложи выполнить `/db-update`** для применения изменений к БД
|
# Частичная загрузка конкретных файлов
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
||||||
## Примеры
|
|
||||||
|
# Загрузка расширения
|
||||||
```powershell
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
||||||
# Полная загрузка
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -V8Path "C:\Program Files\1cv8\8.3.25.1257\bin" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full
|
# Загрузка + обновление БД в одном запуске
|
||||||
|
powershell.exe -NoProfile -File ".opencode/skills/db-load-xml/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB
|
||||||
# Частичная загрузка конкретных файлов
|
```
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
|
||||||
|
|
||||||
# Загрузка расширения
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\ext_src" -Mode Full -Extension "МоёРасширение"
|
|
||||||
|
|
||||||
# Загрузка + обновление БД в одном запуске
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-load-xml.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -ConfigDir "C:\WS\cfsrc" -Mode Full -UpdateDB
|
|
||||||
```
|
|
||||||
+388
-279
@@ -1,279 +1,388 @@
|
|||||||
# db-load-xml v1.3 — Load 1C configuration from XML files
|
# db-load-xml v1.10 — Load 1C configuration from XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Загрузка конфигурации 1С из XML-файлов
|
Загрузка конфигурации 1С из XML-файлов
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Загружает конфигурацию в информационную базу из XML-файлов.
|
Загружает конфигурацию в информационную базу из XML-файлов.
|
||||||
Поддерживает полную и частичную загрузку.
|
Поддерживает полную и частичную загрузку.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER ConfigDir
|
.PARAMETER ConfigDir
|
||||||
Каталог XML-исходников конфигурации
|
Каталог XML-исходников конфигурации
|
||||||
|
|
||||||
.PARAMETER Mode
|
.PARAMETER Mode
|
||||||
Режим загрузки: Full или Partial (по умолчанию Full)
|
Режим загрузки: Full или Partial (по умолчанию Full)
|
||||||
|
|
||||||
.PARAMETER Files
|
.PARAMETER Files
|
||||||
Относительные пути файлов через запятую (для режима Partial)
|
Относительные пути файлов через запятую (для режима Partial)
|
||||||
|
|
||||||
.PARAMETER ListFile
|
.PARAMETER ListFile
|
||||||
Путь к файлу со списком файлов (альтернатива -Files, для режима Partial)
|
Путь к файлу со списком файлов (альтернатива -Files, для режима Partial)
|
||||||
|
|
||||||
.PARAMETER Extension
|
.PARAMETER Extension
|
||||||
Имя расширения для загрузки
|
Имя расширения для загрузки
|
||||||
|
|
||||||
.PARAMETER AllExtensions
|
.PARAMETER AllExtensions
|
||||||
Загрузить все расширения
|
Загрузить все расширения
|
||||||
|
|
||||||
.PARAMETER Format
|
.PARAMETER Format
|
||||||
Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical)
|
Формат файлов: Hierarchical или Plain (по умолчанию Hierarchical)
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Full
|
.\db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Full
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
.\db-load-xml.ps1 -InfoBasePath "C:\Bases\MyDB" -ConfigDir "C:\src" -Mode Partial -Files "Catalogs/Номенклатура.xml,Catalogs/Номенклатура/Ext/ObjectModule.bsl"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$ConfigDir,
|
[string]$ConfigDir,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[ValidateSet("Full", "Partial")]
|
[ValidateSet("Full", "Partial")]
|
||||||
[string]$Mode = "Full",
|
[string]$Mode = "Full",
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Files,
|
[string]$Files,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$ListFile,
|
[string]$ListFile,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Extension,
|
[string]$Extension,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$AllExtensions,
|
[switch]$AllExtensions,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[ValidateSet("Hierarchical", "Plain")]
|
[ValidateSet("Hierarchical", "Plain")]
|
||||||
[string]$Format = "Hierarchical",
|
[string]$Format = "Hierarchical",
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$UpdateDB,
|
[switch]$UpdateDB,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$StrictLog
|
[switch]$StrictLog
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Validate connection ---
|
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
if (-not $V8Path) {
|
||||||
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
$V8Path = Find-ProjectV8Path
|
||||||
exit 1
|
}
|
||||||
}
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
# --- Validate config dir ---
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
if (-not (Test-Path $ConfigDir)) {
|
Select-Object -First 1
|
||||||
Write-Host "Error: config directory not found: $ConfigDir" -ForegroundColor Red
|
if ($found) {
|
||||||
exit 1
|
$V8Path = $found.FullName
|
||||||
}
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
# --- Validate Partial mode ---
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
if ($Mode -eq "Partial" -and -not $Files -and -not $ListFile) {
|
exit 1
|
||||||
Write-Host "Error: -Files or -ListFile required for Partial mode" -ForegroundColor Red
|
}
|
||||||
exit 1
|
}
|
||||||
}
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
# --- Temp dir ---
|
}
|
||||||
$tempDir = Join-Path $env:TEMP "db_load_xml_$(Get-Random)"
|
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
if (-not (Test-Path $V8Path)) {
|
||||||
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
try {
|
exit 1
|
||||||
# --- Build arguments ---
|
}
|
||||||
$arguments = @("DESIGNER")
|
|
||||||
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
|
||||||
} else {
|
# --- Validate connection ---
|
||||||
$arguments += "/F", "`"$InfoBasePath`""
|
if ($engine -eq "ibcmd") {
|
||||||
}
|
if (-not $InfoBasePath) {
|
||||||
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
exit 1
|
||||||
if ($Password) { $arguments += "/P`"$Password`"" }
|
}
|
||||||
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
if ($Mode -eq "Full") {
|
}
|
||||||
Write-Host "Executing full configuration load..."
|
|
||||||
} else {
|
# --- Validate config dir ---
|
||||||
Write-Host "Executing partial configuration load..."
|
if (-not (Test-Path $ConfigDir)) {
|
||||||
|
Write-Host "Error: config directory not found: $ConfigDir" -ForegroundColor Red
|
||||||
# Build list file
|
exit 1
|
||||||
$generatedListFile = $null
|
}
|
||||||
if ($ListFile) {
|
|
||||||
# Use provided list file
|
# --- Validate Partial mode ---
|
||||||
if (-not (Test-Path $ListFile)) {
|
if ($Mode -eq "Partial" -and -not $Files -and -not $ListFile) {
|
||||||
Write-Host "Error: list file not found: $ListFile" -ForegroundColor Red
|
Write-Host "Error: -Files or -ListFile required for Partial mode" -ForegroundColor Red
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
$generatedListFile = $ListFile
|
|
||||||
} else {
|
# --- Temp dir ---
|
||||||
# Generate from -Files parameter
|
$tempDir = Join-Path $env:TEMP "db_load_xml_$(Get-Random)"
|
||||||
$fileList = $Files -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
$generatedListFile = Join-Path $tempDir "load_list.txt"
|
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
try {
|
||||||
[System.IO.File]::WriteAllLines($generatedListFile, $fileList, $utf8Bom)
|
if ($engine -eq "ibcmd") {
|
||||||
|
# --- ibcmd branch (file infobase only; hierarchical full-directory import) ---
|
||||||
Write-Host "Files to load: $($fileList.Count)"
|
if ($Format -eq "Plain") {
|
||||||
foreach ($f in $fileList) { Write-Host " $f" }
|
Write-Host "Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
|
||||||
}
|
exit 1
|
||||||
|
}
|
||||||
$arguments += "-listFile", "`"$generatedListFile`""
|
if ($AllExtensions) {
|
||||||
$arguments += "-partial"
|
$arguments = @("infobase", "config", "import", "all-extensions", "$ConfigDir", "--db-path=$InfoBasePath")
|
||||||
$arguments += "-updateConfigDumpInfo"
|
} elseif ($Mode -eq "Partial" -or $Files -or $ListFile) {
|
||||||
}
|
# partial: import specific files (relative to ConfigDir)
|
||||||
|
$fileList = @()
|
||||||
$arguments += "-Format", $Format
|
if ($ListFile) {
|
||||||
|
if (-not (Test-Path $ListFile)) {
|
||||||
# --- Extensions ---
|
Write-Host "Error: list file not found: $ListFile" -ForegroundColor Red
|
||||||
if ($Extension) {
|
exit 1
|
||||||
$arguments += "-Extension", "`"$Extension`""
|
}
|
||||||
} elseif ($AllExtensions) {
|
$fileList = @(Get-Content -Path $ListFile -Encoding UTF8 | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||||
$arguments += "-AllExtensions"
|
} elseif ($Files) {
|
||||||
}
|
$fileList = @($Files -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||||
|
}
|
||||||
# --- UpdateDB ---
|
if ($fileList.Count -eq 0) {
|
||||||
if ($UpdateDB) {
|
Write-Host "Error: -Files or -ListFile required for partial import" -ForegroundColor Red
|
||||||
$arguments += "/UpdateDBCfg"
|
exit 1
|
||||||
}
|
}
|
||||||
|
$arguments = @("infobase", "config", "import", "files") + $fileList
|
||||||
# --- Output ---
|
$arguments += "--base-dir=$ConfigDir", "--db-path=$InfoBasePath"
|
||||||
$outFile = Join-Path $tempDir "load_log.txt"
|
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||||
$arguments += "/Out", "`"$outFile`""
|
} else {
|
||||||
$arguments += "/DisableStartupDialogs"
|
$arguments = @("infobase", "config", "import", "--db-path=$InfoBasePath")
|
||||||
|
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||||
# --- Execute ---
|
$arguments += "$ConfigDir"
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
}
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
$exitCode = $process.ExitCode
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
|
$arguments += "--data=$tempDir"
|
||||||
# --- Read log ---
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
$logContent = $null
|
$output = & $V8Path @arguments 2>&1
|
||||||
if (Test-Path $outFile) {
|
$exitCode = $LASTEXITCODE
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
if ($exitCode -ne 0) {
|
||||||
}
|
Write-Host "Error loading configuration from files (code: $exitCode)" -ForegroundColor Red
|
||||||
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
# --- Scan log for silent rejections ---
|
exit $exitCode
|
||||||
# Platform often writes load-time rejections into /Out but exits with code 0.
|
}
|
||||||
# These patterns flag cases where metadata was dropped or rejected silently.
|
Write-Host "Configuration loaded successfully from: $ConfigDir" -ForegroundColor Green
|
||||||
$fatalLogPatterns = @(
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
'Неверное свойство объекта метаданных',
|
|
||||||
'не входит в состав объекта метаданных',
|
if ($UpdateDB) {
|
||||||
'Неизвестное имя типа',
|
$applyArgs = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force")
|
||||||
'Неизвестный объект метаданных',
|
if ($UserName) { $applyArgs += "--user=$UserName" }
|
||||||
'Ни один из документов не является регистратором для регистра',
|
if ($Password) { $applyArgs += "--password=$Password" }
|
||||||
'Неверное значение перечисления',
|
$applyArgs += "--data=$tempDir"
|
||||||
'не может быть приведен к типу'
|
Write-Host "Running: ibcmd $($applyArgs -join ' ')"
|
||||||
)
|
$applyOut = & $V8Path @applyArgs 2>&1
|
||||||
$silentFailures = @()
|
$exitCode = $LASTEXITCODE
|
||||||
if ($logContent) {
|
if ($exitCode -eq 0) {
|
||||||
foreach ($line in ($logContent -split "`r?`n")) {
|
Write-Host "Database configuration updated successfully" -ForegroundColor Green
|
||||||
foreach ($pat in $fatalLogPatterns) {
|
} else {
|
||||||
if ($line -match [regex]::Escape($pat)) {
|
Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
$silentFailures += $line.Trim()
|
}
|
||||||
break
|
if ($applyOut) { Write-Host ($applyOut | Out-String) }
|
||||||
}
|
}
|
||||||
}
|
exit $exitCode
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
# --- 1cv8 branch ---
|
||||||
# --- Result ---
|
# --- Build arguments ---
|
||||||
# Default: mirror platform's verdict via exit code. Log content (including any
|
$arguments = @("DESIGNER")
|
||||||
# rejection warnings) is always printed to stdout for visibility. With -StrictLog,
|
|
||||||
# elevate exit code to 1 when rejection patterns are found even if platform said 0.
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
if ($exitCode -eq 0) {
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
Write-Host "Load completed successfully" -ForegroundColor Green
|
} else {
|
||||||
} else {
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
}
|
||||||
}
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
if ($logContent) {
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
Write-Host "--- Log ---"
|
|
||||||
Write-Host $logContent
|
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
|
||||||
Write-Host "--- End ---"
|
|
||||||
}
|
if ($Mode -eq "Full") {
|
||||||
|
Write-Host "Executing full configuration load..."
|
||||||
if ($silentFailures.Count -gt 0) {
|
} else {
|
||||||
$msg = "[warning] log contains $($silentFailures.Count) rejection(s) — platform loaded config but dropped properties/refs"
|
Write-Host "Executing partial configuration load..."
|
||||||
if (-not $StrictLog) { $msg += " (pass -StrictLog to treat as error)" }
|
|
||||||
Write-Host $msg -ForegroundColor Yellow
|
# Build list file
|
||||||
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
|
$rawList = @()
|
||||||
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
|
if ($ListFile) {
|
||||||
}
|
if (-not (Test-Path $ListFile)) {
|
||||||
|
Write-Host "Error: list file not found: $ListFile" -ForegroundColor Red
|
||||||
exit $exitCode
|
exit 1
|
||||||
|
}
|
||||||
} finally {
|
$rawList = @(Get-Content -Path $ListFile -Encoding UTF8 | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||||
if (Test-Path $tempDir) {
|
} else {
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
$rawList = @($Files -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
# Support-state service files are NOT partially loadable — exclude with a hint.
|
||||||
|
$supportRe = 'ParentConfigurations\.bin$|(^|[\\/])ConfigDumpInfo\.xml$'
|
||||||
|
$supportFiles = @($rawList | Where-Object { $_ -match $supportRe })
|
||||||
|
$fileList = @($rawList | Where-Object { $_ -notmatch $supportRe })
|
||||||
|
if ($supportFiles.Count -gt 0) {
|
||||||
|
Write-Host "[ВНИМАНИЕ] Служебные файлы состояния поддержки исключены из частичной загрузки (частично не грузятся):" -ForegroundColor Yellow
|
||||||
|
foreach ($sf in $supportFiles) { Write-Host " - $sf" -ForegroundColor Yellow }
|
||||||
|
Write-Host " Смена состояния поддержки применяется только полной загрузкой: -Mode Full." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
if ($fileList.Count -eq 0) {
|
||||||
|
Write-Host "Error: после исключения служебных файлов поддержки загружать нечего. Для смены поддержки используйте -Mode Full." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$generatedListFile = Join-Path $tempDir "load_list.txt"
|
||||||
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
[System.IO.File]::WriteAllLines($generatedListFile, $fileList, $utf8Bom)
|
||||||
|
Write-Host "Files to load: $($fileList.Count)"
|
||||||
|
foreach ($f in $fileList) { Write-Host " $f" }
|
||||||
|
|
||||||
|
$arguments += "-listFile", "`"$generatedListFile`""
|
||||||
|
$arguments += "-partial"
|
||||||
|
$arguments += "-updateConfigDumpInfo"
|
||||||
|
}
|
||||||
|
|
||||||
|
$arguments += "-Format", $Format
|
||||||
|
|
||||||
|
# --- Extensions ---
|
||||||
|
if ($Extension) {
|
||||||
|
$arguments += "-Extension", "`"$Extension`""
|
||||||
|
} elseif ($AllExtensions) {
|
||||||
|
$arguments += "-AllExtensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- UpdateDB ---
|
||||||
|
if ($UpdateDB) {
|
||||||
|
$arguments += "/UpdateDBCfg"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "load_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Read log ---
|
||||||
|
$logContent = $null
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Scan log for silent rejections ---
|
||||||
|
# Platform often writes load-time rejections into /Out but exits with code 0.
|
||||||
|
# These patterns flag cases where metadata was dropped or rejected silently.
|
||||||
|
$fatalLogPatterns = @(
|
||||||
|
'Неверное свойство объекта метаданных',
|
||||||
|
'не входит в состав объекта метаданных',
|
||||||
|
'Неизвестное имя типа',
|
||||||
|
'Неизвестный объект метаданных',
|
||||||
|
'Ни один из документов не является регистратором для регистра',
|
||||||
|
'Неверное значение перечисления',
|
||||||
|
'не может быть приведен к типу'
|
||||||
|
)
|
||||||
|
$silentFailures = @()
|
||||||
|
if ($logContent) {
|
||||||
|
foreach ($line in ($logContent -split "`r?`n")) {
|
||||||
|
foreach ($pat in $fatalLogPatterns) {
|
||||||
|
if ($line -match [regex]::Escape($pat)) {
|
||||||
|
$silentFailures += $line.Trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
# Default: mirror platform's verdict via exit code. Log content (including any
|
||||||
|
# rejection warnings) is always printed to stdout for visibility. With -StrictLog,
|
||||||
|
# elevate exit code to 1 when rejection patterns are found even if platform said 0.
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Load completed successfully" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($silentFailures.Count -gt 0) {
|
||||||
|
$msg = "[warning] log contains $($silentFailures.Count) rejection(s) — platform loaded config but dropped properties/refs"
|
||||||
|
if (-not $StrictLog) { $msg += " (pass -StrictLog to treat as error)" }
|
||||||
|
Write-Host $msg -ForegroundColor Yellow
|
||||||
|
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
|
||||||
|
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+140
-19
@@ -1,34 +1,68 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-xml v1.3 — Load 1C configuration from XML files
|
# db-load-xml v1.10 — Load 1C configuration from XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
candidates = glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
v8path = _find_project_v8path()
|
||||||
|
if not v8path:
|
||||||
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates.sort()
|
v8path = max(candidates, key=_version_key)
|
||||||
return candidates[-1]
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return v8path
|
return v8path
|
||||||
|
|
||||||
|
|
||||||
@@ -73,8 +107,14 @@ def main():
|
|||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Validate connection ---
|
# --- Validate connection ---
|
||||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -88,6 +128,77 @@ def main():
|
|||||||
print("Error: -Files or -ListFile required for Partial mode", file=sys.stderr)
|
print("Error: -Files or -ListFile required for Partial mode", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# --- ibcmd branch (file infobase only; hierarchical full-directory import) ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if args.Format == "Plain":
|
||||||
|
print("Error: ibcmd config import supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if args.AllExtensions:
|
||||||
|
arguments = ["infobase", "config", "import", "all-extensions", args.ConfigDir, f"--db-path={args.InfoBasePath}"]
|
||||||
|
elif args.Mode == "Partial" or args.Files or args.ListFile:
|
||||||
|
# partial: import specific files (relative to ConfigDir)
|
||||||
|
if args.ListFile:
|
||||||
|
if not os.path.isfile(args.ListFile):
|
||||||
|
print(f"Error: list file not found: {args.ListFile}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
with open(args.ListFile, encoding="utf-8-sig") as f:
|
||||||
|
file_list = [ln.strip() for ln in f if ln.strip()]
|
||||||
|
elif args.Files:
|
||||||
|
file_list = [p.strip() for p in args.Files.split(",") if p.strip()]
|
||||||
|
else:
|
||||||
|
file_list = []
|
||||||
|
if not file_list:
|
||||||
|
print("Error: -Files or -ListFile required for partial import", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
arguments = ["infobase", "config", "import", "files"] + file_list
|
||||||
|
arguments += [f"--base-dir={args.ConfigDir}", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if args.Extension:
|
||||||
|
arguments.append(f"--extension={args.Extension}")
|
||||||
|
else:
|
||||||
|
arguments = ["infobase", "config", "import", f"--db-path={args.InfoBasePath}"]
|
||||||
|
if args.Extension:
|
||||||
|
arguments.append(f"--extension={args.Extension}")
|
||||||
|
arguments.append(args.ConfigDir)
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Error loading configuration from files (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
print(f"Configuration loaded successfully from: {args.ConfigDir}")
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
exit_code = 0
|
||||||
|
if args.UpdateDB:
|
||||||
|
apply_args = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"]
|
||||||
|
if args.UserName:
|
||||||
|
apply_args.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
apply_args.append(f"--password={args.Password}")
|
||||||
|
apply_args.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(apply_args)}")
|
||||||
|
ar = subprocess.run([v8path] + apply_args, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
exit_code = ar.returncode
|
||||||
|
if exit_code == 0:
|
||||||
|
print("Database configuration updated successfully")
|
||||||
|
else:
|
||||||
|
print(f"Error updating database configuration (code: {exit_code})", file=sys.stderr)
|
||||||
|
if ar.stdout:
|
||||||
|
print(ar.stdout)
|
||||||
|
if ar.stderr:
|
||||||
|
print(ar.stderr, file=sys.stderr)
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
# --- Temp dir ---
|
# --- Temp dir ---
|
||||||
temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_xml_{random.randint(0, 999999)}")
|
temp_dir = os.path.join(tempfile.gettempdir(), f"db_load_xml_{random.randint(0, 999999)}")
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
@@ -114,23 +225,33 @@ def main():
|
|||||||
print("Executing partial configuration load...")
|
print("Executing partial configuration load...")
|
||||||
|
|
||||||
# Build list file
|
# Build list file
|
||||||
generated_list_file = None
|
|
||||||
if args.ListFile:
|
if args.ListFile:
|
||||||
# Use provided list file
|
|
||||||
if not os.path.isfile(args.ListFile):
|
if not os.path.isfile(args.ListFile):
|
||||||
print(f"Error: list file not found: {args.ListFile}", file=sys.stderr)
|
print(f"Error: list file not found: {args.ListFile}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
generated_list_file = args.ListFile
|
with open(args.ListFile, encoding="utf-8-sig") as f:
|
||||||
|
raw_list = [ln.strip() for ln in f if ln.strip()]
|
||||||
else:
|
else:
|
||||||
# Generate from -Files parameter
|
raw_list = [f.strip() for f in args.Files.split(",") if f.strip()]
|
||||||
file_list = [f.strip() for f in args.Files.split(",") if f.strip()]
|
|
||||||
generated_list_file = os.path.join(temp_dir, "load_list.txt")
|
|
||||||
with open(generated_list_file, "w", encoding="utf-8-sig") as f:
|
|
||||||
f.write("\n".join(file_list))
|
|
||||||
|
|
||||||
print(f"Files to load: {len(file_list)}")
|
# Support-state service files are NOT partially loadable — exclude with a hint.
|
||||||
for fl in file_list:
|
support_re = re.compile(r"ParentConfigurations\.bin$|(^|[\\/])ConfigDumpInfo\.xml$")
|
||||||
print(f" {fl}")
|
support_files = [x for x in raw_list if support_re.search(x)]
|
||||||
|
file_list = [x for x in raw_list if not support_re.search(x)]
|
||||||
|
if support_files:
|
||||||
|
print("[ВНИМАНИЕ] Служебные файлы состояния поддержки исключены из частичной загрузки (частично не грузятся):", file=sys.stderr)
|
||||||
|
for sf in support_files:
|
||||||
|
print(f" - {sf}", file=sys.stderr)
|
||||||
|
print(" Смена состояния поддержки применяется только полной загрузкой: -Mode Full.", file=sys.stderr)
|
||||||
|
if not file_list:
|
||||||
|
print("Error: после исключения служебных файлов поддержки загружать нечего. Для смены поддержки используйте -Mode Full.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
generated_list_file = os.path.join(temp_dir, "load_list.txt")
|
||||||
|
with open(generated_list_file, "w", encoding="utf-8-sig") as f:
|
||||||
|
f.write("\n".join(file_list))
|
||||||
|
print(f"Files to load: {len(file_list)}")
|
||||||
|
for fl in file_list:
|
||||||
|
print(f" {fl}")
|
||||||
|
|
||||||
arguments += ["-listFile", generated_list_file]
|
arguments += ["-listFile", generated_list_file]
|
||||||
arguments.append("-partial")
|
arguments.append("-partial")
|
||||||
@@ -1,76 +1,76 @@
|
|||||||
---
|
---
|
||||||
name: db-run
|
name: db-run
|
||||||
description: Запуск 1С:Предприятие. Используй когда нужно запустить 1С, открыть базу, запустить предприятие
|
description: Запуск 1С:Предприятие. Используй когда нужно запустить 1С, открыть базу, запустить предприятие
|
||||||
argument-hint: "[database]"
|
argument-hint: "[database]"
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-run — Запуск 1С:Предприятие
|
# /db-run — Запуск 1С:Предприятие
|
||||||
|
|
||||||
Запускает информационную базу в режиме 1С:Предприятие (пользовательский режим).
|
Запускает информационную базу в режиме 1С:Предприятие (пользовательский режим).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-run [database]
|
/db-run [database]
|
||||||
/db-run dev
|
/db-run dev
|
||||||
/db-run dev /Execute process.epf
|
/db-run dev /Execute process.epf
|
||||||
/db-run dev /C "параметр запуска"
|
/db-run dev /C "параметр запуска"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-Execute <файл.epf>` | нет | Запуск внешней обработки сразу после старта |
|
| `-Execute <файл.epf>` | нет | Запуск внешней обработки сразу после старта |
|
||||||
| `-CParam <строка>` | нет | Параметр запуска (/C) |
|
| `-CParam <строка>` | нет | Параметр запуска (/C) |
|
||||||
| `-URL <ссылка>` | нет | Навигационная ссылка (формат `e1cib/...`) |
|
| `-URL <ссылка>` | нет | Навигационная ссылка (формат `e1cib/...`) |
|
||||||
|
|
||||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
## Важно
|
## Важно
|
||||||
|
|
||||||
Скрипт запускает 1С в фоне (`Start-Process` без `-Wait`) — управление возвращается сразу.
|
Скрипт запускает 1С в фоне (`Start-Process` без `-Wait`) — управление возвращается сразу.
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Простой запуск
|
# Простой запуск
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
||||||
|
|
||||||
# Запуск с обработкой
|
# Запуск с обработкой
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf"
|
powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Execute "C:\epf\МояОбработка.epf"
|
||||||
|
|
||||||
# Открыть по навигационной ссылке
|
# Открыть по навигационной ссылке
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура"
|
powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -URL "e1cib/data/Справочник.Номенклатура"
|
||||||
|
|
||||||
# Серверная база с параметром запуска
|
# Серверная база с параметром запуска
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-run.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление"
|
powershell.exe -NoProfile -File ".opencode/skills/db-run/scripts/db-run.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -CParam "ЗапуститьОбновление"
|
||||||
```
|
```
|
||||||
+170
-145
@@ -1,145 +1,170 @@
|
|||||||
# db-run v1.0 — Launch 1C:Enterprise
|
# db-run v1.1 — Launch 1C:Enterprise
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Запуск 1С:Предприятие
|
Запуск 1С:Предприятие
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Запускает информационную базу в режиме 1С:Предприятие (пользовательский режим).
|
Запускает информационную базу в режиме 1С:Предприятие (пользовательский режим).
|
||||||
Запуск в фоне — не ждёт завершения процесса.
|
Запуск в фоне — не ждёт завершения процесса.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER Execute
|
.PARAMETER Execute
|
||||||
Путь к внешней обработке для запуска
|
Путь к внешней обработке для запуска
|
||||||
|
|
||||||
.PARAMETER CParam
|
.PARAMETER CParam
|
||||||
Параметр запуска (/C)
|
Параметр запуска (/C)
|
||||||
|
|
||||||
.PARAMETER URL
|
.PARAMETER URL
|
||||||
Навигационная ссылка (e1cib/...)
|
Навигационная ссылка (e1cib/...)
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB"
|
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -Execute "C:\epf\МояОбработка.epf"
|
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -Execute "C:\epf\МояОбработка.epf"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -CParam "ЗапуститьОбновление"
|
.\db-run.ps1 -InfoBasePath "C:\Bases\MyDB" -CParam "ЗапуститьОбновление"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Execute,
|
[string]$Execute,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$CParam,
|
[string]$CParam,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$URL
|
[string]$URL
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Validate connection ---
|
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
if (-not $V8Path) {
|
||||||
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
$V8Path = Find-ProjectV8Path
|
||||||
exit 1
|
}
|
||||||
}
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
# --- Build arguments as single string ---
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
# Note: Start-Process without -NoNewWindow uses ShellExecute.
|
Select-Object -First 1
|
||||||
# Passing ArgumentList as array can corrupt Cyrillic when ShellExecute
|
if ($found) {
|
||||||
# re-joins elements. Single string avoids this.
|
$V8Path = $found.FullName
|
||||||
$argString = "ENTERPRISE"
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
$argString += " /S `"$InfoBaseServer/$InfoBaseRef`""
|
exit 1
|
||||||
} else {
|
}
|
||||||
$argString += " /F `"$InfoBasePath`""
|
}
|
||||||
}
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
if ($UserName) { $argString += " /N`"$UserName`"" }
|
}
|
||||||
if ($Password) { $argString += " /P`"$Password`"" }
|
|
||||||
|
if (-not (Test-Path $V8Path)) {
|
||||||
# --- Optional params ---
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
if ($Execute) {
|
exit 1
|
||||||
$ext = [System.IO.Path]::GetExtension($Execute).ToLower()
|
}
|
||||||
if ($ext -eq ".erf") {
|
|
||||||
Write-Host "[WARN] /Execute не поддерживает ERF-файлы (внешние отчёты)." -ForegroundColor Yellow
|
# --- Validate connection ---
|
||||||
Write-Host " Откройте отчёт через «Файл -> Открыть»: $Execute" -ForegroundColor Yellow
|
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
Write-Host " Запускаю базу без /Execute." -ForegroundColor Yellow
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
$Execute = ""
|
exit 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if ($Execute) {
|
# --- Build arguments as single string ---
|
||||||
$argString += " /Execute `"$Execute`""
|
# Note: Start-Process without -NoNewWindow uses ShellExecute.
|
||||||
}
|
# Passing ArgumentList as array can corrupt Cyrillic when ShellExecute
|
||||||
if ($CParam) {
|
# re-joins elements. Single string avoids this.
|
||||||
$argString += " /C `"$CParam`""
|
$argString = "ENTERPRISE"
|
||||||
}
|
|
||||||
if ($URL) {
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
$argString += " /URL `"$URL`""
|
$argString += " /S `"$InfoBaseServer/$InfoBaseRef`""
|
||||||
}
|
} else {
|
||||||
|
$argString += " /F `"$InfoBasePath`""
|
||||||
$argString += " /DisableStartupDialogs"
|
}
|
||||||
|
|
||||||
# --- Execute (background, no wait) ---
|
if ($UserName) { $argString += " /N`"$UserName`"" }
|
||||||
Write-Host "Running: 1cv8.exe $argString"
|
if ($Password) { $argString += " /P`"$Password`"" }
|
||||||
Start-Process -FilePath $V8Path -ArgumentList $argString
|
|
||||||
Write-Host "1C:Enterprise launched" -ForegroundColor Green
|
# --- Optional params ---
|
||||||
|
if ($Execute) {
|
||||||
|
$ext = [System.IO.Path]::GetExtension($Execute).ToLower()
|
||||||
|
if ($ext -eq ".erf") {
|
||||||
|
Write-Host "[WARN] /Execute не поддерживает ERF-файлы (внешние отчёты)." -ForegroundColor Yellow
|
||||||
|
Write-Host " Откройте отчёт через «Файл -> Открыть»: $Execute" -ForegroundColor Yellow
|
||||||
|
Write-Host " Запускаю базу без /Execute." -ForegroundColor Yellow
|
||||||
|
$Execute = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($Execute) {
|
||||||
|
$argString += " /Execute `"$Execute`""
|
||||||
|
}
|
||||||
|
if ($CParam) {
|
||||||
|
$argString += " /C `"$CParam`""
|
||||||
|
}
|
||||||
|
if ($URL) {
|
||||||
|
$argString += " /URL `"$URL`""
|
||||||
|
}
|
||||||
|
|
||||||
|
$argString += " /DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute (background, no wait) ---
|
||||||
|
Write-Host "Running: 1cv8.exe $argString"
|
||||||
|
Start-Process -FilePath $V8Path -ArgumentList $argString
|
||||||
|
Write-Host "1C:Enterprise launched" -ForegroundColor Green
|
||||||
+41
-6
@@ -1,26 +1,61 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-run v1.0 — Launch 1C:Enterprise
|
# db-run v1.1 — Launch 1C:Enterprise
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
found = sorted(glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe"))
|
v8path = _find_project_v8path()
|
||||||
if found:
|
if not v8path:
|
||||||
return found[-1]
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
|
if candidates:
|
||||||
|
v8path = max(candidates, key=_version_key)
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -1,93 +1,86 @@
|
|||||||
---
|
---
|
||||||
name: db-update
|
name: db-update
|
||||||
description: Обновление конфигурации базы данных 1С. Используй когда нужно обновить БД, применить конфигурацию, UpdateDBCfg
|
description: Обновление конфигурации базы данных 1С. Используй когда нужно обновить БД, применить конфигурацию, UpdateDBCfg
|
||||||
argument-hint: "[database]"
|
argument-hint: "[database]"
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
# /db-update — Обновление конфигурации БД
|
# /db-update — Обновление конфигурации БД
|
||||||
|
|
||||||
Применяет изменения основной конфигурации к конфигурации базы данных (`/UpdateDBCfg`). Обязательный шаг после `/db-load-cf`, `/db-load-xml`, `/db-load-git`.
|
Применяет изменения основной конфигурации к конфигурации базы данных (`/UpdateDBCfg`). Обязательный шаг после `/db-load-cf`, `/db-load-xml`, `/db-load-git`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/db-update [database]
|
/db-update [database]
|
||||||
/db-update dev
|
/db-update dev
|
||||||
/db-update dev -Dynamic+
|
/db-update dev -Dynamic+
|
||||||
```
|
```
|
||||||
|
|
||||||
## Параметры подключения
|
## Параметры подключения
|
||||||
|
|
||||||
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` (путь к платформе) и разреши базу:
|
||||||
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
1. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
2. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
3. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
4. Если ветка не совпала — используй `default`
|
4. Если ветка не совпала — используй `default`
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если файла нет — предложи `/db-list add`.
|
Если файла нет — предложи `/db-list add`.
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/db-update/scripts/db-update.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-Extension <имя>` | нет | Обновить расширение |
|
| `-Extension <имя>` | нет | Обновить расширение |
|
||||||
| `-AllExtensions` | нет | Обновить все расширения |
|
| `-AllExtensions` | нет | Обновить все расширения |
|
||||||
| `-Dynamic <+/->` | нет | `+` — динамическое обновление, `-` — отключить |
|
| `-Dynamic <+/->` | нет | `+` — динамическое обновление, `-` — отключить |
|
||||||
| `-Server` | нет | Обновление на стороне сервера |
|
| `-Server` | нет | Обновление на стороне сервера |
|
||||||
| `-WarningsAsErrors` | нет | Предупреждения считать ошибками |
|
| `-WarningsAsErrors` | нет | Предупреждения считать ошибками |
|
||||||
|
|
||||||
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
> `*` — нужен либо `-InfoBasePath`, либо пара `-InfoBaseServer` + `-InfoBaseRef`
|
||||||
|
|
||||||
### Фоновое обновление (серверная база)
|
### Фоновое обновление (серверная база)
|
||||||
|
|
||||||
| Параметр | Описание |
|
| Параметр | Описание |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| `-BackgroundStart` | Начать фоновое обновление |
|
| `-BackgroundStart` | Начать фоновое обновление |
|
||||||
| `-BackgroundFinish` | Дождаться окончания |
|
| `-BackgroundFinish` | Дождаться окончания |
|
||||||
| `-BackgroundCancel` | Отменить |
|
| `-BackgroundCancel` | Отменить |
|
||||||
| `-BackgroundSuspend` | Приостановить |
|
| `-BackgroundSuspend` | Приостановить |
|
||||||
| `-BackgroundResume` | Возобновить |
|
| `-BackgroundResume` | Возобновить |
|
||||||
|
|
||||||
## Коды возврата
|
## Предупреждения
|
||||||
|
|
||||||
| Код | Описание |
|
- Если обновление **не динамическое** — потребуется **монопольный доступ** к базе (все пользователи должны выйти)
|
||||||
|-----|----------|
|
- Для серверных баз рекомендуется `-Dynamic+` для обновления без остановки
|
||||||
| 0 | Успешно |
|
- Если структура данных существенно изменилась (удаление реквизитов, изменение типов) — динамическое обновление может быть невозможно
|
||||||
| 1 | Ошибка (см. лог) |
|
|
||||||
|
## Примеры
|
||||||
## Предупреждения
|
|
||||||
|
```powershell
|
||||||
- Если обновление **не динамическое** — потребуется **монопольный доступ** к базе (все пользователи должны выйти)
|
# Обычное обновление (файловая база)
|
||||||
- Для серверных баз рекомендуется `-Dynamic+` для обновления без остановки
|
powershell.exe -NoProfile -File ".opencode/skills/db-update/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
||||||
- Если структура данных существенно изменилась (удаление реквизитов, изменение типов) — динамическое обновление может быть невозможно
|
|
||||||
|
# Динамическое обновление (серверная база)
|
||||||
## Примеры
|
powershell.exe -NoProfile -File ".opencode/skills/db-update/scripts/db-update.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic "+"
|
||||||
|
|
||||||
```powershell
|
# Обновление расширения
|
||||||
# Обычное обновление (файловая база)
|
powershell.exe -NoProfile -File ".opencode/skills/db-update/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin"
|
```
|
||||||
|
|
||||||
# Динамическое обновление (серверная база)
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -Dynamic "+"
|
|
||||||
|
|
||||||
# Обновление расширения
|
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-update.ps1" -InfoBasePath "C:\Bases\MyDB" -UserName "Admin" -Extension "МоёРасширение"
|
|
||||||
```
|
|
||||||
+243
-184
@@ -1,184 +1,243 @@
|
|||||||
# db-update v1.0 — Update 1C database configuration
|
# db-update v1.4 — Update 1C database configuration
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Обновление конфигурации базы данных 1С
|
Обновление конфигурации базы данных 1С
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Применяет изменения основной конфигурации к конфигурации базы данных.
|
Применяет изменения основной конфигурации к конфигурации базы данных.
|
||||||
Поддерживает динамическое обновление, обновление расширений.
|
Поддерживает динамическое обновление, обновление расширений.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER Extension
|
.PARAMETER Extension
|
||||||
Имя расширения для обновления
|
Имя расширения для обновления
|
||||||
|
|
||||||
.PARAMETER AllExtensions
|
.PARAMETER AllExtensions
|
||||||
Обновить все расширения
|
Обновить все расширения
|
||||||
|
|
||||||
.PARAMETER Dynamic
|
.PARAMETER Dynamic
|
||||||
Динамическое обновление: "+" включить, "-" отключить
|
Динамическое обновление: "+" включить, "-" отключить
|
||||||
|
|
||||||
.PARAMETER Server
|
.PARAMETER Server
|
||||||
Обновление на стороне сервера
|
Обновление на стороне сервера
|
||||||
|
|
||||||
.PARAMETER WarningsAsErrors
|
.PARAMETER WarningsAsErrors
|
||||||
Предупреждения считать ошибками
|
Предупреждения считать ошибками
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-update.ps1 -InfoBasePath "C:\Bases\MyDB"
|
.\db-update.ps1 -InfoBasePath "C:\Bases\MyDB"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\db-update.ps1 -InfoBasePath "C:\Bases\MyDB" -Dynamic "+" -Extension "МоёРасширение"
|
.\db-update.ps1 -InfoBasePath "C:\Bases\MyDB" -Dynamic "+" -Extension "МоёРасширение"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Extension,
|
[string]$Extension,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$AllExtensions,
|
[switch]$AllExtensions,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[ValidateSet("+", "-")]
|
[ValidateSet("+", "-")]
|
||||||
[string]$Dynamic,
|
[string]$Dynamic,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$Server,
|
[switch]$Server,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[switch]$WarningsAsErrors
|
[switch]$WarningsAsErrors
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Validate connection ---
|
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
if (-not $V8Path) {
|
||||||
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
$V8Path = Find-ProjectV8Path
|
||||||
exit 1
|
}
|
||||||
}
|
if (-not $V8Path) {
|
||||||
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
# --- Temp dir ---
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
$tempDir = Join-Path $env:TEMP "db_update_$(Get-Random)"
|
Select-Object -First 1
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
if ($found) {
|
||||||
|
$V8Path = $found.FullName
|
||||||
try {
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
# --- Build arguments ---
|
} else {
|
||||||
$arguments = @("DESIGNER")
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
}
|
||||||
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
}
|
||||||
} else {
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
$arguments += "/F", "`"$InfoBasePath`""
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
if (-not (Test-Path $V8Path)) {
|
||||||
if ($Password) { $arguments += "/P`"$Password`"" }
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
$arguments += "/UpdateDBCfg"
|
}
|
||||||
|
|
||||||
# --- Options ---
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
if ($Dynamic) {
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
$arguments += "-Dynamic$Dynamic"
|
|
||||||
}
|
# --- Validate connection ---
|
||||||
if ($Server) {
|
if ($engine -eq "ibcmd") {
|
||||||
$arguments += "-Server"
|
if (-not $InfoBasePath) {
|
||||||
}
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
if ($WarningsAsErrors) {
|
exit 1
|
||||||
$arguments += "-WarningsAsErrors"
|
}
|
||||||
}
|
} elseif (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
|
Write-Host "Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef" -ForegroundColor Red
|
||||||
# --- Extensions ---
|
exit 1
|
||||||
if ($Extension) {
|
}
|
||||||
$arguments += "-Extension", "`"$Extension`""
|
|
||||||
} elseif ($AllExtensions) {
|
# --- Temp dir ---
|
||||||
$arguments += "-AllExtensions"
|
$tempDir = Join-Path $env:TEMP "db_update_$(Get-Random)"
|
||||||
}
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
|
|
||||||
# --- Output ---
|
try {
|
||||||
$outFile = Join-Path $tempDir "update_log.txt"
|
if ($engine -eq "ibcmd") {
|
||||||
$arguments += "/Out", "`"$outFile`""
|
# --- ibcmd branch (file infobase only) ---
|
||||||
$arguments += "/DisableStartupDialogs"
|
if ($AllExtensions) {
|
||||||
|
Write-Host "Error: ibcmd config apply does not support -AllExtensions (use -Extension)" -ForegroundColor Red
|
||||||
# --- Execute ---
|
exit 1
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
}
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
$arguments = @("infobase", "config", "apply", "--db-path=$InfoBasePath", "--force")
|
||||||
$exitCode = $process.ExitCode
|
if ($Dynamic -eq "+") { $arguments += "--dynamic=auto" }
|
||||||
|
elseif ($Dynamic -eq "-") { $arguments += "--dynamic=disable" }
|
||||||
# --- Result ---
|
if ($Extension) { $arguments += "--extension=$Extension" }
|
||||||
if ($exitCode -eq 0) {
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
Write-Host "Database configuration updated successfully" -ForegroundColor Green
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
} else {
|
$arguments += "--data=$tempDir"
|
||||||
Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
}
|
$output = & $V8Path @arguments 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
if (Test-Path $outFile) {
|
if ($exitCode -eq 0) {
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
Write-Host "Database configuration updated successfully" -ForegroundColor Green
|
||||||
if ($logContent) {
|
} else {
|
||||||
Write-Host "--- Log ---"
|
Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
Write-Host $logContent
|
}
|
||||||
Write-Host "--- End ---"
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
}
|
exit $exitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
exit $exitCode
|
# --- 1cv8 branch ---
|
||||||
|
# --- Build arguments ---
|
||||||
} finally {
|
$arguments = @("DESIGNER")
|
||||||
if (Test-Path $tempDir) {
|
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
}
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
}
|
} else {
|
||||||
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
|
|
||||||
|
$arguments += "/UpdateDBCfg"
|
||||||
|
|
||||||
|
# --- Options ---
|
||||||
|
if ($Dynamic) {
|
||||||
|
$arguments += "-Dynamic$Dynamic"
|
||||||
|
}
|
||||||
|
if ($Server) {
|
||||||
|
$arguments += "-Server"
|
||||||
|
}
|
||||||
|
if ($WarningsAsErrors) {
|
||||||
|
$arguments += "-WarningsAsErrors"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Extensions ---
|
||||||
|
if ($Extension) {
|
||||||
|
$arguments += "-Extension", "`"$Extension`""
|
||||||
|
} elseif ($AllExtensions) {
|
||||||
|
$arguments += "-AllExtensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "update_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Database configuration updated successfully" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error updating database configuration (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
-7
@@ -1,29 +1,65 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-update v1.0 — Update 1C database configuration
|
# db-update v1.4 — Update 1C database configuration
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
found = sorted(glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe"))
|
v8path = _find_project_v8path()
|
||||||
if found:
|
if not v8path:
|
||||||
return found[-1]
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
|
if candidates:
|
||||||
|
v8path = max(candidates, key=_version_key)
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -52,11 +88,48 @@ def main():
|
|||||||
|
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Validate connection ---
|
# --- Validate connection ---
|
||||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
print("Error: specify -InfoBasePath or -InfoBaseServer + -InfoBaseRef", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# --- ibcmd branch (file infobase only) ---
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if args.AllExtensions:
|
||||||
|
print("Error: ibcmd config apply does not support -AllExtensions (use -Extension)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
arguments = ["infobase", "config", "apply", f"--db-path={args.InfoBasePath}", "--force"]
|
||||||
|
if args.Dynamic == "+":
|
||||||
|
arguments.append("--dynamic=auto")
|
||||||
|
elif args.Dynamic == "-":
|
||||||
|
arguments.append("--dynamic=disable")
|
||||||
|
if args.Extension:
|
||||||
|
arguments.append(f"--extension={args.Extension}")
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print("Database configuration updated successfully")
|
||||||
|
else:
|
||||||
|
print(f"Error updating database configuration (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
# --- Temp dir ---
|
# --- Temp dir ---
|
||||||
temp_dir = os.path.join(tempfile.gettempdir(), f"db_update_{random.randint(0, 999999)}")
|
temp_dir = os.path.join(tempfile.gettempdir(), f"db_update_{random.randint(0, 999999)}")
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
+196
-196
@@ -1,196 +1,196 @@
|
|||||||
---
|
---
|
||||||
name: epf-bsp-add-command
|
name: epf-bsp-add-command
|
||||||
description: Определить команду в БСП‑описании обработки (`СведенияОВнешнейОбработке`) — открытие формы, вызов клиентского/серверного метода, заполнение объекта и т.п. Используй когда нужно зарегистрировать команду в дополнительной обработке БСП
|
description: Определить команду в БСП‑описании обработки (`СведенияОВнешнейОбработке`) — открытие формы, вызов клиентского/серверного метода, заполнение объекта и т.п. Используй когда нужно зарегистрировать команду в дополнительной обработке БСП
|
||||||
argument-hint: <ProcessorName> <Идентификатор> [ТипКоманды] [Представление]
|
argument-hint: <ProcessorName> <Идентификатор> [ТипКоманды] [Представление]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Read
|
- Read
|
||||||
- Edit
|
- Edit
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
# /epf-bsp-add-command — Добавление команды БСП
|
# /epf-bsp-add-command — Добавление команды БСП
|
||||||
|
|
||||||
Добавляет команду в существующую функцию `СведенияОВнешнейОбработке()` и генерирует соответствующий обработчик.
|
Добавляет команду в существующую функцию `СведенияОВнешнейОбработке()` и генерирует соответствующий обработчик.
|
||||||
|
|
||||||
Предварительно обработка должна быть инициализирована через `/epf-bsp-init`.
|
Предварительно обработка должна быть инициализирована через `/epf-bsp-init`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/epf-bsp-add-command <ProcessorName> <Идентификатор> [ТипКоманды] [Представление]
|
/epf-bsp-add-command <ProcessorName> <Идентификатор> [ТипКоманды] [Представление]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|---------------|:------------:|-----------------------|--------------------------------------------|
|
|---------------|:------------:|-----------------------|--------------------------------------------|
|
||||||
| ProcessorName | да | — | Имя обработки |
|
| ProcessorName | да | — | Имя обработки |
|
||||||
| Идентификатор | да | — | Внутреннее имя команды (латиница) |
|
| Идентификатор | да | — | Внутреннее имя команды (латиница) |
|
||||||
| ТипКоманды | нет | из вида обработки | Тип запуска команды (см. маппинг ниже) |
|
| ТипКоманды | нет | из вида обработки | Тип запуска команды (см. маппинг ниже) |
|
||||||
| Представление | нет | = Идентификатор | Отображаемое имя команды для пользователя |
|
| Представление | нет | = Идентификатор | Отображаемое имя команды для пользователя |
|
||||||
| SrcDir | нет | `src` | Каталог исходников |
|
| SrcDir | нет | `src` | Каталог исходников |
|
||||||
|
|
||||||
## Маппинг типов команд
|
## Маппинг типов команд
|
||||||
|
|
||||||
Пользователь может указать тип в свободной форме:
|
Пользователь может указать тип в свободной форме:
|
||||||
|
|
||||||
| Пользователь пишет | ТипКоманды |
|
| Пользователь пишет | ТипКоманды |
|
||||||
|---------------------------------------|-----------------------------------------------------|
|
|---------------------------------------|-----------------------------------------------------|
|
||||||
| открыть форму, форма | `ТипКомандыОткрытиеФормы()` |
|
| открыть форму, форма | `ТипКомандыОткрытиеФормы()` |
|
||||||
| клиентский метод, на клиенте | `ТипКомандыВызовКлиентскогоМетода()` |
|
| клиентский метод, на клиенте | `ТипКомандыВызовКлиентскогоМетода()` |
|
||||||
| серверный метод, на сервере | `ТипКомандыВызовСерверногоМетода()` |
|
| серверный метод, на сервере | `ТипКомандыВызовСерверногоМетода()` |
|
||||||
| заполнение формы, заполнить форму | `ТипКомандыЗаполнениеФормы()` |
|
| заполнение формы, заполнить форму | `ТипКомандыЗаполнениеФормы()` |
|
||||||
| сценарий, безопасный режим | `ТипКомандыСценарийВБезопасномРежиме()` |
|
| сценарий, безопасный режим | `ТипКомандыСценарийВБезопасномРежиме()` |
|
||||||
|
|
||||||
Если пользователь не указал тип — определи по виду обработки из существующего кода `СведенияОВнешнейОбработке()`:
|
Если пользователь не указал тип — определи по виду обработки из существующего кода `СведенияОВнешнейОбработке()`:
|
||||||
|
|
||||||
| Вид обработки (из кода) | ТипКоманды по умолчанию |
|
| Вид обработки (из кода) | ТипКоманды по умолчанию |
|
||||||
|----------------------------|-------------------------------------------|
|
|----------------------------|-------------------------------------------|
|
||||||
| ДополнительнаяОбработка | `ТипКомандыОткрытиеФормы()` |
|
| ДополнительнаяОбработка | `ТипКомандыОткрытиеФормы()` |
|
||||||
| ДополнительныйОтчет | `ТипКомандыОткрытиеФормы()` |
|
| ДополнительныйОтчет | `ТипКомандыОткрытиеФормы()` |
|
||||||
| ЗаполнениеОбъекта | `ТипКомандыВызовСерверногоМетода()` |
|
| ЗаполнениеОбъекта | `ТипКомандыВызовСерверногоМетода()` |
|
||||||
| Отчет | `ТипКомандыОткрытиеФормы()` |
|
| Отчет | `ТипКомандыОткрытиеФормы()` |
|
||||||
| ПечатнаяФорма | `ТипКомандыВызовСерверногоМетода()` |
|
| ПечатнаяФорма | `ТипКомандыВызовСерверногоМетода()` |
|
||||||
| СозданиеСвязанныхОбъектов | `ТипКомандыВызовСерверногоМетода()` |
|
| СозданиеСвязанныхОбъектов | `ТипКомандыВызовСерверногоМетода()` |
|
||||||
|
|
||||||
## Шаблон добавления команды
|
## Шаблон добавления команды
|
||||||
|
|
||||||
Вставляется в `СведенияОВнешнейОбработке()` **перед** строкой `Возврат ПараметрыРегистрации`:
|
Вставляется в `СведенияОВнешнейОбработке()` **перед** строкой `Возврат ПараметрыРегистрации`:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
|
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
|
||||||
НоваяКоманда.Представление = НСтр("ru = '{{Представление}}'");
|
НоваяКоманда.Представление = НСтр("ru = '{{Представление}}'");
|
||||||
НоваяКоманда.Идентификатор = "{{Идентификатор}}";
|
НоваяКоманда.Идентификатор = "{{Идентификатор}}";
|
||||||
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ТипКоманды}};
|
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ТипКоманды}};
|
||||||
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
||||||
```
|
```
|
||||||
|
|
||||||
Для печатных форм (ВидОбработкиПечатнаяФорма) добавь также:
|
Для печатных форм (ВидОбработкиПечатнаяФорма) добавь также:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
НоваяКоманда.Модификатор = "ПечатьMXL";
|
НоваяКоманда.Модификатор = "ПечатьMXL";
|
||||||
```
|
```
|
||||||
|
|
||||||
Примечание: в отличие от первой команды (из `/epf-bsp-init`), дополнительные команды используют строковые литералы `НСтр("ru = '...'")` для представления и строку для идентификатора, а не `Метаданные()`.
|
Примечание: в отличие от первой команды (из `/epf-bsp-init`), дополнительные команды используют строковые литералы `НСтр("ru = '...'")` для представления и строку для идентификатора, а не `Метаданные()`.
|
||||||
|
|
||||||
## Шаблоны обработчиков
|
## Шаблоны обработчиков
|
||||||
|
|
||||||
### ВызовСерверногоМетода — если обработчик уже есть
|
### ВызовСерверногоМетода — если обработчик уже есть
|
||||||
|
|
||||||
Если процедура `ВыполнитьКоманду` уже существует в модуле объекта, добавь ветку перед `КонецЕсли`:
|
Если процедура `ВыполнитьКоманду` уже существует в модуле объекта, добавь ветку перед `КонецЕсли`:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
ИначеЕсли ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
ИначеЕсли ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
||||||
// TODO: Реализация {{Идентификатор}}
|
// TODO: Реализация {{Идентификатор}}
|
||||||
```
|
```
|
||||||
|
|
||||||
### ВызовСерверногоМетода — если обработчика нет
|
### ВызовСерверногоМетода — если обработчика нет
|
||||||
|
|
||||||
Для глобальных обработок (без `ОбъектыНазначения`):
|
Для глобальных обработок (без `ОбъектыНазначения`):
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт
|
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт
|
||||||
|
|
||||||
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
||||||
// TODO: Реализация {{Идентификатор}}
|
// TODO: Реализация {{Идентификатор}}
|
||||||
КонецЕсли;
|
КонецЕсли;
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
|
|
||||||
Для назначаемых обработок (с `ОбъектыНазначения`):
|
Для назначаемых обработок (с `ОбъектыНазначения`):
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт
|
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт
|
||||||
|
|
||||||
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
||||||
// TODO: Реализация {{Идентификатор}}
|
// TODO: Реализация {{Идентификатор}}
|
||||||
КонецЕсли;
|
КонецЕсли;
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
|
|
||||||
### ПечатнаяФорма — если процедура Печать уже есть
|
### ПечатнаяФорма — если процедура Печать уже есть
|
||||||
|
|
||||||
Добавь блок перед `КонецПроцедуры`:
|
Добавь блок перед `КонецПроцедуры`:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "{{Идентификатор}}");
|
ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "{{Идентификатор}}");
|
||||||
Если ПечатнаяФорма <> Неопределено Тогда
|
Если ПечатнаяФорма <> Неопределено Тогда
|
||||||
ПечатнаяФорма.ТабличныйДокумент = Сформировать{{Идентификатор}}(МассивОбъектов, ОбъектыПечати);
|
ПечатнаяФорма.ТабличныйДокумент = Сформировать{{Идентификатор}}(МассивОбъектов, ОбъектыПечати);
|
||||||
ПечатнаяФорма.СинонимМакета = НСтр("ru = '{{Представление}}'");
|
ПечатнаяФорма.СинонимМакета = НСтр("ru = '{{Представление}}'");
|
||||||
КонецЕсли;
|
КонецЕсли;
|
||||||
```
|
```
|
||||||
|
|
||||||
### ПечатнаяФорма — если процедуры Печать нет
|
### ПечатнаяФорма — если процедуры Печать нет
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
|
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
|
||||||
|
|
||||||
ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "{{Идентификатор}}");
|
ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "{{Идентификатор}}");
|
||||||
Если ПечатнаяФорма <> Неопределено Тогда
|
Если ПечатнаяФорма <> Неопределено Тогда
|
||||||
ПечатнаяФорма.ТабличныйДокумент = Сформировать{{Идентификатор}}(МассивОбъектов, ОбъектыПечати);
|
ПечатнаяФорма.ТабличныйДокумент = Сформировать{{Идентификатор}}(МассивОбъектов, ОбъектыПечати);
|
||||||
ПечатнаяФорма.СинонимМакета = НСтр("ru = '{{Представление}}'");
|
ПечатнаяФорма.СинонимМакета = НСтр("ru = '{{Представление}}'");
|
||||||
КонецЕсли;
|
КонецЕсли;
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
|
|
||||||
### ВызовКлиентскогоМетода
|
### ВызовКлиентскогоМетода
|
||||||
|
|
||||||
Добавляется в **модуль формы** (`Forms/<FormName>/Ext/Form/Module.bsl`):
|
Добавляется в **модуль формы** (`Forms/<FormName>/Ext/Form/Module.bsl`):
|
||||||
|
|
||||||
Для глобальных обработок:
|
Для глобальных обработок:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
&НаКлиенте
|
&НаКлиенте
|
||||||
Процедура ВыполнитьКоманду(ИдентификаторКоманды) Экспорт
|
Процедура ВыполнитьКоманду(ИдентификаторКоманды) Экспорт
|
||||||
|
|
||||||
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
||||||
// TODO: Реализация {{Идентификатор}}
|
// TODO: Реализация {{Идентификатор}}
|
||||||
КонецЕсли;
|
КонецЕсли;
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
|
|
||||||
Для назначаемых обработок:
|
Для назначаемых обработок:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
&НаКлиенте
|
&НаКлиенте
|
||||||
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначенияМассив) Экспорт
|
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначенияМассив) Экспорт
|
||||||
|
|
||||||
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
Если ИдентификаторКоманды = "{{Идентификатор}}" Тогда
|
||||||
// TODO: Реализация {{Идентификатор}}
|
// TODO: Реализация {{Идентификатор}}
|
||||||
КонецЕсли;
|
КонецЕсли;
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
|
|
||||||
Если процедура уже есть — добавь ветку `ИначеЕсли`.
|
Если процедура уже есть — добавь ветку `ИначеЕсли`.
|
||||||
|
|
||||||
## Инструкции
|
## Инструкции
|
||||||
|
|
||||||
1. Найди и прочитай `ObjectModule.bsl` через Glob: `src/{{ProcessorName}}/Ext/ObjectModule.bsl`
|
1. Найди и прочитай `ObjectModule.bsl` через Glob: `src/{{ProcessorName}}/Ext/ObjectModule.bsl`
|
||||||
2. Убедись что `СведенияОВнешнейОбработке()` существует. Если нет — предложи вызвать `/epf-bsp-init`
|
2. Убедись что `СведенияОВнешнейОбработке()` существует. Если нет — предложи вызвать `/epf-bsp-init`
|
||||||
3. Определи вид обработки из существующего кода (найди строку с `ВидОбработки...()`)
|
3. Определи вид обработки из существующего кода (найди строку с `ВидОбработки...()`)
|
||||||
4. Вставь блок команды **перед** `Возврат ПараметрыРегистрации`
|
4. Вставь блок команды **перед** `Возврат ПараметрыРегистрации`
|
||||||
5. Добавь обработчик:
|
5. Добавь обработчик:
|
||||||
- Для серверных обработчиков — в `ObjectModule.bsl`, область `ПрограммныйИнтерфейс`
|
- Для серверных обработчиков — в `ObjectModule.bsl`, область `ПрограммныйИнтерфейс`
|
||||||
- Для клиентских обработчиков — в модуль формы (найти через Glob: `src/{{ProcessorName}}/Forms/*/Ext/Form/Module.bsl`)
|
- Для клиентских обработчиков — в модуль формы (найти через Glob: `src/{{ProcessorName}}/Forms/*/Ext/Form/Module.bsl`)
|
||||||
6. Если обработчик (`ВыполнитьКоманду` / `Печать`) уже есть — добавь ветку, не создавай дубль процедуры
|
6. Если обработчик (`ВыполнитьКоманду` / `Печать`) уже есть — добавь ветку, не создавай дубль процедуры
|
||||||
7. Используй табы для отступов
|
7. Используй табы для отступов
|
||||||
|
|
||||||
## Пример
|
## Пример
|
||||||
|
|
||||||
Пользователь: `/epf-bsp-add-command МояОбработка ЗаказПокупателя серверный "Заказ покупателя"`
|
Пользователь: `/epf-bsp-add-command МояОбработка ЗаказПокупателя серверный "Заказ покупателя"`
|
||||||
|
|
||||||
В `СведенияОВнешнейОбработке()` перед `Возврат` добавится:
|
В `СведенияОВнешнейОбработке()` перед `Возврат` добавится:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
|
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
|
||||||
НоваяКоманда.Представление = НСтр("ru = 'Заказ покупателя'");
|
НоваяКоманда.Представление = НСтр("ru = 'Заказ покупателя'");
|
||||||
НоваяКоманда.Идентификатор = "ЗаказПокупателя";
|
НоваяКоманда.Идентификатор = "ЗаказПокупателя";
|
||||||
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
|
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
|
||||||
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
||||||
```
|
```
|
||||||
|
|
||||||
И в существующую процедуру `ВыполнитьКоманду` добавится блок обработки.
|
И в существующую процедуру `ВыполнитьКоманду` добавится блок обработки.
|
||||||
@@ -1,208 +1,208 @@
|
|||||||
---
|
---
|
||||||
name: epf-bsp-init
|
name: epf-bsp-init
|
||||||
description: Сформировать функцию `СведенияОВнешнейОбработке` в модуле объекта обработки — описание для подключения через подсистему БСП «Дополнительные отчёты и обработки». Используй когда нужно сделать обработку совместимой с БСП, подключаемой через «Дополнительные отчёты и обработки»
|
description: Сформировать функцию `СведенияОВнешнейОбработке` в модуле объекта обработки — описание для подключения через подсистему БСП «Дополнительные отчёты и обработки». Используй когда нужно сделать обработку совместимой с БСП, подключаемой через «Дополнительные отчёты и обработки»
|
||||||
argument-hint: <ProcessorName> <Вид>
|
argument-hint: <ProcessorName> <Вид>
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Read
|
- Read
|
||||||
- Edit
|
- Edit
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
# /epf-bsp-init — Регистрация обработки в БСП
|
# /epf-bsp-init — Регистрация обработки в БСП
|
||||||
|
|
||||||
Добавляет в модуль объекта обработки функцию `СведенияОВнешнейОбработке()`, необходимую для регистрации в подсистеме «Дополнительные отчёты и обработки» БСП.
|
Добавляет в модуль объекта обработки функцию `СведенияОВнешнейОбработке()`, необходимую для регистрации в подсистеме «Дополнительные отчёты и обработки» БСП.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/epf-bsp-init <ProcessorName> <Вид> [Назначение...]
|
/epf-bsp-init <ProcessorName> <Вид> [Назначение...]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|---------------|:------------:|--------------|---------------------------------------------------------|
|
|---------------|:------------:|--------------|---------------------------------------------------------|
|
||||||
| ProcessorName | да | — | Имя обработки (должна быть создана через `/epf-init`) |
|
| ProcessorName | да | — | Имя обработки (должна быть создана через `/epf-init`) |
|
||||||
| Вид | да | — | Вид обработки (см. маппинг ниже) |
|
| Вид | да | — | Вид обработки (см. маппинг ниже) |
|
||||||
| Назначение | * | — | Объекты метаданных для назначаемых видов |
|
| Назначение | * | — | Объекты метаданных для назначаемых видов |
|
||||||
| SrcDir | нет | `src` | Каталог исходников |
|
| SrcDir | нет | `src` | Каталог исходников |
|
||||||
|
|
||||||
\* Назначение обязательно для видов: ЗаполнениеОбъекта, Отчет, ПечатнаяФорма, СозданиеСвязанныхОбъектов.
|
\* Назначение обязательно для видов: ЗаполнениеОбъекта, Отчет, ПечатнаяФорма, СозданиеСвязанныхОбъектов.
|
||||||
|
|
||||||
## Маппинг вида обработки
|
## Маппинг вида обработки
|
||||||
|
|
||||||
Пользователь может указать вид в свободной форме. Определи нужный по контексту:
|
Пользователь может указать вид в свободной форме. Определи нужный по контексту:
|
||||||
|
|
||||||
| Пользователь пишет | Вид | API-метод |
|
| Пользователь пишет | Вид | API-метод |
|
||||||
|-------------------------------------------|----------------------------|----------------------------------------------|
|
|-------------------------------------------|----------------------------|----------------------------------------------|
|
||||||
| доп обработка, обработка, глобальная | ДополнительнаяОбработка | `ВидОбработкиДополнительнаяОбработка()` |
|
| доп обработка, обработка, глобальная | ДополнительнаяОбработка | `ВидОбработкиДополнительнаяОбработка()` |
|
||||||
| доп отчёт, глобальный отчёт | ДополнительныйОтчет | `ВидОбработкиДополнительныйОтчет()` |
|
| доп отчёт, глобальный отчёт | ДополнительныйОтчет | `ВидОбработкиДополнительныйОтчет()` |
|
||||||
| заполнение, заполнить | ЗаполнениеОбъекта | `ВидОбработкиЗаполнениеОбъекта()` |
|
| заполнение, заполнить | ЗаполнениеОбъекта | `ВидОбработкиЗаполнениеОбъекта()` |
|
||||||
| отчёт (назначаемый, для объекта) | Отчет | `ВидОбработкиОтчет()` |
|
| отчёт (назначаемый, для объекта) | Отчет | `ВидОбработкиОтчет()` |
|
||||||
| печатная форма, печать | ПечатнаяФорма | `ВидОбработкиПечатнаяФорма()` |
|
| печатная форма, печать | ПечатнаяФорма | `ВидОбработкиПечатнаяФорма()` |
|
||||||
| создание связанных объектов | СозданиеСвязанныхОбъектов | `ВидОбработкиСозданиеСвязанныхОбъектов()` |
|
| создание связанных объектов | СозданиеСвязанныхОбъектов | `ВидОбработкиСозданиеСвязанныхОбъектов()` |
|
||||||
|
|
||||||
## Тип команды по умолчанию
|
## Тип команды по умолчанию
|
||||||
|
|
||||||
| Вид | ТипКоманды по умолчанию |
|
| Вид | ТипКоманды по умолчанию |
|
||||||
|----------------------------|-------------------------------------------|
|
|----------------------------|-------------------------------------------|
|
||||||
| ДополнительнаяОбработка | `ТипКомандыОткрытиеФормы()` |
|
| ДополнительнаяОбработка | `ТипКомандыОткрытиеФормы()` |
|
||||||
| ДополнительныйОтчет | `ТипКомандыОткрытиеФормы()` |
|
| ДополнительныйОтчет | `ТипКомандыОткрытиеФормы()` |
|
||||||
| ЗаполнениеОбъекта | `ТипКомандыВызовСерверногоМетода()` |
|
| ЗаполнениеОбъекта | `ТипКомандыВызовСерверногоМетода()` |
|
||||||
| Отчет | `ТипКомандыОткрытиеФормы()` |
|
| Отчет | `ТипКомандыОткрытиеФормы()` |
|
||||||
| ПечатнаяФорма | `ТипКомандыВызовСерверногоМетода()` |
|
| ПечатнаяФорма | `ТипКомандыВызовСерверногоМетода()` |
|
||||||
| СозданиеСвязанныхОбъектов | `ТипКомандыВызовСерверногоМетода()` |
|
| СозданиеСвязанныхОбъектов | `ТипКомандыВызовСерверногоМетода()` |
|
||||||
|
|
||||||
## Шаблон: СведенияОВнешнейОбработке
|
## Шаблон: СведенияОВнешнейОбработке
|
||||||
|
|
||||||
Базовый шаблон — одинаковый для всех видов, отличаются только вызовы API-методов и условные секции.
|
Базовый шаблон — одинаковый для всех видов, отличаются только вызовы API-методов и условные секции.
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
Функция СведенияОВнешнейОбработке() Экспорт
|
Функция СведенияОВнешнейОбработке() Экспорт
|
||||||
|
|
||||||
МетаданныеОбработки = Метаданные();
|
МетаданныеОбработки = Метаданные();
|
||||||
|
|
||||||
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
|
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
|
||||||
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ВидОбработки}};
|
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ВидОбработки}};
|
||||||
ПараметрыРегистрации.Версия = "1.0";
|
ПараметрыРегистрации.Версия = "1.0";
|
||||||
|
|
||||||
{{СЕКЦИЯ_НАЗНАЧЕНИЕ}}
|
{{СЕКЦИЯ_НАЗНАЧЕНИЕ}}
|
||||||
|
|
||||||
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
|
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
|
||||||
НоваяКоманда.Представление = МетаданныеОбработки.Представление();
|
НоваяКоманда.Представление = МетаданныеОбработки.Представление();
|
||||||
НоваяКоманда.Идентификатор = МетаданныеОбработки.Имя;
|
НоваяКоманда.Идентификатор = МетаданныеОбработки.Имя;
|
||||||
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ТипКоманды}};
|
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.{{ТипКоманды}};
|
||||||
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
||||||
{{СЕКЦИЯ_МОДИФИКАТОР}}
|
{{СЕКЦИЯ_МОДИФИКАТОР}}
|
||||||
|
|
||||||
Возврат ПараметрыРегистрации;
|
Возврат ПараметрыРегистрации;
|
||||||
|
|
||||||
КонецФункции
|
КонецФункции
|
||||||
```
|
```
|
||||||
|
|
||||||
### Подстановки
|
### Подстановки
|
||||||
|
|
||||||
- `{{ВидОбработки}}` — API-метод из таблицы маппинга вида
|
- `{{ВидОбработки}}` — API-метод из таблицы маппинга вида
|
||||||
- `{{ТипКоманды}}` — API-метод из таблицы типа команды по умолчанию
|
- `{{ТипКоманды}}` — API-метод из таблицы типа команды по умолчанию
|
||||||
|
|
||||||
### Условные секции
|
### Условные секции
|
||||||
|
|
||||||
**`{{СЕКЦИЯ_НАЗНАЧЕНИЕ}}`** — только для назначаемых видов (ЗаполнениеОбъекта, Отчет, ПечатнаяФорма, СозданиеСвязанныхОбъектов). Одна строка на каждый объект:
|
**`{{СЕКЦИЯ_НАЗНАЧЕНИЕ}}`** — только для назначаемых видов (ЗаполнениеОбъекта, Отчет, ПечатнаяФорма, СозданиеСвязанныхОбъектов). Одна строка на каждый объект:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
ПараметрыРегистрации.Назначение.Добавить("Документ.СчетНаОплату");
|
ПараметрыРегистрации.Назначение.Добавить("Документ.СчетНаОплату");
|
||||||
```
|
```
|
||||||
|
|
||||||
Формат имени объекта: `ИмяКлассаОбъектаМетаданного.ИмяОбъекта` (например `Документ.СчетНаОплату`, `Справочник.Контрагенты`).
|
Формат имени объекта: `ИмяКлассаОбъектаМетаданного.ИмяОбъекта` (например `Документ.СчетНаОплату`, `Справочник.Контрагенты`).
|
||||||
|
|
||||||
Для глобальных видов (ДополнительнаяОбработка, ДополнительныйОтчет) — секция не нужна, удалить вместе с пустой строкой.
|
Для глобальных видов (ДополнительнаяОбработка, ДополнительныйОтчет) — секция не нужна, удалить вместе с пустой строкой.
|
||||||
|
|
||||||
**`{{СЕКЦИЯ_МОДИФИКАТОР}}`** — только для ПечатнаяФорма:
|
**`{{СЕКЦИЯ_МОДИФИКАТОР}}`** — только для ПечатнаяФорма:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
НоваяКоманда.Модификатор = "ПечатьMXL";
|
НоваяКоманда.Модификатор = "ПечатьMXL";
|
||||||
```
|
```
|
||||||
|
|
||||||
Для остальных видов — удалить вместе с пустой строкой.
|
Для остальных видов — удалить вместе с пустой строкой.
|
||||||
|
|
||||||
## Шаблоны серверных обработчиков
|
## Шаблоны серверных обработчиков
|
||||||
|
|
||||||
Для видов с типом команды `ВызовСерверногоМетода` добавь соответствующую процедуру-обработчик в ту же область `ПрограммныйИнтерфейс`, после `СведенияОВнешнейОбработке`.
|
Для видов с типом команды `ВызовСерверногоМетода` добавь соответствующую процедуру-обработчик в ту же область `ПрограммныйИнтерфейс`, после `СведенияОВнешнейОбработке`.
|
||||||
|
|
||||||
### Для ЗаполнениеОбъекта / СозданиеСвязанныхОбъектов
|
### Для ЗаполнениеОбъекта / СозданиеСвязанныхОбъектов
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт
|
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт
|
||||||
|
|
||||||
// TODO: Реализация
|
// TODO: Реализация
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
|
|
||||||
### Для ПечатнаяФорма
|
### Для ПечатнаяФорма
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
|
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
|
||||||
|
|
||||||
// TODO: Реализация
|
// TODO: Реализация
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
|
|
||||||
### Для ДополнительнаяОбработка / ДополнительныйОтчет (с ВызовСерверногоМетода)
|
### Для ДополнительнаяОбработка / ДополнительныйОтчет (с ВызовСерверногоМетода)
|
||||||
|
|
||||||
Если пользователь явно выбрал серверный метод вместо открытия формы:
|
Если пользователь явно выбрал серверный метод вместо открытия формы:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт
|
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт
|
||||||
|
|
||||||
// TODO: Реализация
|
// TODO: Реализация
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
```
|
```
|
||||||
|
|
||||||
Обрати внимание: у глобальных обработок нет параметра `ОбъектыНазначения`.
|
Обрати внимание: у глобальных обработок нет параметра `ОбъектыНазначения`.
|
||||||
|
|
||||||
## Инструкции
|
## Инструкции
|
||||||
|
|
||||||
1. Найди `ObjectModule.bsl` через Glob: `src/{{ProcessorName}}/Ext/ObjectModule.bsl`
|
1. Найди `ObjectModule.bsl` через Glob: `src/{{ProcessorName}}/Ext/ObjectModule.bsl`
|
||||||
2. Прочитай файл
|
2. Прочитай файл
|
||||||
3. Если `СведенияОВнешнейОбработке` уже есть — сообщи пользователю и не дублируй
|
3. Если `СведенияОВнешнейОбработке` уже есть — сообщи пользователю и не дублируй
|
||||||
4. Если файл не найден — предложи сначала вызвать `/epf-init`
|
4. Если файл не найден — предложи сначала вызвать `/epf-init`
|
||||||
5. Найди область `#Область ПрограммныйИнтерфейс` ... `#КонецОбласти`
|
5. Найди область `#Область ПрограммныйИнтерфейс` ... `#КонецОбласти`
|
||||||
6. Вставь функцию `СведенияОВнешнейОбработке()` внутрь этой области
|
6. Вставь функцию `СведенияОВнешнейОбработке()` внутрь этой области
|
||||||
7. Если вид требует серверный обработчик — вставь его тоже в эту область, после функции
|
7. Если вид требует серверный обработчик — вставь его тоже в эту область, после функции
|
||||||
8. Используй табы для отступов (как в исходном файле)
|
8. Используй табы для отступов (как в исходном файле)
|
||||||
|
|
||||||
## Пример
|
## Пример
|
||||||
|
|
||||||
Пользователь: `/epf-bsp-init МояОбработка печатная форма для Документ.СчетНаОплату`
|
Пользователь: `/epf-bsp-init МояОбработка печатная форма для Документ.СчетНаОплату`
|
||||||
|
|
||||||
Результат в `ObjectModule.bsl`:
|
Результат в `ObjectModule.bsl`:
|
||||||
|
|
||||||
```bsl
|
```bsl
|
||||||
#Область ОписаниеПеременных
|
#Область ОписаниеПеременных
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
|
|
||||||
#Область ПрограммныйИнтерфейс
|
#Область ПрограммныйИнтерфейс
|
||||||
|
|
||||||
Функция СведенияОВнешнейОбработке() Экспорт
|
Функция СведенияОВнешнейОбработке() Экспорт
|
||||||
|
|
||||||
МетаданныеОбработки = Метаданные();
|
МетаданныеОбработки = Метаданные();
|
||||||
|
|
||||||
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
|
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
|
||||||
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма();
|
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма();
|
||||||
ПараметрыРегистрации.Версия = "1.0";
|
ПараметрыРегистрации.Версия = "1.0";
|
||||||
|
|
||||||
ПараметрыРегистрации.Назначение.Добавить("Документ.СчетНаОплату");
|
ПараметрыРегистрации.Назначение.Добавить("Документ.СчетНаОплату");
|
||||||
|
|
||||||
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
|
НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
|
||||||
НоваяКоманда.Представление = МетаданныеОбработки.Представление();
|
НоваяКоманда.Представление = МетаданныеОбработки.Представление();
|
||||||
НоваяКоманда.Идентификатор = МетаданныеОбработки.Имя;
|
НоваяКоманда.Идентификатор = МетаданныеОбработки.Имя;
|
||||||
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
|
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();
|
||||||
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
НоваяКоманда.ПоказыватьОповещение = Ложь;
|
||||||
НоваяКоманда.Модификатор = "ПечатьMXL";
|
НоваяКоманда.Модификатор = "ПечатьMXL";
|
||||||
|
|
||||||
Возврат ПараметрыРегистрации;
|
Возврат ПараметрыРегистрации;
|
||||||
|
|
||||||
КонецФункции
|
КонецФункции
|
||||||
|
|
||||||
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
|
Процедура Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт
|
||||||
|
|
||||||
// TODO: Реализация
|
// TODO: Реализация
|
||||||
|
|
||||||
КонецПроцедуры
|
КонецПроцедуры
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
|
|
||||||
#Область СлужебныеПроцедурыИФункции
|
#Область СлужебныеПроцедурыИФункции
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
|
|
||||||
- Добавить ещё команду: `/epf-bsp-add-command`
|
- Добавить ещё команду: `/epf-bsp-add-command`
|
||||||
- Добавить форму: `/form-add`
|
- Добавить форму: `/form-add`
|
||||||
- Добавить макет: `/template-add`
|
- Добавить макет: `/template-add`
|
||||||
- Собрать EPF: `/epf-build`
|
- Собрать EPF: `/epf-build`
|
||||||
@@ -1,69 +1,69 @@
|
|||||||
---
|
---
|
||||||
name: epf-build
|
name: epf-build
|
||||||
description: Собрать внешнюю обработку 1С (EPF/ERF) из XML-исходников. Используй когда пользователь просит собрать, скомпилировать обработку или получить EPF/ERF файл из исходников
|
description: Собрать внешнюю обработку 1С (EPF/ERF) из XML-исходников. Используй когда пользователь просит собрать, скомпилировать обработку или получить EPF/ERF файл из исходников
|
||||||
argument-hint: <ProcessorName>
|
argument-hint: <ProcessorName>
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
# /epf-build — Сборка обработки
|
# /epf-build — Сборка обработки
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/epf-build <ProcessorName> [SrcDir] [OutDir]
|
/epf-build <ProcessorName> [SrcDir] [OutDir]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|---------------|:------------:|--------------|--------------------------------------|
|
|---------------|:------------:|--------------|--------------------------------------|
|
||||||
| ProcessorName | да | — | Имя обработки (имя корневого XML) |
|
| ProcessorName | да | — | Имя обработки (имя корневого XML) |
|
||||||
| SrcDir | нет | `src` | Каталог исходников |
|
| SrcDir | нет | `src` | Каталог исходников |
|
||||||
| OutDir | нет | `build` | Каталог для результата |
|
| OutDir | нет | `build` | Каталог для результата |
|
||||||
|
|
||||||
## Параметры подключения (опционально)
|
## Параметры подключения (опционально)
|
||||||
|
|
||||||
Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы.
|
Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы.
|
||||||
|
|
||||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
||||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
5. Если ветка не совпала — используй `default`
|
5. Если ветка не совпала — используй `default`
|
||||||
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для EPF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
|
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для EPF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
|
||||||
|
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников |
|
| `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников |
|
||||||
| `-OutputFile <путь>` | да | Путь к выходному EPF/ERF-файлу |
|
| `-OutputFile <путь>` | да | Путь к выходному EPF/ERF-файлу |
|
||||||
|
|
||||||
> `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных
|
> `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Сборка обработки (файловая база)
|
# Сборка обработки (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МояОбработка.xml" -OutputFile "build/МояОбработка.epf"
|
||||||
```
|
```
|
||||||
+225
-173
@@ -1,173 +1,225 @@
|
|||||||
# epf-build v1.0 — Build external data processor or report (EPF/ERF) from XML sources
|
# epf-build v1.4 — Build external data processor or report (EPF/ERF) from XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Сборка внешней обработки/отчёта 1С из XML-исходников
|
Сборка внешней обработки/отчёта 1С из XML-исходников
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Собирает EPF/ERF-файл из XML-исходников с помощью платформы 1С.
|
Собирает EPF/ERF-файл из XML-исходников с помощью платформы 1С.
|
||||||
Общий скрипт для epf-build и erf-build.
|
Общий скрипт для epf-build и erf-build.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER SourceFile
|
.PARAMETER SourceFile
|
||||||
Путь к корневому XML-файлу исходников
|
Путь к корневому XML-файлу исходников
|
||||||
|
|
||||||
.PARAMETER OutputFile
|
.PARAMETER OutputFile
|
||||||
Путь к выходному EPF/ERF-файлу
|
Путь к выходному EPF/ERF-файлу
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src\МояОбработка.xml" -OutputFile "build\МояОбработка.epf"
|
.\epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src\МояОбработка.xml" -OutputFile "build\МояОбработка.epf"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src\МойОтчёт.xml" -OutputFile "build\МойОтчёт.erf"
|
.\epf-build.ps1 -InfoBasePath "C:\Bases\MyDB" -SourceFile "src\МойОтчёт.xml" -OutputFile "build\МойОтчёт.erf"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$SourceFile,
|
[string]$SourceFile,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$OutputFile
|
[string]$OutputFile
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Auto-create stub database if no connection specified ---
|
|
||||||
$autoCreatedBase = $null
|
if (-not $V8Path) {
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
$V8Path = Find-ProjectV8Path
|
||||||
$sourceDir = Split-Path $SourceFile -Parent
|
}
|
||||||
$autoBasePath = Join-Path $env:TEMP "epf_stub_db_$(Get-Random)"
|
if (-not $V8Path) {
|
||||||
$stubScript = Join-Path $PSScriptRoot "stub-db-create.ps1"
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
Write-Host "No database specified. Creating temporary stub database..."
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
$stubArgs = "-SourceDir `"$sourceDir`" -V8Path `"$V8Path`" -TempBasePath `"$autoBasePath`""
|
Select-Object -First 1
|
||||||
$stubProc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$stubScript`" $stubArgs" -NoNewWindow -Wait -PassThru
|
if ($found) {
|
||||||
if ($stubProc.ExitCode -ne 0) {
|
$V8Path = $found.FullName
|
||||||
Write-Host "Error: failed to create stub database" -ForegroundColor Red
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
exit 1
|
} else {
|
||||||
}
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
$InfoBasePath = $autoBasePath
|
exit 1
|
||||||
$autoCreatedBase = $autoBasePath
|
}
|
||||||
}
|
}
|
||||||
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
# --- Validate source file ---
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
if (-not (Test-Path $SourceFile)) {
|
}
|
||||||
Write-Host "Error: source file not found: $SourceFile" -ForegroundColor Red
|
|
||||||
exit 1
|
if (-not (Test-Path $V8Path)) {
|
||||||
}
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
# --- Ensure output directory exists ---
|
}
|
||||||
$outDir = Split-Path $OutputFile -Parent
|
|
||||||
if ($outDir -and -not (Test-Path $outDir)) {
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
}
|
if ($engine -eq "ibcmd" -and $InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath or omit for stub)" -ForegroundColor Red
|
||||||
# --- Temp dir ---
|
exit 1
|
||||||
$tempDir = Join-Path $env:TEMP "epf_build_$(Get-Random)"
|
}
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
|
||||||
|
# --- Auto-create stub database if no connection specified ---
|
||||||
try {
|
$autoCreatedBase = $null
|
||||||
# --- Build arguments ---
|
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
$arguments = @("DESIGNER")
|
$sourceDir = Split-Path $SourceFile -Parent
|
||||||
|
$autoBasePath = Join-Path $env:TEMP "epf_stub_db_$(Get-Random)"
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
$stubScript = Join-Path $PSScriptRoot "stub-db-create.ps1"
|
||||||
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
Write-Host "No database specified. Creating temporary stub database..."
|
||||||
} else {
|
$stubArgs = "-SourceDir `"$sourceDir`" -V8Path `"$V8Path`" -TempBasePath `"$autoBasePath`""
|
||||||
$arguments += "/F", "`"$InfoBasePath`""
|
$stubProc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$stubScript`" $stubArgs" -NoNewWindow -Wait -PassThru
|
||||||
}
|
if ($stubProc.ExitCode -ne 0) {
|
||||||
|
Write-Host "Error: failed to create stub database" -ForegroundColor Red
|
||||||
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
exit 1
|
||||||
if ($Password) { $arguments += "/P`"$Password`"" }
|
}
|
||||||
|
$InfoBasePath = $autoBasePath
|
||||||
$arguments += "/LoadExternalDataProcessorOrReportFromFiles", "`"$SourceFile`"", "`"$OutputFile`""
|
$autoCreatedBase = $autoBasePath
|
||||||
|
}
|
||||||
# --- Output ---
|
|
||||||
$outFile = Join-Path $tempDir "build_log.txt"
|
# --- Validate source file ---
|
||||||
$arguments += "/Out", "`"$outFile`""
|
if (-not (Test-Path $SourceFile)) {
|
||||||
$arguments += "/DisableStartupDialogs"
|
Write-Host "Error: source file not found: $SourceFile" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
# --- Execute ---
|
}
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
# --- Ensure output directory exists ---
|
||||||
$exitCode = $process.ExitCode
|
$outDir = Split-Path $OutputFile -Parent
|
||||||
|
if ($outDir -and -not (Test-Path $outDir)) {
|
||||||
# --- Result ---
|
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
||||||
if ($exitCode -eq 0) {
|
}
|
||||||
Write-Host "Build completed successfully: $OutputFile" -ForegroundColor Green
|
|
||||||
} else {
|
# --- Temp dir ---
|
||||||
Write-Host "Error building (code: $exitCode)" -ForegroundColor Red
|
$tempDir = Join-Path $env:TEMP "epf_build_$(Get-Random)"
|
||||||
}
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
|
|
||||||
if (Test-Path $outFile) {
|
try {
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
if ($engine -eq "ibcmd") {
|
||||||
if ($logContent) {
|
# --- ibcmd branch: build EPF/ERF via config import --out ---
|
||||||
Write-Host "--- Log ---"
|
$srcDir = Split-Path $SourceFile -Parent
|
||||||
Write-Host $logContent
|
$arguments = @("infobase", "config", "import", "$srcDir", "--out=$OutputFile", "--db-path=$InfoBasePath")
|
||||||
Write-Host "--- End ---"
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
}
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
}
|
$arguments += "--data=$tempDir"
|
||||||
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
exit $exitCode
|
$output = & $V8Path @arguments 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
} finally {
|
if ($exitCode -eq 0) {
|
||||||
if (Test-Path $tempDir) {
|
Write-Host "External data processor/report built successfully: $OutputFile" -ForegroundColor Green
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
} else {
|
||||||
}
|
Write-Host "Error building external data processor/report (code: $exitCode)" -ForegroundColor Red
|
||||||
if ($autoCreatedBase -and (Test-Path $autoCreatedBase)) {
|
}
|
||||||
Remove-Item -Path $autoCreatedBase -Recurse -Force -ErrorAction SilentlyContinue
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
}
|
exit $exitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- 1cv8 branch ---
|
||||||
|
# --- Build arguments ---
|
||||||
|
$arguments = @("DESIGNER")
|
||||||
|
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
|
|
||||||
|
$arguments += "/LoadExternalDataProcessorOrReportFromFiles", "`"$SourceFile`"", "`"$OutputFile`""
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "build_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Build completed successfully: $OutputFile" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error building (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if ($autoCreatedBase -and (Test-Path $autoCreatedBase)) {
|
||||||
|
Remove-Item -Path $autoCreatedBase -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
-7
@@ -1,34 +1,68 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# epf-build v1.0 — Build external data processor or report (EPF/ERF) from XML sources
|
# epf-build v1.4 — Build external data processor or report (EPF/ERF) from XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
candidates = glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
v8path = _find_project_v8path()
|
||||||
|
if not v8path:
|
||||||
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates.sort()
|
v8path = max(candidates, key=_version_key)
|
||||||
return candidates[-1]
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return v8path
|
return v8path
|
||||||
|
|
||||||
|
|
||||||
@@ -51,6 +85,10 @@ def main():
|
|||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
if engine == "ibcmd" and args.InfoBaseServer and args.InfoBaseRef:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath or omit for stub)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# --- Auto-create stub database if no connection specified ---
|
# --- Auto-create stub database if no connection specified ---
|
||||||
auto_created_base = None
|
auto_created_base = None
|
||||||
@@ -84,6 +122,29 @@ def main():
|
|||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if engine == "ibcmd":
|
||||||
|
# --- ibcmd branch: build EPF/ERF via config import --out ---
|
||||||
|
src_dir = os.path.dirname(os.path.abspath(args.SourceFile))
|
||||||
|
arguments = ["infobase", "config", "import", src_dir, f"--out={args.OutputFile}", f"--db-path={args.InfoBasePath}"]
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"External data processor/report built successfully: {args.OutputFile}")
|
||||||
|
else:
|
||||||
|
print(f"Error building external data processor/report (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
# --- Build arguments ---
|
# --- Build arguments ---
|
||||||
arguments = ["DESIGNER"]
|
arguments = ["DESIGNER"]
|
||||||
|
|
||||||
+24
-1
@@ -1,4 +1,4 @@
|
|||||||
# stub-db-create v1.0 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
# stub-db-create v1.2 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -1252,6 +1252,29 @@ $propsXml </Properties>$childObjLine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- 5a. Stub via ibcmd (one call: create [--import --apply]) ---
|
||||||
|
$stubEngine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
|
if ($stubEngine -eq "ibcmd") {
|
||||||
|
Write-Host "Creating infobase (ibcmd): $TempBasePath"
|
||||||
|
$ibData = Join-Path $env:TEMP "stub_data_$(Get-Random)"
|
||||||
|
New-Item -ItemType Directory -Path $ibData -Force | Out-Null
|
||||||
|
$ibArgs = @("infobase", "create", "--db-path=$TempBasePath", "--create-database")
|
||||||
|
if ($hasRefTypes) { $ibArgs += "--import=$(Join-Path $TempBasePath 'cfg')", "--apply", "--force" }
|
||||||
|
$ibArgs += "--data=$ibData"
|
||||||
|
$ibOut = & $V8Path @ibArgs 2>&1
|
||||||
|
$ibRc = $LASTEXITCODE
|
||||||
|
Remove-Item -Path $ibData -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
if ($ibRc -ne 0) {
|
||||||
|
if ($ibOut) { Write-Host ($ibOut | Out-String) }
|
||||||
|
Write-Error "Failed to create stub infobase (code: $ibRc)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if ($hasRefTypes) { Remove-Item -Path (Join-Path $TempBasePath "cfg") -Recurse -Force -ErrorAction SilentlyContinue }
|
||||||
|
Write-Host "[OK] Stub database created: $TempBasePath"
|
||||||
|
Write-Host $TempBasePath
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
# --- 5. Create infobase ---
|
# --- 5. Create infobase ---
|
||||||
Write-Host "Creating infobase: $TempBasePath"
|
Write-Host "Creating infobase: $TempBasePath"
|
||||||
$createArgs = "CREATEINFOBASE File=`"$TempBasePath`" /DisableStartupDialogs"
|
$createArgs = "CREATEINFOBASE File=`"$TempBasePath`" /DisableStartupDialogs"
|
||||||
+27
-1
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# stub-db-create v1.0 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
# stub-db-create v1.2 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -1034,6 +1034,32 @@ def main():
|
|||||||
if register_columns:
|
if register_columns:
|
||||||
print('WARNING: Register column categories (Dimension/Resource/Attribute) are guessed. Form field bindings may not survive round-trip through a real database.')
|
print('WARNING: Register column categories (Dimension/Resource/Attribute) are guessed. Form field bindings may not survive round-trip through a real database.')
|
||||||
|
|
||||||
|
# Stub via ibcmd (one call: create [--import --apply])
|
||||||
|
stub_engine = "ibcmd" if os.path.basename(args.V8Path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
if stub_engine == "ibcmd":
|
||||||
|
import shutil
|
||||||
|
print(f'Creating infobase (ibcmd): {temp_base}')
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="stub_data_")
|
||||||
|
ib_args = [args.V8Path, 'infobase', 'create', f'--db-path={temp_base}', '--create-database']
|
||||||
|
if has_ref_types:
|
||||||
|
ib_args += [f'--import={os.path.join(temp_base, "cfg")}', '--apply', '--force']
|
||||||
|
ib_args.append(f'--data={ib_data}')
|
||||||
|
result = subprocess.run(ib_args, capture_output=True, encoding='utf-8', errors='replace')
|
||||||
|
shutil.rmtree(ib_data, ignore_errors=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
print(f'Failed to create stub infobase (code: {result.returncode})', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if has_ref_types:
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(os.path.join(temp_base, 'cfg'), ignore_errors=True)
|
||||||
|
print(f'[OK] Stub database created: {temp_base}')
|
||||||
|
print(temp_base)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# Create infobase
|
# Create infobase
|
||||||
print(f'Creating infobase: {temp_base}')
|
print(f'Creating infobase: {temp_base}')
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
@@ -1,69 +1,69 @@
|
|||||||
---
|
---
|
||||||
name: epf-dump
|
name: epf-dump
|
||||||
description: Разобрать EPF-файл обработки 1С (EPF/ERF) в XML-исходники. Используй когда пользователь просит разобрать, декомпилировать обработку, получить исходники из EPF/ERF файла
|
description: Разобрать EPF-файл обработки 1С (EPF/ERF) в XML-исходники. Используй когда пользователь просит разобрать, декомпилировать обработку, получить исходники из EPF/ERF файла
|
||||||
argument-hint: <EpfFile>
|
argument-hint: <EpfFile>
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
# /epf-dump — Разборка обработки
|
# /epf-dump — Разборка обработки
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/epf-dump <EpfFile> [OutDir]
|
/epf-dump <EpfFile> [OutDir]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|----------|:------------:|--------------|-------------------------------------|
|
|----------|:------------:|--------------|-------------------------------------|
|
||||||
| EpfFile | да | — | Путь к EPF-файлу |
|
| EpfFile | да | — | Путь к EPF-файлу |
|
||||||
| OutDir | нет | `src` | Каталог для выгрузки исходников |
|
| OutDir | нет | `src` | Каталог для выгрузки исходников |
|
||||||
|
|
||||||
## Параметры подключения (обязательно)
|
## Параметры подключения (обязательно)
|
||||||
|
|
||||||
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
|
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
|
||||||
|
|
||||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
||||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
5. Если ветка не совпала — используй `default`
|
5. Если ветка не совпала — используй `default`
|
||||||
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
|
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
|
||||||
|
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы, или полный путь к `1cv8.exe` / `ibcmd.exe` |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-InputFile <путь>` | да | Путь к EPF/ERF-файлу |
|
| `-InputFile <путь>` | да | Путь к EPF/ERF-файлу |
|
||||||
| `-OutputDir <путь>` | да | Каталог для выгрузки исходников |
|
| `-OutputDir <путь>` | да | Каталог для выгрузки исходников |
|
||||||
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
||||||
|
|
||||||
> `*` — обязательно хотя бы одно подключение. Без базы скрипт завершится с ошибкой (dump в пустой базе безвозвратно теряет ссылочные типы)
|
> `*` — обязательно хотя бы одно подключение. Без базы скрипт завершится с ошибкой (dump в пустой базе безвозвратно теряет ссылочные типы)
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Разборка обработки (файловая база)
|
# Разборка обработки (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МояОбработка.epf" -OutputDir "src"
|
||||||
```
|
```
|
||||||
+224
-167
@@ -1,167 +1,224 @@
|
|||||||
# epf-dump v1.0 — Dump external data processor or report (EPF/ERF) to XML sources
|
# epf-dump v1.4 — Dump external data processor or report (EPF/ERF) to XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Разборка внешней обработки/отчёта 1С в XML-исходники
|
Разборка внешней обработки/отчёта 1С в XML-исходники
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Разбирает EPF/ERF-файл во XML-исходники с помощью платформы 1С.
|
Разбирает EPF/ERF-файл во XML-исходники с помощью платформы 1С.
|
||||||
Общий скрипт для epf-dump и erf-dump.
|
Общий скрипт для epf-dump и erf-dump.
|
||||||
|
|
||||||
.PARAMETER V8Path
|
.PARAMETER V8Path
|
||||||
Путь к каталогу bin платформы или к 1cv8.exe
|
Путь к каталогу bin платформы или к 1cv8.exe
|
||||||
|
|
||||||
.PARAMETER InfoBasePath
|
.PARAMETER InfoBasePath
|
||||||
Путь к файловой информационной базе
|
Путь к файловой информационной базе
|
||||||
|
|
||||||
.PARAMETER InfoBaseServer
|
.PARAMETER InfoBaseServer
|
||||||
Сервер 1С (для серверной базы)
|
Сервер 1С (для серверной базы)
|
||||||
|
|
||||||
.PARAMETER InfoBaseRef
|
.PARAMETER InfoBaseRef
|
||||||
Имя базы на сервере
|
Имя базы на сервере
|
||||||
|
|
||||||
.PARAMETER UserName
|
.PARAMETER UserName
|
||||||
Имя пользователя 1С
|
Имя пользователя 1С
|
||||||
|
|
||||||
.PARAMETER Password
|
.PARAMETER Password
|
||||||
Пароль пользователя
|
Пароль пользователя
|
||||||
|
|
||||||
.PARAMETER InputFile
|
.PARAMETER InputFile
|
||||||
Путь к EPF/ERF-файлу
|
Путь к EPF/ERF-файлу
|
||||||
|
|
||||||
.PARAMETER OutputDir
|
.PARAMETER OutputDir
|
||||||
Каталог для выгрузки исходников
|
Каталог для выгрузки исходников
|
||||||
|
|
||||||
.PARAMETER Format
|
.PARAMETER Format
|
||||||
Формат выгрузки: Hierarchical или Plain (по умолчанию Hierarchical)
|
Формат выгрузки: Hierarchical или Plain (по умолчанию Hierarchical)
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build\МояОбработка.epf" -OutputDir "src"
|
.\epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build\МояОбработка.epf" -OutputDir "src"
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build\МойОтчёт.erf" -OutputDir "src"
|
.\epf-dump.ps1 -InfoBasePath "C:\Bases\MyDB" -InputFile "build\МойОтчёт.erf" -OutputDir "src"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$V8Path,
|
[string]$V8Path,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBasePath,
|
[string]$InfoBasePath,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseServer,
|
[string]$InfoBaseServer,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$InfoBaseRef,
|
[string]$InfoBaseRef,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$UserName,
|
[string]$UserName,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[string]$Password,
|
[string]$Password,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$InputFile,
|
[string]$InputFile,
|
||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$OutputDir,
|
[string]$OutputDir,
|
||||||
|
|
||||||
[Parameter(Mandatory=$false)]
|
[Parameter(Mandatory=$false)]
|
||||||
[ValidateSet("Hierarchical", "Plain")]
|
[ValidateSet("Hierarchical", "Plain")]
|
||||||
[string]$Format = "Hierarchical"
|
[string]$Format = "Hierarchical"
|
||||||
)
|
)
|
||||||
|
|
||||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
if (-not $V8Path) {
|
function Find-ProjectV8Path {
|
||||||
$found = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1
|
$dir = (Get-Location).Path
|
||||||
if ($found) {
|
while ($dir) {
|
||||||
$V8Path = $found.FullName
|
$pf = Join-Path $dir ".v8-project.json"
|
||||||
} else {
|
if (Test-Path $pf) {
|
||||||
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
try {
|
||||||
exit 1
|
$j = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
}
|
if ($j.v8path) { return [string]$j.v8path }
|
||||||
} elseif (Test-Path $V8Path -PathType Container) {
|
} catch {}
|
||||||
$V8Path = Join-Path $V8Path "1cv8.exe"
|
return $null
|
||||||
}
|
}
|
||||||
|
$parent = Split-Path $dir -Parent
|
||||||
if (-not (Test-Path $V8Path)) {
|
if (-not $parent -or $parent -eq $dir) { break }
|
||||||
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
$dir = $parent
|
||||||
exit 1
|
}
|
||||||
}
|
return $null
|
||||||
|
}
|
||||||
# --- Validate database connection ---
|
|
||||||
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
if (-not $V8Path) {
|
||||||
Write-Host "Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef" -ForegroundColor Red
|
$V8Path = Find-ProjectV8Path
|
||||||
Write-Host "Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly." -ForegroundColor Yellow
|
}
|
||||||
exit 1
|
if (-not $V8Path) {
|
||||||
}
|
$found = Get-ChildItem @("C:\Program Files\1cv8\*\bin\1cv8.exe", "C:\Program Files (x86)\1cv8\*\bin\1cv8.exe") -ErrorAction SilentlyContinue |
|
||||||
|
Sort-Object { try { [version]$_.Directory.Parent.Name } catch { [version]"0.0" } } -Descending |
|
||||||
# --- Validate input file ---
|
Select-Object -First 1
|
||||||
if (-not (Test-Path $InputFile)) {
|
if ($found) {
|
||||||
Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red
|
$V8Path = $found.FullName
|
||||||
exit 1
|
Write-Host "Auto-selected platform $($found.Directory.Parent.Name): $V8Path" -ForegroundColor Yellow
|
||||||
}
|
} else {
|
||||||
|
Write-Host "Error: 1cv8.exe not found. Specify -V8Path" -ForegroundColor Red
|
||||||
# --- Ensure output directory exists ---
|
exit 1
|
||||||
if (-not (Test-Path $OutputDir)) {
|
}
|
||||||
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
}
|
||||||
}
|
if (Test-Path $V8Path -PathType Container) {
|
||||||
|
$V8Path = Join-Path $V8Path "1cv8.exe"
|
||||||
# --- Temp dir ---
|
}
|
||||||
$tempDir = Join-Path $env:TEMP "epf_dump_$(Get-Random)"
|
|
||||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
if (-not (Test-Path $V8Path)) {
|
||||||
|
Write-Host "Error: 1cv8.exe not found at $V8Path" -ForegroundColor Red
|
||||||
try {
|
exit 1
|
||||||
# --- Build arguments ---
|
}
|
||||||
$arguments = @("DESIGNER")
|
|
||||||
|
# --- Validate database connection ---
|
||||||
if ($InfoBaseServer -and $InfoBaseRef) {
|
if (-not $InfoBasePath -and (-not $InfoBaseServer -or -not $InfoBaseRef)) {
|
||||||
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
Write-Host "Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef" -ForegroundColor Red
|
||||||
} else {
|
Write-Host "Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly." -ForegroundColor Yellow
|
||||||
$arguments += "/F", "`"$InfoBasePath`""
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
# --- Detect engine (ibcmd vs 1cv8) by exe name ---
|
||||||
if ($Password) { $arguments += "/P`"$Password`"" }
|
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||||
|
if ($engine -eq "ibcmd") {
|
||||||
$arguments += "/DumpExternalDataProcessorOrReportToFiles", "`"$OutputDir`"", "`"$InputFile`""
|
if (-not $InfoBasePath) {
|
||||||
$arguments += "-Format", $Format
|
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
# --- Output ---
|
}
|
||||||
$outFile = Join-Path $tempDir "dump_log.txt"
|
if ($Format -eq "Plain") {
|
||||||
$arguments += "/Out", "`"$outFile`""
|
Write-Host "Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red
|
||||||
$arguments += "/DisableStartupDialogs"
|
exit 1
|
||||||
|
}
|
||||||
# --- Execute ---
|
}
|
||||||
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
|
||||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
# --- Validate input file ---
|
||||||
$exitCode = $process.ExitCode
|
if (-not (Test-Path $InputFile)) {
|
||||||
|
Write-Host "Error: input file not found: $InputFile" -ForegroundColor Red
|
||||||
# --- Result ---
|
exit 1
|
||||||
if ($exitCode -eq 0) {
|
}
|
||||||
Write-Host "Dump completed successfully to: $OutputDir" -ForegroundColor Green
|
|
||||||
} else {
|
# --- Ensure output directory exists ---
|
||||||
Write-Host "Error dumping (code: $exitCode)" -ForegroundColor Red
|
if (-not (Test-Path $OutputDir)) {
|
||||||
}
|
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||||
|
}
|
||||||
if (Test-Path $outFile) {
|
|
||||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
# --- Temp dir ---
|
||||||
if ($logContent) {
|
$tempDir = Join-Path $env:TEMP "epf_dump_$(Get-Random)"
|
||||||
Write-Host "--- Log ---"
|
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||||
Write-Host $logContent
|
|
||||||
Write-Host "--- End ---"
|
try {
|
||||||
}
|
if ($engine -eq "ibcmd") {
|
||||||
}
|
# --- ibcmd branch: dump EPF/ERF via config export --file ---
|
||||||
|
$arguments = @("infobase", "config", "export", "--file=$InputFile", "$OutputDir", "--db-path=$InfoBasePath")
|
||||||
exit $exitCode
|
if ($UserName) { $arguments += "--user=$UserName" }
|
||||||
|
if ($Password) { $arguments += "--password=$Password" }
|
||||||
} finally {
|
$arguments += "--data=$tempDir"
|
||||||
if (Test-Path $tempDir) {
|
Write-Host "Running: ibcmd $($arguments -join ' ')"
|
||||||
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
$output = & $V8Path @arguments 2>&1
|
||||||
}
|
$exitCode = $LASTEXITCODE
|
||||||
}
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "External data processor/report dumped successfully to: $OutputDir" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error dumping external data processor/report (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
if ($output) { Write-Host ($output | Out-String) }
|
||||||
|
exit $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1cv8 branch ---
|
||||||
|
# --- Build arguments ---
|
||||||
|
$arguments = @("DESIGNER")
|
||||||
|
|
||||||
|
if ($InfoBaseServer -and $InfoBaseRef) {
|
||||||
|
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
|
||||||
|
} else {
|
||||||
|
$arguments += "/F", "`"$InfoBasePath`""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($UserName) { $arguments += "/N`"$UserName`"" }
|
||||||
|
if ($Password) { $arguments += "/P`"$Password`"" }
|
||||||
|
|
||||||
|
$arguments += "/DumpExternalDataProcessorOrReportToFiles", "`"$OutputDir`"", "`"$InputFile`""
|
||||||
|
$arguments += "-Format", $Format
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
$outFile = Join-Path $tempDir "dump_log.txt"
|
||||||
|
$arguments += "/Out", "`"$outFile`""
|
||||||
|
$arguments += "/DisableStartupDialogs"
|
||||||
|
|
||||||
|
# --- Execute ---
|
||||||
|
Write-Host "Running: 1cv8.exe $($arguments -join ' ')"
|
||||||
|
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||||
|
$exitCode = $process.ExitCode
|
||||||
|
|
||||||
|
# --- Result ---
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Host "Dump completed successfully to: $OutputDir" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Error dumping (code: $exitCode)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $outFile) {
|
||||||
|
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||||
|
if ($logContent) {
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Write-Host $logContent
|
||||||
|
Write-Host "--- End ---"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (Test-Path $tempDir) {
|
||||||
|
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
-7
@@ -1,34 +1,68 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# epf-dump v1.0 — Dump external data processor or report (EPF/ERF) to XML sources
|
# epf-dump v1.4 — Dump external data processor or report (EPF/ERF) to XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import atexit
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def _find_project_v8path():
|
||||||
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
|
d = os.getcwd()
|
||||||
|
while True:
|
||||||
|
pf = os.path.join(d, ".v8-project.json")
|
||||||
|
if os.path.isfile(pf):
|
||||||
|
try:
|
||||||
|
with open(pf, encoding="utf-8-sig") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = data.get("v8path")
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def _version_key(p):
|
||||||
|
"""Numeric sort key from version dir name (.../1cv8/<ver>/bin/1cv8.exe)."""
|
||||||
|
ver = os.path.basename(os.path.dirname(os.path.dirname(p)))
|
||||||
|
return [int(x) for x in re.findall(r"\d+", ver)]
|
||||||
|
|
||||||
|
|
||||||
def resolve_v8path(v8path):
|
def resolve_v8path(v8path):
|
||||||
"""Resolve path to 1cv8.exe."""
|
"""Resolve path to 1cv8.exe."""
|
||||||
if not v8path:
|
if not v8path:
|
||||||
candidates = glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
v8path = _find_project_v8path()
|
||||||
|
if not v8path:
|
||||||
|
candidates = (
|
||||||
|
glob.glob(r"C:\Program Files\1cv8\*\bin\1cv8.exe")
|
||||||
|
+ glob.glob(r"C:\Program Files (x86)\1cv8\*\bin\1cv8.exe")
|
||||||
|
)
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates.sort()
|
v8path = max(candidates, key=_version_key)
|
||||||
return candidates[-1]
|
ver = os.path.basename(os.path.dirname(os.path.dirname(v8path)))
|
||||||
|
print(f"Auto-selected platform {ver}: {v8path}")
|
||||||
else:
|
else:
|
||||||
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
print("Error: 1cv8.exe not found. Specify -V8Path", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif os.path.isdir(v8path):
|
if os.path.isdir(v8path):
|
||||||
v8path = os.path.join(v8path, "1cv8.exe")
|
v8path = os.path.join(v8path, "1cv8.exe")
|
||||||
|
|
||||||
if not os.path.isfile(v8path):
|
if not os.path.isfile(v8path):
|
||||||
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
print(f"Error: 1cv8.exe not found at {v8path}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return v8path
|
return v8path
|
||||||
|
|
||||||
|
|
||||||
@@ -57,12 +91,20 @@ def main():
|
|||||||
|
|
||||||
# --- Resolve V8Path ---
|
# --- Resolve V8Path ---
|
||||||
v8path = resolve_v8path(args.V8Path)
|
v8path = resolve_v8path(args.V8Path)
|
||||||
|
engine = "ibcmd" if os.path.basename(v8path).lower().startswith("ibcmd") else "1cv8"
|
||||||
|
|
||||||
# --- Validate database connection ---
|
# --- Validate database connection ---
|
||||||
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
if not args.InfoBasePath and (not args.InfoBaseServer or not args.InfoBaseRef):
|
||||||
print("Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef", file=sys.stderr)
|
print("Error: database connection required. Specify -InfoBasePath or -InfoBaseServer/-InfoBaseRef", file=sys.stderr)
|
||||||
print("Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly.")
|
print("Dump in an empty database loses reference types (CatalogRef, DocumentRef, etc.) irreversibly.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
if engine == "ibcmd":
|
||||||
|
if not args.InfoBasePath:
|
||||||
|
print("Error: ibcmd supports file infobases only (use -InfoBasePath)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if args.Format == "Plain":
|
||||||
|
print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# --- Validate input file ---
|
# --- Validate input file ---
|
||||||
if not os.path.isfile(args.InputFile):
|
if not os.path.isfile(args.InputFile):
|
||||||
@@ -78,6 +120,28 @@ def main():
|
|||||||
os.makedirs(temp_dir, exist_ok=True)
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if engine == "ibcmd":
|
||||||
|
# --- ibcmd branch: dump EPF/ERF via config export --file ---
|
||||||
|
arguments = ["infobase", "config", "export", f"--file={args.InputFile}", args.OutputDir, f"--db-path={args.InfoBasePath}"]
|
||||||
|
ib_data = tempfile.mkdtemp(prefix="ibcmd_data_")
|
||||||
|
atexit.register(shutil.rmtree, ib_data, ignore_errors=True)
|
||||||
|
if args.UserName:
|
||||||
|
arguments.append(f"--user={args.UserName}")
|
||||||
|
if args.Password:
|
||||||
|
arguments.append(f"--password={args.Password}")
|
||||||
|
arguments.append(f"--data={ib_data}")
|
||||||
|
print(f"Running: ibcmd {' '.join(arguments)}")
|
||||||
|
result = subprocess.run([v8path] + arguments, capture_output=True, encoding="utf-8", errors="replace")
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"External data processor/report dumped successfully to: {args.OutputDir}")
|
||||||
|
else:
|
||||||
|
print(f"Error dumping external data processor/report (code: {result.returncode})", file=sys.stderr)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
# --- Build arguments ---
|
# --- Build arguments ---
|
||||||
arguments = ["DESIGNER"]
|
arguments = ["DESIGNER"]
|
||||||
|
|
||||||
@@ -1,41 +1,41 @@
|
|||||||
---
|
---
|
||||||
name: epf-init
|
name: epf-init
|
||||||
description: Создать пустую внешнюю обработку 1С (scaffold XML-исходников). Используй когда нужно создать новую внешнюю обработку с нуля
|
description: Создать пустую внешнюю обработку 1С (scaffold XML-исходников). Используй когда нужно создать новую внешнюю обработку с нуля
|
||||||
argument-hint: <Name> [Synonym]
|
argument-hint: <Name> [Synonym]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Write
|
- Write
|
||||||
- Edit
|
- Edit
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
# /epf-init — Создание новой обработки
|
# /epf-init — Создание новой обработки
|
||||||
|
|
||||||
Генерирует минимальный набор XML-исходников для внешней обработки 1С: корневой файл метаданных и каталог обработки.
|
Генерирует минимальный набор XML-исходников для внешней обработки 1С: корневой файл метаданных и каталог обработки.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/epf-init <Name> [Synonym] [SrcDir]
|
/epf-init <Name> [Synonym] [SrcDir]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|-----------|:------------:|--------------|-------------------------------------|
|
|-----------|:------------:|--------------|-------------------------------------|
|
||||||
| Name | да | — | Имя обработки (латиница/кириллица) |
|
| Name | да | — | Имя обработки (латиница/кириллица) |
|
||||||
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
||||||
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"]
|
powershell.exe -NoProfile -File ".opencode/skills/epf-init/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
|
|
||||||
- Добавить форму: `/form-add`
|
- Добавить форму: `/form-add`
|
||||||
- Добавить макет: `/template-add`
|
- Добавить макет: `/template-add`
|
||||||
- Добавить справку: `/help-add`
|
- Добавить справку: `/help-add`
|
||||||
- Собрать EPF: `/epf-build`
|
- Собрать EPF: `/epf-build`
|
||||||
+90
-90
@@ -1,90 +1,90 @@
|
|||||||
# epf-init v1.1 — Init 1C external data processor scaffold
|
# epf-init v1.1 — Init 1C external data processor scaffold
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string]$Name,
|
[string]$Name,
|
||||||
|
|
||||||
[string]$Synonym = $Name,
|
[string]$Synonym = $Name,
|
||||||
|
|
||||||
[string]$SrcDir = "src"
|
[string]$SrcDir = "src"
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
$uuid1 = [guid]::NewGuid().ToString()
|
$uuid1 = [guid]::NewGuid().ToString()
|
||||||
$uuid2 = [guid]::NewGuid().ToString()
|
$uuid2 = [guid]::NewGuid().ToString()
|
||||||
$uuid3 = [guid]::NewGuid().ToString()
|
$uuid3 = [guid]::NewGuid().ToString()
|
||||||
$uuid4 = [guid]::NewGuid().ToString()
|
$uuid4 = [guid]::NewGuid().ToString()
|
||||||
|
|
||||||
$xml = @"
|
$xml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||||
<ExternalDataProcessor uuid="$uuid1">
|
<ExternalDataProcessor uuid="$uuid1">
|
||||||
<InternalInfo>
|
<InternalInfo>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>c3831ec8-d8d5-4f93-8a22-f9bfae07327f</xr:ClassId>
|
<xr:ClassId>c3831ec8-d8d5-4f93-8a22-f9bfae07327f</xr:ClassId>
|
||||||
<xr:ObjectId>$uuid2</xr:ObjectId>
|
<xr:ObjectId>$uuid2</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:GeneratedType name="ExternalDataProcessorObject.$Name" category="Object">
|
<xr:GeneratedType name="ExternalDataProcessorObject.$Name" category="Object">
|
||||||
<xr:TypeId>$uuid3</xr:TypeId>
|
<xr:TypeId>$uuid3</xr:TypeId>
|
||||||
<xr:ValueId>$uuid4</xr:ValueId>
|
<xr:ValueId>$uuid4</xr:ValueId>
|
||||||
</xr:GeneratedType>
|
</xr:GeneratedType>
|
||||||
</InternalInfo>
|
</InternalInfo>
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$Name</Name>
|
<Name>$Name</Name>
|
||||||
<Synonym>
|
<Synonym>
|
||||||
<v8:item>
|
<v8:item>
|
||||||
<v8:lang>ru</v8:lang>
|
<v8:lang>ru</v8:lang>
|
||||||
<v8:content>$Synonym</v8:content>
|
<v8:content>$Synonym</v8:content>
|
||||||
</v8:item>
|
</v8:item>
|
||||||
</Synonym>
|
</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
<DefaultForm/>
|
<DefaultForm/>
|
||||||
<AuxiliaryForm/>
|
<AuxiliaryForm/>
|
||||||
</Properties>
|
</Properties>
|
||||||
<ChildObjects/>
|
<ChildObjects/>
|
||||||
</ExternalDataProcessor>
|
</ExternalDataProcessor>
|
||||||
</MetaDataObject>
|
</MetaDataObject>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
$rootFile = Join-Path $SrcDir "$Name.xml"
|
$rootFile = Join-Path $SrcDir "$Name.xml"
|
||||||
$processorDir = Join-Path $SrcDir $Name
|
$processorDir = Join-Path $SrcDir $Name
|
||||||
|
|
||||||
if (Test-Path $rootFile) {
|
if (Test-Path $rootFile) {
|
||||||
Write-Error "Файл уже существует: $rootFile"
|
Write-Error "Файл уже существует: $rootFile"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not (Test-Path $SrcDir)) {
|
if (-not (Test-Path $SrcDir)) {
|
||||||
New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null
|
||||||
}
|
}
|
||||||
$extDir = Join-Path $processorDir "Ext"
|
$extDir = Join-Path $processorDir "Ext"
|
||||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
|
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
|
||||||
|
|
||||||
# --- Модуль объекта ---
|
# --- Модуль объекта ---
|
||||||
|
|
||||||
$moduleBsl = @"
|
$moduleBsl = @"
|
||||||
#Область ОписаниеПеременных
|
#Область ОписаниеПеременных
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
|
|
||||||
#Область ПрограммныйИнтерфейс
|
#Область ПрограммныйИнтерфейс
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
|
|
||||||
#Область СлужебныеПроцедурыИФункции
|
#Область СлужебныеПроцедурыИФункции
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
"@
|
"@
|
||||||
|
|
||||||
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
||||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
|
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
|
||||||
|
|
||||||
Write-Host "[OK] Создана обработка: $rootFile"
|
Write-Host "[OK] Создана обработка: $rootFile"
|
||||||
Write-Host " Каталог: $processorDir"
|
Write-Host " Каталог: $processorDir"
|
||||||
Write-Host " Модуль: $modulePath"
|
Write-Host " Модуль: $modulePath"
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
---
|
---
|
||||||
name: epf-validate
|
name: epf-validate
|
||||||
description: Валидация внешней обработки 1С (EPF). Используй после создания или модификации обработки для проверки корректности
|
description: Валидация внешней обработки 1С (EPF). Используй после создания или модификации обработки для проверки корректности
|
||||||
argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30]
|
argument-hint: <ObjectPath> [-Detailed] [-MaxErrors 30]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /epf-validate — валидация внешней обработки (EPF)
|
# /epf-validate — валидация внешней обработки (EPF)
|
||||||
|
|
||||||
Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Также работает для внешних отчётов (ERF).
|
Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов. Также работает для внешних отчётов (ERF).
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
| Параметр | Обяз. | Умолч. | Описание |
|
| Параметр | Обяз. | Умолч. | Описание |
|
||||||
|------------|:-----:|---------|-------------------------------------------------|
|
|------------|:-----:|---------|-------------------------------------------------|
|
||||||
| ObjectPath | да | — | Путь к корневому XML или каталогу обработки |
|
| ObjectPath | да | — | Путь к корневому XML или каталогу обработки |
|
||||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||||
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка"
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка/МояОбработка.xml"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-validate/scripts/epf-validate.ps1" -ObjectPath "src/МояОбработка/МояОбработка.xml"
|
||||||
```
|
```
|
||||||
|
|
||||||
+842
-842
File diff suppressed because it is too large
Load Diff
@@ -1,71 +1,71 @@
|
|||||||
---
|
---
|
||||||
name: erf-build
|
name: erf-build
|
||||||
description: Собрать внешний отчёт 1С (ERF) из XML-исходников. Используй когда пользователь просит собрать, скомпилировать отчёт или получить ERF файл из исходников
|
description: Собрать внешний отчёт 1С (ERF) из XML-исходников. Используй когда пользователь просит собрать, скомпилировать отчёт или получить ERF файл из исходников
|
||||||
argument-hint: <ReportName>
|
argument-hint: <ReportName>
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
# /erf-build — Сборка отчёта
|
# /erf-build — Сборка отчёта
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/erf-build <ReportName> [SrcDir] [OutDir]
|
/erf-build <ReportName> [SrcDir] [OutDir]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|------------|:------------:|--------------|--------------------------------------|
|
|------------|:------------:|--------------|--------------------------------------|
|
||||||
| ReportName | да | — | Имя отчёта (имя корневого XML) |
|
| ReportName | да | — | Имя отчёта (имя корневого XML) |
|
||||||
| SrcDir | нет | `src` | Каталог исходников |
|
| SrcDir | нет | `src` | Каталог исходников |
|
||||||
| OutDir | нет | `build` | Каталог для результата |
|
| OutDir | нет | `build` | Каталог для результата |
|
||||||
|
|
||||||
## Параметры подключения (опционально)
|
## Параметры подключения (опционально)
|
||||||
|
|
||||||
Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы.
|
Предпочтительно использовать конкретную базу — это надёжнее и не требует создания временной базы.
|
||||||
|
|
||||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
||||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
5. Если ветка не совпала — используй `default`
|
5. Если ветка не совпала — используй `default`
|
||||||
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для ERF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
|
6. Если `.v8-project.json` нет или база не найдена — не указывай параметры подключения: скрипт автоматически создаст временную базу. Для ERF со ссылочными типами (CatalogRef, DocumentRef и т.д.) генерируются заглушки метаданных. Временная база удаляется после сборки.
|
||||||
|
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
Используй общий скрипт из epf-build:
|
Используй общий скрипт из epf-build:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников |
|
| `-SourceFile <путь>` | да | Путь к корневому XML-файлу исходников |
|
||||||
| `-OutputFile <путь>` | да | Путь к выходному ERF-файлу |
|
| `-OutputFile <путь>` | да | Путь к выходному ERF-файлу |
|
||||||
|
|
||||||
> `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных
|
> `*` — опционально. Если не указано — автоматически создаётся временная база со заглушками метаданных
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Сборка отчёта (файловая база)
|
# Сборка отчёта (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" -InfoBasePath "C:\Bases\MyDB" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-build/scripts/epf-build.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -SourceFile "src/МойОтчёт.xml" -OutputFile "build/МойОтчёт.erf"
|
||||||
```
|
```
|
||||||
@@ -1,71 +1,71 @@
|
|||||||
---
|
---
|
||||||
name: erf-dump
|
name: erf-dump
|
||||||
description: Разобрать ERF-файл отчёта 1С в XML-исходники. Используй когда пользователь просит разобрать, декомпилировать отчёт, получить исходники из ERF файла
|
description: Разобрать ERF-файл отчёта 1С в XML-исходники. Используй когда пользователь просит разобрать, декомпилировать отчёт, получить исходники из ERF файла
|
||||||
argument-hint: <ErfFile>
|
argument-hint: <ErfFile>
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
# /erf-dump — Разборка отчёта
|
# /erf-dump — Разборка отчёта
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/erf-dump <ErfFile> [OutDir]
|
/erf-dump <ErfFile> [OutDir]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|----------|:------------:|--------------|-------------------------------------|
|
|----------|:------------:|--------------|-------------------------------------|
|
||||||
| ErfFile | да | — | Путь к ERF-файлу |
|
| ErfFile | да | — | Путь к ERF-файлу |
|
||||||
| OutDir | нет | `src` | Каталог для выгрузки исходников |
|
| OutDir | нет | `src` | Каталог для выгрузки исходников |
|
||||||
|
|
||||||
## Параметры подключения (обязательно)
|
## Параметры подключения (обязательно)
|
||||||
|
|
||||||
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
|
Для разборки EPF/ERF требуется информационная база с конфигурацией. Без базы ссылочные типы безвозвратно теряются.
|
||||||
|
|
||||||
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
1. Прочитай `.v8-project.json` из корня проекта. Возьми `v8path` и разреши базу:
|
||||||
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
2. Если пользователь указал параметры подключения (путь, сервер) — используй напрямую
|
||||||
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
3. Если указал базу по имени — ищи по id / alias / name в `.v8-project.json`
|
||||||
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
4. Если не указал — сопоставь текущую ветку Git с `databases[].branches`
|
||||||
5. Если ветка не совпала — используй `default`
|
5. Если ветка не совпала — используй `default`
|
||||||
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
|
6. Если `.v8-project.json` нет или база не найдена — **сообщи пользователю об ошибке**. Для dump база обязательна: в пустой базе ссылочные типы (CatalogRef, DocumentRef и т.д.) безвозвратно сбрасываются в строки. Предложи указать базу или зарегистрировать через `/db-list add`.
|
||||||
|
|
||||||
Если `v8path` не задан — автоопределение: `Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort -Desc | Select -First 1`
|
Если `v8path` не задан — скрипт сам попытается определить платформу (`.v8-project.json` → Program Files).
|
||||||
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
Если использованная база не зарегистрирована — после выполнения предложи добавить через `/db-list add`.
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
Используй общий скрипт из epf-dump:
|
Используй общий скрипт из epf-dump:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" <параметры>
|
powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" <параметры>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Параметры скрипта
|
### Параметры скрипта
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|----------|:------------:|----------|
|
|----------|:------------:|----------|
|
||||||
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
| `-V8Path <путь>` | нет | Каталог bin платформы (или полный путь к 1cv8.exe) |
|
||||||
| `-InfoBasePath <путь>` | * | Файловая база |
|
| `-InfoBasePath <путь>` | * | Файловая база |
|
||||||
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
| `-InfoBaseServer <сервер>` | * | Сервер 1С (для серверной базы) |
|
||||||
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
| `-InfoBaseRef <имя>` | * | Имя базы на сервере |
|
||||||
| `-UserName <имя>` | нет | Имя пользователя |
|
| `-UserName <имя>` | нет | Имя пользователя |
|
||||||
| `-Password <пароль>` | нет | Пароль |
|
| `-Password <пароль>` | нет | Пароль |
|
||||||
| `-InputFile <путь>` | да | Путь к ERF-файлу |
|
| `-InputFile <путь>` | да | Путь к ERF-файлу |
|
||||||
| `-OutputDir <путь>` | да | Каталог для выгрузки исходников |
|
| `-OutputDir <путь>` | да | Каталог для выгрузки исходников |
|
||||||
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
| `-Format <формат>` | нет | `Hierarchical` (по умолч.) / `Plain` |
|
||||||
|
|
||||||
> `*` — обязательно хотя бы одно подключение. Без базы скрипт завершится с ошибкой (dump в пустой базе безвозвратно теряет ссылочные типы)
|
> `*` — обязательно хотя бы одно подключение. Без базы скрипт завершится с ошибкой (dump в пустой базе безвозвратно теряет ссылочные типы)
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Разборка отчёта (файловая база)
|
# Разборка отчёта (файловая база)
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" -InfoBasePath "C:\Bases\MyDB" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
||||||
|
|
||||||
# Серверная база
|
# Серверная база
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/../epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
powershell.exe -NoProfile -File ".opencode/skills/epf-dump/scripts/epf-dump.ps1" -InfoBaseServer "srv01" -InfoBaseRef "MyDB" -UserName "Admin" -Password "secret" -InputFile "build/МойОтчёт.erf" -OutputDir "src"
|
||||||
```
|
```
|
||||||
@@ -1,42 +1,42 @@
|
|||||||
---
|
---
|
||||||
name: erf-init
|
name: erf-init
|
||||||
description: Создать пустой внешний отчёт 1С (scaffold XML-исходников). Используй когда нужно создать новый внешний отчёт с нуля
|
description: Создать пустой внешний отчёт 1С (scaffold XML-исходников). Используй когда нужно создать новый внешний отчёт с нуля
|
||||||
argument-hint: <Name> [Synonym] [--with-skd]
|
argument-hint: <Name> [Synonym] [--with-skd]
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
- Write
|
- Write
|
||||||
- Edit
|
- Edit
|
||||||
- Glob
|
- Glob
|
||||||
- Grep
|
- Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
# /erf-init — Создание нового отчёта
|
# /erf-init — Создание нового отчёта
|
||||||
|
|
||||||
Генерирует минимальный набор XML-исходников для внешнего отчёта 1С: корневой файл метаданных и каталог отчёта.
|
Генерирует минимальный набор XML-исходников для внешнего отчёта 1С: корневой файл метаданных и каталог отчёта.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/erf-init <Name> [Synonym] [SrcDir] [--with-skd]
|
/erf-init <Name> [Synonym] [SrcDir] [--with-skd]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|-----------|:------------:|--------------|---------------------------------------|
|
|-----------|:------------:|--------------|---------------------------------------|
|
||||||
| Name | да | — | Имя отчёта (латиница/кириллица) |
|
| Name | да | — | Имя отчёта (латиница/кириллица) |
|
||||||
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
||||||
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
||||||
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema |
|
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD]
|
powershell.exe -NoProfile -File ".opencode/skills/erf-init/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
|
|
||||||
- Добавить форму: `/form-add`
|
- Добавить форму: `/form-add`
|
||||||
- Добавить макет: `/template-add`
|
- Добавить макет: `/template-add`
|
||||||
- Добавить справку: `/help-add`
|
- Добавить справку: `/help-add`
|
||||||
- Собрать ERF: `/erf-build`
|
- Собрать ERF: `/erf-build`
|
||||||
+180
-180
@@ -1,180 +1,180 @@
|
|||||||
# erf-init v1.1 — Init 1C external report scaffold
|
# erf-init v1.1 — Init 1C external report scaffold
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string]$Name,
|
[string]$Name,
|
||||||
|
|
||||||
[string]$Synonym = $Name,
|
[string]$Synonym = $Name,
|
||||||
|
|
||||||
[string]$SrcDir = "src",
|
[string]$SrcDir = "src",
|
||||||
|
|
||||||
[switch]$WithSKD
|
[switch]$WithSKD
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
$uuid1 = [guid]::NewGuid().ToString()
|
$uuid1 = [guid]::NewGuid().ToString()
|
||||||
$uuid2 = [guid]::NewGuid().ToString()
|
$uuid2 = [guid]::NewGuid().ToString()
|
||||||
$uuid3 = [guid]::NewGuid().ToString()
|
$uuid3 = [guid]::NewGuid().ToString()
|
||||||
$uuid4 = [guid]::NewGuid().ToString()
|
$uuid4 = [guid]::NewGuid().ToString()
|
||||||
|
|
||||||
# --- Формируем Properties ---
|
# --- Формируем Properties ---
|
||||||
|
|
||||||
$mainDCSValue = ""
|
$mainDCSValue = ""
|
||||||
$childObjectsContent = ""
|
$childObjectsContent = ""
|
||||||
|
|
||||||
if ($WithSKD) {
|
if ($WithSKD) {
|
||||||
$mainDCSValue = "ExternalReport.$Name.Template.ОсновнаяСхемаКомпоновкиДанных"
|
$mainDCSValue = "ExternalReport.$Name.Template.ОсновнаяСхемаКомпоновкиДанных"
|
||||||
$childObjectsContent = @"
|
$childObjectsContent = @"
|
||||||
|
|
||||||
<Template>ОсновнаяСхемаКомпоновкиДанных</Template>
|
<Template>ОсновнаяСхемаКомпоновкиДанных</Template>
|
||||||
|
|
||||||
"@
|
"@
|
||||||
}
|
}
|
||||||
|
|
||||||
$mainDCSElement = if ($mainDCSValue) {
|
$mainDCSElement = if ($mainDCSValue) {
|
||||||
"<MainDataCompositionSchema>$mainDCSValue</MainDataCompositionSchema>"
|
"<MainDataCompositionSchema>$mainDCSValue</MainDataCompositionSchema>"
|
||||||
} else {
|
} else {
|
||||||
"<MainDataCompositionSchema/>"
|
"<MainDataCompositionSchema/>"
|
||||||
}
|
}
|
||||||
|
|
||||||
$childObjectsXml = if ($childObjectsContent) {
|
$childObjectsXml = if ($childObjectsContent) {
|
||||||
"<ChildObjects>$childObjectsContent</ChildObjects>"
|
"<ChildObjects>$childObjectsContent</ChildObjects>"
|
||||||
} else {
|
} else {
|
||||||
"<ChildObjects/>"
|
"<ChildObjects/>"
|
||||||
}
|
}
|
||||||
|
|
||||||
$xml = @"
|
$xml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||||
<ExternalReport uuid="$uuid1">
|
<ExternalReport uuid="$uuid1">
|
||||||
<InternalInfo>
|
<InternalInfo>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
<xr:ClassId>e41aff26-25cf-4bb6-b6c1-3f478a75f374</xr:ClassId>
|
<xr:ClassId>e41aff26-25cf-4bb6-b6c1-3f478a75f374</xr:ClassId>
|
||||||
<xr:ObjectId>$uuid2</xr:ObjectId>
|
<xr:ObjectId>$uuid2</xr:ObjectId>
|
||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
<xr:GeneratedType name="ExternalReportObject.$Name" category="Object">
|
<xr:GeneratedType name="ExternalReportObject.$Name" category="Object">
|
||||||
<xr:TypeId>$uuid3</xr:TypeId>
|
<xr:TypeId>$uuid3</xr:TypeId>
|
||||||
<xr:ValueId>$uuid4</xr:ValueId>
|
<xr:ValueId>$uuid4</xr:ValueId>
|
||||||
</xr:GeneratedType>
|
</xr:GeneratedType>
|
||||||
</InternalInfo>
|
</InternalInfo>
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$Name</Name>
|
<Name>$Name</Name>
|
||||||
<Synonym>
|
<Synonym>
|
||||||
<v8:item>
|
<v8:item>
|
||||||
<v8:lang>ru</v8:lang>
|
<v8:lang>ru</v8:lang>
|
||||||
<v8:content>$Synonym</v8:content>
|
<v8:content>$Synonym</v8:content>
|
||||||
</v8:item>
|
</v8:item>
|
||||||
</Synonym>
|
</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
<DefaultForm/>
|
<DefaultForm/>
|
||||||
<AuxiliaryForm/>
|
<AuxiliaryForm/>
|
||||||
$mainDCSElement
|
$mainDCSElement
|
||||||
<DefaultSettingsForm/>
|
<DefaultSettingsForm/>
|
||||||
<AuxiliarySettingsForm/>
|
<AuxiliarySettingsForm/>
|
||||||
<DefaultVariantForm/>
|
<DefaultVariantForm/>
|
||||||
<VariantsStorage/>
|
<VariantsStorage/>
|
||||||
<SettingsStorage/>
|
<SettingsStorage/>
|
||||||
</Properties>
|
</Properties>
|
||||||
$childObjectsXml
|
$childObjectsXml
|
||||||
</ExternalReport>
|
</ExternalReport>
|
||||||
</MetaDataObject>
|
</MetaDataObject>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
$rootFile = Join-Path $SrcDir "$Name.xml"
|
$rootFile = Join-Path $SrcDir "$Name.xml"
|
||||||
$reportDir = Join-Path $SrcDir $Name
|
$reportDir = Join-Path $SrcDir $Name
|
||||||
|
|
||||||
if (Test-Path $rootFile) {
|
if (Test-Path $rootFile) {
|
||||||
Write-Error "Файл уже существует: $rootFile"
|
Write-Error "Файл уже существует: $rootFile"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not (Test-Path $SrcDir)) {
|
if (-not (Test-Path $SrcDir)) {
|
||||||
New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null
|
||||||
}
|
}
|
||||||
$extDir = Join-Path $reportDir "Ext"
|
$extDir = Join-Path $reportDir "Ext"
|
||||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
|
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
|
||||||
|
|
||||||
# --- Модуль объекта ---
|
# --- Модуль объекта ---
|
||||||
|
|
||||||
$moduleBsl = @"
|
$moduleBsl = @"
|
||||||
#Область ОписаниеПеременных
|
#Область ОписаниеПеременных
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
|
|
||||||
#Область ПрограммныйИнтерфейс
|
#Область ПрограммныйИнтерфейс
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
|
|
||||||
#Область СлужебныеПроцедурыИФункции
|
#Область СлужебныеПроцедурыИФункции
|
||||||
|
|
||||||
#КонецОбласти
|
#КонецОбласти
|
||||||
"@
|
"@
|
||||||
|
|
||||||
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
||||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
|
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
|
||||||
|
|
||||||
Write-Host "[OK] Создан отчёт: $rootFile"
|
Write-Host "[OK] Создан отчёт: $rootFile"
|
||||||
Write-Host " Каталог: $reportDir"
|
Write-Host " Каталог: $reportDir"
|
||||||
Write-Host " Модуль: $modulePath"
|
Write-Host " Модуль: $modulePath"
|
||||||
|
|
||||||
# --- СКД-макет (если --WithSKD) ---
|
# --- СКД-макет (если --WithSKD) ---
|
||||||
|
|
||||||
if ($WithSKD) {
|
if ($WithSKD) {
|
||||||
$templatesDir = Join-Path $reportDir "Templates"
|
$templatesDir = Join-Path $reportDir "Templates"
|
||||||
$skdName = "ОсновнаяСхемаКомпоновкиДанных"
|
$skdName = "ОсновнаяСхемаКомпоновкиДанных"
|
||||||
$skdMetaPath = Join-Path $templatesDir "$skdName.xml"
|
$skdMetaPath = Join-Path $templatesDir "$skdName.xml"
|
||||||
$skdExtDir = Join-Path (Join-Path $templatesDir $skdName) "Ext"
|
$skdExtDir = Join-Path (Join-Path $templatesDir $skdName) "Ext"
|
||||||
New-Item -ItemType Directory -Path $skdExtDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $skdExtDir -Force | Out-Null
|
||||||
|
|
||||||
$skdUuid = [guid]::NewGuid().ToString()
|
$skdUuid = [guid]::NewGuid().ToString()
|
||||||
|
|
||||||
$skdMetaXml = @"
|
$skdMetaXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||||
<Template uuid="$skdUuid">
|
<Template uuid="$skdUuid">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$skdName</Name>
|
<Name>$skdName</Name>
|
||||||
<Synonym>
|
<Synonym>
|
||||||
<v8:item>
|
<v8:item>
|
||||||
<v8:lang>ru</v8:lang>
|
<v8:lang>ru</v8:lang>
|
||||||
<v8:content>Основная схема компоновки данных</v8:content>
|
<v8:content>Основная схема компоновки данных</v8:content>
|
||||||
</v8:item>
|
</v8:item>
|
||||||
</Synonym>
|
</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
<TemplateType>DataCompositionSchema</TemplateType>
|
<TemplateType>DataCompositionSchema</TemplateType>
|
||||||
</Properties>
|
</Properties>
|
||||||
</Template>
|
</Template>
|
||||||
</MetaDataObject>
|
</MetaDataObject>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($skdMetaPath, $skdMetaXml, $enc)
|
[System.IO.File]::WriteAllText($skdMetaPath, $skdMetaXml, $enc)
|
||||||
|
|
||||||
$skdContent = @"
|
$skdContent = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
|
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
|
||||||
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
|
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
|
||||||
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
|
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
|
||||||
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
|
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
|
||||||
xmlns:v8="http://v8.1c.ru/8.1/data/core"
|
xmlns:v8="http://v8.1c.ru/8.1/data/core"
|
||||||
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
|
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
|
||||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<dataSource>
|
<dataSource>
|
||||||
<name>ИсточникДанных1</name>
|
<name>ИсточникДанных1</name>
|
||||||
<dataSourceType>Local</dataSourceType>
|
<dataSourceType>Local</dataSourceType>
|
||||||
</dataSource>
|
</dataSource>
|
||||||
</DataCompositionSchema>
|
</DataCompositionSchema>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
$skdFilePath = Join-Path $skdExtDir "Template.xml"
|
$skdFilePath = Join-Path $skdExtDir "Template.xml"
|
||||||
[System.IO.File]::WriteAllText($skdFilePath, $skdContent, $enc)
|
[System.IO.File]::WriteAllText($skdFilePath, $skdContent, $enc)
|
||||||
|
|
||||||
Write-Host " СКД: $skdMetaPath"
|
Write-Host " СКД: $skdMetaPath"
|
||||||
Write-Host " Тело: $skdFilePath"
|
Write-Host " Тело: $skdFilePath"
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user