diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1 index e2331d05..fb460651 100644 --- a/.claude/skills/meta-edit/scripts/meta-edit.ps1 +++ b/.claude/skills/meta-edit/scripts/meta-edit.ps1 @@ -1,4 +1,4 @@ -# meta-edit v1.11 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts) +# meta-edit v1.12 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts + create-if-missing свойств) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -1974,8 +1974,19 @@ function Modify-Properties($propsDef) { } if (-not $propEl) { - Warn "Property '$propName' not found in Properties" - return + # create-if-missing: известное свойство создаём (порядок 1С терпит — append); неизвестное → ошибка (опечатка) + if ($script:knownObjectProps -notcontains $propName) { + Write-Error "modify-property: неизвестное свойство '$propName' — нет такого свойства объекта (опечатка?)" + exit 1 + } + $newNodes = Import-Fragment "<$propName/>" + if ($newNodes.Count -gt 0) { + Insert-PropertyInOrder $script:propertiesEl $newNodes[0] $null $propName + $propEl = $newNodes[0] + } else { + Warn "Property '$propName': could not create element" + return + } } # Complex property: Owners, RegisterRecords, BasedOn, InputByString @@ -2248,7 +2259,23 @@ function Modify-ChildElements($modifyDef, [string]$childType) { Info "Modified $xmlTag '$elemName'.$changeProp = $valueStr" $script:modifyCount++ } else { - Warn "$xmlTag '$elemName': property '$changeProp' not found" + # create-if-missing: известное свойство создаём в позиции; неизвестное → ошибка (опечатка) + if ($script:knownChildProps -notcontains $changeProp) { + Write-Error "modify: неизвестное свойство '$changeProp' у $xmlTag '$elemName' (опечатка?)" + exit 1 + } + $valueStr = "$changeValue" + if ($changeValue -is [bool]) { + $valueStr = if ($changeValue) { "true" } else { "false" } + } else { + $valueStr = Normalize-EnumValue $changeProp $valueStr + } + $newNodes = Import-Fragment "<$changeProp>$(Esc-Xml $valueStr)" + if ($newNodes.Count -gt 0) { + Insert-PropertyInOrder $propsEl $newNodes[0] $script:attrPropOrder $changeProp + Info "Created $xmlTag '$elemName'.$changeProp = $valueStr" + $script:modifyCount++ + } } } } @@ -2286,6 +2313,73 @@ $script:complexPropertyMap = @{ "InputByString" = @{ tag = "xr:Field"; attr = $null } } +# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property. +# Известное отсутствующее свойство create-if-missing создаётся; неизвестное (опечатка) → ошибка. +$script:knownObjectProps = @( + 'ActionPeriod','ActionPeriodUse','Addressing','AutoOrderByCode','Autonumbering','AuxiliaryChoiceForm', + 'AuxiliaryFolderChoiceForm','AuxiliaryFolderForm','AuxiliaryForm','AuxiliaryListForm','AuxiliaryObjectForm', + 'AuxiliaryRecordForm','AuxiliarySettingsForm','BaseCalculationTypes','BasePeriod','BasedOn', + 'CharacteristicExtValues','Characteristics','ChartOfAccounts','ChartOfCalculationTypes','CheckUnique', + 'ChoiceDataGetModeOnInputByString','ChoiceFoldersAndItems','ChoiceForm','ChoiceHistoryOnInput','ChoiceMode', + 'ChoiceParameterLinks','ChoiceParameters','CodeAllowedLength','CodeLength','CodeMask','CodeSeries','CodeType', + 'Comment','Correspondence','CreateOnInput','CreateTaskInPrivilegedMode','CurrentPerformer','DataHistory', + 'DataLockControlMode','DataLockFields','DefaultChoiceForm','DefaultFolderChoiceForm','DefaultFolderForm', + 'DefaultForm','DefaultListForm','DefaultObjectForm','DefaultPresentation','DefaultRecordForm','DefaultSettingsForm', + 'DefaultVariantForm','DependenceOnCalculationTypes','DescriptionLength','DistributedInfoBase','EditFormat', + 'EditType','EnableTotalsSliceFirst','EnableTotalsSliceLast','EnableTotalsSplitting', + 'ExecuteAfterWriteDataHistoryVersionProcessing','Explanation','ExtDimensionTypes','ExtendedEdit', + 'ExtendedListPresentation','ExtendedObjectPresentation','ExtendedPresentation','ExtendedRecordPresentation', + 'FillChecking','FoldersOnTop','Format','FullTextSearch','FullTextSearchOnInputByString','Hierarchical', + 'HierarchyType','IncludeConfigurationExtensions','IncludeHelpInContents','InformationRegisterPeriodicity', + 'InputByString','LevelCount','LimitLevelCount','LinkByType','ListPresentation','MainAddressingAttribute', + 'MainDataCompositionSchema','MainFilterOnPeriod','MarkNegatives','Mask','MaxExtDimensionCount','MaxValue', + 'MinValue','MultiLine','Name','NumberAllowedLength','NumberLength','NumberPeriodicity','NumberType','Numerator', + 'ObjectPresentation','OrderLength','Owners','PasswordMode','PeriodAdjustmentLength','Periodicity', + 'PostInPrivilegedMode','Posting','PredefinedDataUpdate','QuickChoice','RealTimePosting','RecordPresentation', + 'RegisterRecords','RegisterRecordsDeletion','RegisterRecordsWritingOnPost','RegisterType','RegisteredDocuments', + 'Schedule','ScheduleDate','ScheduleValue','SearchStringModeOnInputByString','SequenceFilling','SettingsStorage', + 'StandardAttributes','StandardTabularSections','SubordinationUse','Synonym','Task','TaskNumberAutoPrefix', + 'ToolTip','Type','UnpostInPrivilegedMode','UpdateDataHistoryImmediatelyAfterWrite','UseStandardCommands', + 'VariantsStorage','WriteMode' +) + +# Известные свойства дочерних элементов (union Attribute/Dimension/Resource по корпусу) — allowlist default-ветки +# modify-attribute/-dimension/-resource. +$script:knownChildProps = @( + 'AccountingFlag','Balance','BaseDimension','ChoiceFoldersAndItems','ChoiceForm','ChoiceHistoryOnInput', + 'ChoiceParameterLinks','ChoiceParameters','Comment','CreateOnInput','DataHistory','DenyIncompleteValues', + 'DocumentMap','EditFormat','ExtDimensionAccountingFlag','ExtendedEdit','FillChecking','FillFromFillingValue', + 'FillValue','Format','FullTextSearch','Indexing','LinkByType','MainFilter','MarkNegatives','Mask','Master', + 'MaxValue','MinValue','MultiLine','Name','PasswordMode','QuickChoice','RegisterRecordsMap','ScheduleLink', + 'Synonym','ToolTip','Type','Use','UseInTotals' +) + +# Канонический порядок свойств реквизита (последовательность Build-AttributeFragment) — для вставки в позицию. +# Порядок 1С терпит (cert), но держим канонический для консистентности с meta-compile. +$script:attrPropOrder = @( + 'Name','Synonym','Comment','Type','PasswordMode','Format','EditFormat','ToolTip','MarkNegatives','Mask', + 'MultiLine','ExtendedEdit','MinValue','MaxValue','FillFromFillingValue','FillValue','FillChecking', + 'ChoiceFoldersAndItems','ChoiceParameterLinks','ChoiceParameters','QuickChoice','CreateOnInput','ChoiceForm', + 'LinkByType','ChoiceHistoryOnInput','Use','Indexing','FullTextSearch','DataHistory' +) + +# Вставить новый элемент свойства в Properties в канонической позиции (по orderArray); если свойства нет в +# orderArray (или orderArray пуст) — append. Порядок 1С терпит, канонический — для консистентности/снапшотов. +function Insert-PropertyInOrder($propsEl, $newNode, $orderArray, $propName) { + $childIndent = "$(Get-ChildIndent $propsEl)" + $refNode = $null + $idx = if ($orderArray) { [array]::IndexOf($orderArray, $propName) } else { -1 } + if ($idx -ge 0) { + foreach ($ch in $propsEl.ChildNodes) { + if ($ch.NodeType -eq 'Element') { + $ci = [array]::IndexOf($orderArray, $ch.LocalName) + if ($ci -gt $idx) { $refNode = $ch; break } + } + } + } + Insert-BeforeElement $propsEl $newNode $refNode $childIndent +} + function Find-PropertyElement([string]$propName) { foreach ($child in $script:propertiesEl.ChildNodes) { if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) { diff --git a/.claude/skills/meta-edit/scripts/meta-edit.py b/.claude/skills/meta-edit/scripts/meta-edit.py index 7d496acd..8d17135e 100644 --- a/.claude/skills/meta-edit/scripts/meta-edit.py +++ b/.claude/skills/meta-edit/scripts/meta-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-edit v1.11 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts) +# meta-edit v1.12 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts + create-if-missing свойств) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -1858,8 +1858,17 @@ def modify_properties(props_def): break if prop_el is None: - warn(f"Property '{prop_name}' not found in Properties") - continue + # create-if-missing: известное свойство создаём (порядок 1С терпит); неизвестное → ошибка (опечатка) + if prop_name not in known_object_props: + print(f"meta-edit: modify-property: неизвестное свойство '{prop_name}' — нет такого свойства объекта (опечатка?)", file=sys.stderr) + sys.exit(1) + new_nodes = import_fragment(f"<{prop_name}/>") + if new_nodes: + insert_property_in_order(properties_el, new_nodes[0], None, prop_name) + prop_el = new_nodes[0] + else: + warn(f"Property '{prop_name}': could not create element") + continue # Complex property: Owners, RegisterRecords, BasedOn, InputByString if prop_name in complex_property_map: @@ -2100,7 +2109,20 @@ def modify_child_elements(modify_def, child_type): info(f"Modified {xml_tag} '{elem_name}'.{change_prop} = {value_str}") modify_count += 1 else: - warn(f"{xml_tag} '{elem_name}': property '{change_prop}' not found") + # create-if-missing: известное свойство создаём в позиции; неизвестное → ошибка (опечатка) + if change_prop not in known_child_props: + print(f"meta-edit: modify: неизвестное свойство '{change_prop}' у {xml_tag} '{elem_name}' (опечатка?)", file=sys.stderr) + sys.exit(1) + value_str = str(change_value) + if isinstance(change_value, bool): + value_str = "true" if change_value else "false" + else: + value_str = normalize_enum_value(change_prop, value_str) + new_nodes = import_fragment(f"<{change_prop}>{esc_xml(value_str)}") + if new_nodes: + insert_property_in_order(props_el, new_nodes[0], attr_prop_order, change_prop) + info(f"Created {xml_tag} '{elem_name}'.{change_prop} = {value_str}") + modify_count += 1 def process_modify(modify_def): @@ -2128,6 +2150,68 @@ complex_property_map = { "InputByString": {"tag": "xr:Field", "attr": None}, } +# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property. +# Известное отсутствующее свойство create-if-missing создаётся; неизвестное (опечатка) → ошибка. +known_object_props = { + 'ActionPeriod', 'ActionPeriodUse', 'Addressing', 'AutoOrderByCode', 'Autonumbering', 'AuxiliaryChoiceForm', + 'AuxiliaryFolderChoiceForm', 'AuxiliaryFolderForm', 'AuxiliaryForm', 'AuxiliaryListForm', 'AuxiliaryObjectForm', + 'AuxiliaryRecordForm', 'AuxiliarySettingsForm', 'BaseCalculationTypes', 'BasePeriod', 'BasedOn', + 'CharacteristicExtValues', 'Characteristics', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'CheckUnique', + 'ChoiceDataGetModeOnInputByString', 'ChoiceFoldersAndItems', 'ChoiceForm', 'ChoiceHistoryOnInput', 'ChoiceMode', + 'ChoiceParameterLinks', 'ChoiceParameters', 'CodeAllowedLength', 'CodeLength', 'CodeMask', 'CodeSeries', 'CodeType', + 'Comment', 'Correspondence', 'CreateOnInput', 'CreateTaskInPrivilegedMode', 'CurrentPerformer', 'DataHistory', + 'DataLockControlMode', 'DataLockFields', 'DefaultChoiceForm', 'DefaultFolderChoiceForm', 'DefaultFolderForm', + 'DefaultForm', 'DefaultListForm', 'DefaultObjectForm', 'DefaultPresentation', 'DefaultRecordForm', 'DefaultSettingsForm', + 'DefaultVariantForm', 'DependenceOnCalculationTypes', 'DescriptionLength', 'DistributedInfoBase', 'EditFormat', + 'EditType', 'EnableTotalsSliceFirst', 'EnableTotalsSliceLast', 'EnableTotalsSplitting', + 'ExecuteAfterWriteDataHistoryVersionProcessing', 'Explanation', 'ExtDimensionTypes', 'ExtendedEdit', + 'ExtendedListPresentation', 'ExtendedObjectPresentation', 'ExtendedPresentation', 'ExtendedRecordPresentation', + 'FillChecking', 'FoldersOnTop', 'Format', 'FullTextSearch', 'FullTextSearchOnInputByString', 'Hierarchical', + 'HierarchyType', 'IncludeConfigurationExtensions', 'IncludeHelpInContents', 'InformationRegisterPeriodicity', + 'InputByString', 'LevelCount', 'LimitLevelCount', 'LinkByType', 'ListPresentation', 'MainAddressingAttribute', + 'MainDataCompositionSchema', 'MainFilterOnPeriod', 'MarkNegatives', 'Mask', 'MaxExtDimensionCount', 'MaxValue', + 'MinValue', 'MultiLine', 'Name', 'NumberAllowedLength', 'NumberLength', 'NumberPeriodicity', 'NumberType', 'Numerator', + 'ObjectPresentation', 'OrderLength', 'Owners', 'PasswordMode', 'PeriodAdjustmentLength', 'Periodicity', + 'PostInPrivilegedMode', 'Posting', 'PredefinedDataUpdate', 'QuickChoice', 'RealTimePosting', 'RecordPresentation', + 'RegisterRecords', 'RegisterRecordsDeletion', 'RegisterRecordsWritingOnPost', 'RegisterType', 'RegisteredDocuments', + 'Schedule', 'ScheduleDate', 'ScheduleValue', 'SearchStringModeOnInputByString', 'SequenceFilling', 'SettingsStorage', + 'StandardAttributes', 'StandardTabularSections', 'SubordinationUse', 'Synonym', 'Task', 'TaskNumberAutoPrefix', + 'ToolTip', 'Type', 'UnpostInPrivilegedMode', 'UpdateDataHistoryImmediatelyAfterWrite', 'UseStandardCommands', + 'VariantsStorage', 'WriteMode', +} + +# Известные свойства дочерних элементов (union Attribute/Dimension/Resource) — allowlist default-ветки modify-child. +known_child_props = { + 'AccountingFlag', 'Balance', 'BaseDimension', 'ChoiceFoldersAndItems', 'ChoiceForm', 'ChoiceHistoryOnInput', + 'ChoiceParameterLinks', 'ChoiceParameters', 'Comment', 'CreateOnInput', 'DataHistory', 'DenyIncompleteValues', + 'DocumentMap', 'EditFormat', 'ExtDimensionAccountingFlag', 'ExtendedEdit', 'FillChecking', 'FillFromFillingValue', + 'FillValue', 'Format', 'FullTextSearch', 'Indexing', 'LinkByType', 'MainFilter', 'MarkNegatives', 'Mask', 'Master', + 'MaxValue', 'MinValue', 'MultiLine', 'Name', 'PasswordMode', 'QuickChoice', 'RegisterRecordsMap', 'ScheduleLink', + 'Synonym', 'ToolTip', 'Type', 'Use', 'UseInTotals', +} + +# Канонический порядок свойств реквизита (последовательность build_attribute_fragment) — для вставки в позицию. +attr_prop_order = [ + 'Name', 'Synonym', 'Comment', 'Type', 'PasswordMode', 'Format', 'EditFormat', 'ToolTip', 'MarkNegatives', 'Mask', + 'MultiLine', 'ExtendedEdit', 'MinValue', 'MaxValue', 'FillFromFillingValue', 'FillValue', 'FillChecking', + 'ChoiceFoldersAndItems', 'ChoiceParameterLinks', 'ChoiceParameters', 'QuickChoice', 'CreateOnInput', 'ChoiceForm', + 'LinkByType', 'ChoiceHistoryOnInput', 'Use', 'Indexing', 'FullTextSearch', 'DataHistory', +] + + +def insert_property_in_order(props_el, new_node, order_array, prop_name): + """Вставить новое свойство в Properties в канонической позиции (по order_array); иначе append.""" + child_indent = get_child_indent(props_el) + ref_node = None + idx = order_array.index(prop_name) if (order_array and prop_name in order_array) else -1 + if idx >= 0: + for ch in props_el: + cl = localname(ch) + if cl in order_array and order_array.index(cl) > idx: + ref_node = ch + break + insert_before_element(props_el, new_node, ref_node, child_indent) + def find_property_element(prop_name): for child in properties_el: diff --git a/tests/skills/cases/meta-edit/error-modify-attribute-typo.json b/tests/skills/cases/meta-edit/error-modify-attribute-typo.json new file mode 100644 index 00000000..c9eca253 --- /dev/null +++ b/tests/skills/cases/meta-edit/error-modify-attribute-typo.json @@ -0,0 +1,14 @@ +{ + "name": "modify-attribute с опечаткой в имени свойства реквизита → ошибка (не тихий no-op)", + "setup": "empty-config", + "preRun": [ + { + "script": "meta-compile/scripts/meta-compile", + "input": { "type": "Catalog", "name": "Спр", "attributes": ["Комм: String(50)"] }, + "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" } + } + ], + "params": { "objectPath": "Catalogs/Спр" }, + "input": { "modify": { "attributes": { "Комм": { "ЧёТоНеТо": "1" } } } }, + "expectError": true +} diff --git a/tests/skills/cases/meta-edit/error-modify-property-typo.json b/tests/skills/cases/meta-edit/error-modify-property-typo.json new file mode 100644 index 00000000..b251ced9 --- /dev/null +++ b/tests/skills/cases/meta-edit/error-modify-property-typo.json @@ -0,0 +1,14 @@ +{ + "name": "modify-property с опечаткой в имени свойства → ошибка (не тихий no-op)", + "setup": "empty-config", + "preRun": [ + { + "script": "meta-compile/scripts/meta-compile", + "input": { "type": "Catalog", "name": "Спр" }, + "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" } + } + ], + "params": { "objectPath": "Catalogs/Спр" }, + "input": { "modify": { "properties": { "CodeLenght": "11" } } }, + "expectError": true +} diff --git a/tests/skills/integration/meta-edit-create-if-missing.test.mjs b/tests/skills/integration/meta-edit-create-if-missing.test.mjs new file mode 100644 index 00000000..70e6d3f7 --- /dev/null +++ b/tests/skills/integration/meta-edit-create-if-missing.test.mjs @@ -0,0 +1,38 @@ +// meta-edit-create-if-missing.test.mjs — Integration: create-if-missing свойства объекта +// Steps: cf-init → meta-compile Catalog → editFile (удалить DataHistory) → meta-edit modify-property +// DataHistory=Use (свойство отсутствует → должно СОЗДАТЬСЯ, а не тихий no-op) → assertContains + +export const name = 'meta-edit create-if-missing: пересоздание отсутствующего свойства объекта'; +export const setup = 'none'; + +export const steps = [ + { + name: 'cf-init: пустая конфигурация', + script: 'cf-init/scripts/cf-init', + args: { '-Name': 'ТестПравки', '-OutputDir': '{workDir}/config' }, + }, + { + name: 'meta-compile: Catalog Спр', + script: 'meta-compile/scripts/meta-compile', + input: { type: 'Catalog', name: 'Спр' }, + args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}/config' }, + }, + { + name: 'editFile: удалить свойство DataHistory (симуляция отсутствия)', + editFile: '{workDir}/config/Catalogs/Спр.xml', + replace: 'DontUse', + with: '', + }, + { + name: 'meta-edit: modify-property DataHistory=Use (create-if-missing)', + script: 'meta-edit/scripts/meta-edit', + input: { modify: { properties: { DataHistory: 'Use' } } }, + args: { '-DefinitionFile': '{inputFile}', '-ObjectPath': '{workDir}/config/Catalogs/Спр.xml' }, + validate: { script: 'meta-validate/scripts/meta-validate', flag: '-ObjectPath', path: 'config/Catalogs/Спр.xml' }, + }, + { + name: 'assertContains: DataHistory создан', + assertContains: '{workDir}/config/Catalogs/Спр.xml', + expect: 'Use', + }, +];