diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1
index e5a0b997..a75eed03 100644
--- a/.claude/skills/meta-compile/scripts/meta-compile.ps1
+++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1
@@ -1,4 +1,4 @@
-# meta-compile v1.70 — Compile 1C metadata object from JSON
+# meta-compile v1.71 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -267,7 +267,7 @@ $script:validEnumValues = @{
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
- "ReuseSessions" = @("DontUse","AutoUse")
+ "ReuseSessions" = @("DontUse","Use","AutoUse")
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
"SubordinationUse" = @("ToItems","ToFolders","ToFoldersAndItems")
@@ -3800,7 +3800,7 @@ function Emit-HTTPServiceProperties {
X "$i$(Esc-Xml $objName)"
Emit-MLText $i "Synonym" $synonym
- X "$i"
+ if ($def.comment) { X "$i$(Esc-Xml "$($def.comment)")" } else { X "$i" }
$rootURL = if ($def.rootURL) { "$($def.rootURL)" } else { $objName.ToLower() }
X "$i$(Esc-Xml $rootURL)"
@@ -3881,15 +3881,20 @@ function Emit-URLTemplate {
$tmplSynonym = Split-CamelCase $tmplName
$template = ""
- $methods = @{}
+ # [ordered], а не @{}: порядок методов должен совпадать с DSL (он же порядок исходного XML),
+ # иначе PS и py расходятся между собой и с выгрузкой платформы.
+ $methods = [ordered]@{}
+ $tmplComment = ""
if ($tmplDef -is [string]) {
$template = "$tmplDef"
} else {
$template = if ($tmplDef.template) { "$($tmplDef.template)" } else { "/$($tmplName.ToLower())" }
+ if ($tmplDef.synonym) { $tmplSynonym = "$($tmplDef.synonym)" }
+ if ($tmplDef.comment) { $tmplComment = "$($tmplDef.comment)" }
if ($tmplDef.methods) {
$tmplDef.methods.PSObject.Properties | ForEach-Object {
- $methods[$_.Name] = "$($_.Value)"
+ $methods[$_.Name] = $_.Value # строка (HTTP-метод) ЛИБО объект {httpMethod, handler, synonym, comment}
}
}
}
@@ -3898,6 +3903,7 @@ function Emit-URLTemplate {
X "$indent`t"
X "$indent`t`t$(Esc-Xml $tmplName)"
Emit-MLText "$indent`t`t" "Synonym" $tmplSynonym
+ if ($tmplComment) { X "$indent`t`t$(Esc-Xml $tmplComment)" } else { X "$indent`t`t" }
X "$indent`t`t$(Esc-Xml $template)"
X "$indent`t"
@@ -3905,14 +3911,25 @@ function Emit-URLTemplate {
X "$indent`t"
foreach ($methodName in $methods.Keys) {
$methodUuid = New-Guid-String
- $httpMethod = $methods[$methodName]
- $methodSynonym = Split-CamelCase $methodName
- $handler = "${tmplName}${methodName}"
+ $mDef = $methods[$methodName]
+ # Строка — сокращение "только HTTP-метод"; объект — полная форма. Обработчик по умолчанию
+ # выводится как ИмяШаблона+ИмяМетода, но в реальных конфигурациях он произвольный,
+ # поэтому задаётся явно ключом handler.
+ if ($mDef -is [string]) {
+ $httpMethod = "$mDef"; $handler = "${tmplName}${methodName}"
+ $methodSynonym = Split-CamelCase $methodName; $methodComment = ""
+ } else {
+ $httpMethod = if ($mDef.httpMethod) { "$($mDef.httpMethod)" } else { 'GET' }
+ $handler = if ($mDef.handler) { "$($mDef.handler)" } else { "${tmplName}${methodName}" }
+ $methodSynonym = if ($mDef.synonym) { "$($mDef.synonym)" } else { Split-CamelCase $methodName }
+ $methodComment = if ($mDef.comment) { "$($mDef.comment)" } else { "" }
+ }
X "$indent`t`t"
X "$indent`t`t`t"
X "$indent`t`t`t`t$(Esc-Xml $methodName)"
Emit-MLText "$indent`t`t`t`t" "Synonym" $methodSynonym
+ if ($methodComment) { X "$indent`t`t`t`t$(Esc-Xml $methodComment)" } else { X "$indent`t`t`t`t" }
X "$indent`t`t`t`t$httpMethod"
X "$indent`t`t`t`t$(Esc-Xml $handler)"
X "$indent`t`t`t"
@@ -4369,7 +4386,9 @@ if ($objType -in @("FilterCriterion", "SettingsStorage")) {
# --- HTTPService: URLTemplates ---
if ($objType -eq "HTTPService") {
- $urlTemplates = @{}
+ # [ordered]: порядок шаблонов — как в DSL (он же порядок исходного XML). @{} давало произвольный
+ # порядок в PS и расходилось с py, который сортировал; обе ветки приведены к порядку DSL.
+ $urlTemplates = [ordered]@{}
if ($def.urlTemplates) {
$def.urlTemplates.PSObject.Properties | ForEach-Object {
$urlTemplates[$_.Name] = $_.Value
diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py
index 4014521f..470cdd00 100644
--- a/.claude/skills/meta-compile/scripts/meta-compile.py
+++ b/.claude/skills/meta-compile/scripts/meta-compile.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# meta-compile v1.70 — Compile 1C metadata object from JSON
+# meta-compile v1.71 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -416,7 +416,7 @@ valid_enum_values = {
'RegisterRecordsDeletion': ['AutoDelete', 'AutoDeleteOnUnpost', 'AutoDeleteOff'],
'RegisterRecordsWritingOnPost': ['WriteModified', 'WriteSelected', 'WriteAll'],
'ReturnValuesReuse': ['DontUse', 'DuringRequest', 'DuringSession'],
- 'ReuseSessions': ['DontUse', 'AutoUse'],
+ 'ReuseSessions': ['DontUse', 'Use', 'AutoUse'],
'FillChecking': ['DontCheck', 'ShowError', 'ShowWarning'],
'Indexing': ['DontIndex', 'Index', 'IndexWithAdditionalOrder'],
'SubordinationUse': ['ToItems', 'ToFolders', 'ToFoldersAndItems'],
@@ -3729,7 +3729,7 @@ def emit_http_service_properties(indent):
i = indent
X(f'{i}{esc_xml(obj_name)}')
emit_mltext(i, 'Synonym', synonym)
- X(f'{i}')
+ X(f'{i}{esc_xml(str(defn["comment"]))}' if defn.get('comment') else f'{i}')
root_url = str(defn['rootURL']) if defn.get('rootURL') else obj_name.lower()
X(f'{i}{esc_xml(root_url)}')
reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse')
@@ -3800,29 +3800,48 @@ def emit_url_template(indent, tmpl_name, tmpl_def):
tmpl_synonym = split_camel_case(tmpl_name)
template = ''
methods = {}
+ tmpl_comment = ''
if isinstance(tmpl_def, str):
template = tmpl_def
else:
template = str(tmpl_def['template']) if tmpl_def.get('template') else f'/{tmpl_name.lower()}'
+ if tmpl_def.get('synonym'):
+ tmpl_synonym = str(tmpl_def['synonym'])
+ if tmpl_def.get('comment'):
+ tmpl_comment = str(tmpl_def['comment'])
if tmpl_def.get('methods'):
for k, v in tmpl_def['methods'].items():
- methods[k] = str(v)
+ methods[k] = v # строка (HTTP-метод) ЛИБО объект {httpMethod, handler, synonym, comment}
X(f'{indent}')
X(f'{indent}\t')
X(f'{indent}\t\t{esc_xml(tmpl_name)}')
emit_mltext(f'{indent}\t\t', 'Synonym', tmpl_synonym)
+ X(f'{indent}\t\t{esc_xml(tmpl_comment)}' if tmpl_comment else f'{indent}\t\t')
X(f'{indent}\t\t{esc_xml(template)}')
X(f'{indent}\t')
if methods:
X(f'{indent}\t')
- for method_name, http_method in sorted(methods.items()):
+ # Порядок — как в DSL (он же порядок исходного XML), а не отсортированный: иначе
+ # порты расходятся между собой и с выгрузкой платформы.
+ for method_name, m_def in methods.items():
method_uuid = new_uuid()
- method_synonym = split_camel_case(method_name)
- handler = f'{tmpl_name}{method_name}'
+ # Строка — сокращение "только HTTP-метод"; объект — полная форма. Обработчик по
+ # умолчанию ИмяШаблона+ИмяМетода, но в реальных конфигурациях он произвольный.
+ if isinstance(m_def, str):
+ http_method = m_def
+ handler = f'{tmpl_name}{method_name}'
+ method_synonym = split_camel_case(method_name)
+ method_comment = ''
+ else:
+ http_method = str(m_def.get('httpMethod') or 'GET')
+ handler = str(m_def.get('handler') or f'{tmpl_name}{method_name}')
+ method_synonym = str(m_def['synonym']) if m_def.get('synonym') else split_camel_case(method_name)
+ method_comment = str(m_def.get('comment') or '')
X(f'{indent}\t\t')
X(f'{indent}\t\t\t')
X(f'{indent}\t\t\t\t{esc_xml(method_name)}')
emit_mltext(f'{indent}\t\t\t\t', 'Synonym', method_synonym)
+ X(f'{indent}\t\t\t\t{esc_xml(method_comment)}' if method_comment else f'{indent}\t\t\t\t')
X(f'{indent}\t\t\t\t{http_method}')
X(f'{indent}\t\t\t\t{esc_xml(handler)}')
X(f'{indent}\t\t\t')
@@ -4260,7 +4279,8 @@ if obj_type == 'HTTPService':
if url_templates:
has_children = True
X('\t\t')
- for tmpl_name in sorted(url_tmpl_order):
+ # Порядок — как в DSL (он же порядок исходного XML), а не отсортированный.
+ for tmpl_name in url_tmpl_order:
emit_url_template('\t\t\t', tmpl_name, url_templates[tmpl_name])
X('\t\t')
else:
diff --git a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1 b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
index f48a51a6..714e4e5c 100644
--- a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
+++ b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
@@ -1,4 +1,4 @@
-# meta-decompile v0.56 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
+# meta-decompile v0.57 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes, Document,
@@ -92,7 +92,7 @@ foreach ($c in $rootEl.ChildNodes) { if ($c.NodeType -eq 'Element') { $objNode =
if (-not $objNode) { [Console]::Error.WriteLine("meta-decompile: пустой MetaDataObject"); exit 3 }
$objType = $objNode.LocalName
-if ($objType -notin @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document', 'InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'BusinessProcess', 'Task', 'Enum', 'Report', 'DataProcessor', 'Constant', 'DefinedType', 'FunctionalOption', 'DocumentJournal', 'Sequence', 'FilterCriterion', 'DocumentNumerator', 'SettingsStorage', 'CommonModule', 'EventSubscription', 'ScheduledJob', 'CommonForm', 'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference', 'CommonPicture', 'CommonTemplate')) {
+if ($objType -notin @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document', 'InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'BusinessProcess', 'Task', 'Enum', 'Report', 'DataProcessor', 'Constant', 'DefinedType', 'FunctionalOption', 'DocumentJournal', 'Sequence', 'FilterCriterion', 'DocumentNumerator', 'SettingsStorage', 'CommonModule', 'EventSubscription', 'ScheduledJob', 'CommonForm', 'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference', 'CommonPicture', 'CommonTemplate', 'HTTPService')) {
[Console]::Error.WriteLine("meta-decompile: тип '$objType' пока не поддержан (…, CommonPicture, CommonTemplate)"); exit 3
}
@@ -781,6 +781,13 @@ if ($objType -eq 'CommonCommand') {
Add-BoolProp 'modifiesData' 'ModifiesData' $false
Add-EnumProp 'onMainServerUnavalableBehavior' 'OnMainServerUnavalableBehavior' 'Auto'
}
+# HTTPService — корневой URL, повторное использование сеансов, время жизни сеанса.
+# Шаблоны URL с методами разбираются в блоке ChildObjects.
+if ($objType -eq 'HTTPService') {
+ $ru = P 'RootURL'; if ($ru -and $ru -ne $objName.ToLower()) { $dsl['rootURL'] = $ru }
+ Add-EnumProp 'reuseSessions' 'ReuseSessions' 'DontUse'
+ Add-IntProp 'sessionMaxAge' 'SessionMaxAge' 20
+}
# CommonAttribute — общий реквизит: тип + value-свойства + состав объектов + свойства разделения данных.
if ($objType -eq 'CommonAttribute') {
$vt = Get-TypeShorthand ($props.SelectSingleNode('md:Type', $nsm)); if ($vt -and $vt -ne 'String(0)') { $dsl['valueType'] = $vt }
@@ -1122,6 +1129,54 @@ if ($saNode) {
# --- ChildObjects: Attributes + TabularSections ---
$childObjs = $objNode.SelectSingleNode('md:ChildObjects', $nsm)
if ($childObjs) {
+ # HTTPService: шаблоны URL и их методы. Шаблон — {template, methods{}}, метод — строка (только
+ # HTTP-метод, когда обработчик совпадает с авто-выводом ИмяШаблона+ИмяМетода) либо объект.
+ $tmplNodes = @($childObjs.SelectNodes('md:URLTemplate', $nsm))
+ if ($tmplNodes.Count -gt 0) {
+ $tmpls = [ordered]@{}
+ foreach ($t in $tmplNodes) {
+ $tp = $t.SelectSingleNode('md:Properties', $nsm)
+ $tName = ($tp.SelectSingleNode('md:Name', $nsm)).InnerText
+ $tObj = [ordered]@{}
+ $tTemplate = $tp.SelectSingleNode('md:Template', $nsm)
+ if ($tTemplate) { $tObj['template'] = $tTemplate.InnerText }
+ $tSyn = Get-MLValue ($tp.SelectSingleNode('md:Synonym', $nsm))
+ # -cne, не -ne: сравнение синонима с авто-выводом ДОЛЖНО быть регистрочувствительным,
+ # иначе "Post" против "post" считается совпадением и синоним теряется.
+ if ($null -ne $tSyn -and "$tSyn" -cne (Split-CamelWords $tName)) { $tObj['synonym'] = $tSyn }
+ $tCmt = $tp.SelectSingleNode('md:Comment', $nsm)
+ if ($tCmt -and $tCmt.InnerText) { $tObj['comment'] = $tCmt.InnerText }
+
+ $mNodes = @($t.SelectNodes('md:ChildObjects/md:Method', $nsm))
+ if ($mNodes.Count -gt 0) {
+ $methods = [ordered]@{}
+ foreach ($m in $mNodes) {
+ $mp = $m.SelectSingleNode('md:Properties', $nsm)
+ $mName = ($mp.SelectSingleNode('md:Name', $nsm)).InnerText
+ $mHttp = $mp.SelectSingleNode('md:HTTPMethod', $nsm)
+ $mHandler = $mp.SelectSingleNode('md:Handler', $nsm)
+ $mSyn = Get-MLValue ($mp.SelectSingleNode('md:Synonym', $nsm))
+ $mCmt = $mp.SelectSingleNode('md:Comment', $nsm)
+ $httpVal = if ($mHttp) { $mHttp.InnerText } else { 'GET' }
+ $handlerVal = if ($mHandler) { $mHandler.InnerText } else { '' }
+ $synDefault = ($null -eq $mSyn) -or ("$mSyn" -ceq (Split-CamelWords $mName))
+ $cmtEmpty = (-not $mCmt) -or (-not $mCmt.InnerText)
+ if ($handlerVal -eq "$tName$mName" -and $synDefault -and $cmtEmpty) {
+ $methods[$mName] = $httpVal
+ } else {
+ $mo = [ordered]@{ httpMethod = $httpVal }
+ if ($handlerVal) { $mo['handler'] = $handlerVal }
+ if (-not $synDefault) { $mo['synonym'] = $mSyn }
+ if (-not $cmtEmpty) { $mo['comment'] = $mCmt.InnerText }
+ $methods[$mName] = $mo
+ }
+ }
+ $tObj['methods'] = $methods
+ }
+ $tmpls[$tName] = $tObj
+ }
+ $dsl['urlTemplates'] = $tmpls
+ }
$attrs = @($childObjs.SelectNodes('md:Attribute', $nsm))
if ($attrs.Count -gt 0) {
$arr = [System.Collections.ArrayList]@()
diff --git a/.claude/skills/meta-decompile/scripts/meta-decompile.py b/.claude/skills/meta-decompile/scripts/meta-decompile.py
index 1073df9a..e9b8dc83 100644
--- a/.claude/skills/meta-decompile/scripts/meta-decompile.py
+++ b/.claude/skills/meta-decompile/scripts/meta-decompile.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# meta-decompile v0.56 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
+# meta-decompile v0.57 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Зеркало meta-decompile.ps1 (КАНОН). Структура 1:1 — те же имена функций, порядок, комментарии.
diff --git a/tests/skills/cases/meta-compile/http-service.json b/tests/skills/cases/meta-compile/http-service.json
index e59d6a99..3a839607 100644
--- a/tests/skills/cases/meta-compile/http-service.json
+++ b/tests/skills/cases/meta-compile/http-service.json
@@ -4,8 +4,9 @@
"type": "HTTPService",
"name": "ТоварныйAPI",
"rootURL": "api/v1",
- "reuseSessions": "AutoUse",
+ "reuseSessions": "Use",
"sessionMaxAge": 30,
+ "comment": "Публичный API",
"urlTemplates": {
"Товары": {
"template": "/products/{id}",
@@ -13,6 +14,13 @@
"Получить": "GET",
"Обновить": "PUT"
}
+ },
+ "rpc": {
+ "template": "/rpc",
+ "comment": "Удалённый вызов",
+ "methods": {
+ "post": { "httpMethod": "POST", "handler": "ВызовЧерезТелоЗапроса", "synonym": "Post" }
+ }
}
}
},
diff --git a/tests/skills/cases/meta-compile/snapshots/http-service/HTTPServices/ТоварныйAPI.xml b/tests/skills/cases/meta-compile/snapshots/http-service/HTTPServices/ТоварныйAPI.xml
index bce6ee06..d446b118 100644
--- a/tests/skills/cases/meta-compile/snapshots/http-service/HTTPServices/ТоварныйAPI.xml
+++ b/tests/skills/cases/meta-compile/snapshots/http-service/HTTPServices/ТоварныйAPI.xml
@@ -9,9 +9,9 @@
ТоварныйAPI
-
+ Публичный API
api/v1
- AutoUse
+ Use
30
@@ -24,23 +24,11 @@
Товары
+
/products/{id}
-
- Обновить
-
-
- ru
- Обновить
-
-
- PUT
- ТоварыОбновить
-
-
-
Получить
@@ -49,10 +37,54 @@
Получить
+
GET
ТоварыПолучить
+
+
+ Обновить
+
+
+ ru
+ Обновить
+
+
+
+ PUT
+ ТоварыОбновить
+
+
+
+
+
+
+ rpc
+
+
+ ru
+ rpc
+
+
+ Удалённый вызов
+ /rpc
+
+
+
+
+ post
+
+
+ ru
+ Post
+
+
+
+ POST
+ ВызовЧерезТелоЗапроса
+
+