feat(db-repo): py-порт, реестр семей копий и подсказки в db-load-git

Порт .py зеркалит .ps1 по порядку функций, именам и комментариям — расхождения
только там, где их диктует рантайм. Общие функции взяты из соседних портов
дословно.

Копии зарегистрированы в check-inline-drift.mjs: db-repo присоединён к шести
платформенным семьям, четыре функции блока реквизитов хранилища заведены новыми
семьями, разбор сообщений хранилища — семьёй с эталоном db-load-xml. Гард сразу
нашёл настоящее расхождение: копии Get-RepositoryArgs в четырёх соседях остались
со старым comma-return, эталон правился позже. Ресинхронизировано.

Разбор сообщений хранилища добавлен и в db-load-git: он тоже грузит в базу
частично и получает те же три отказа платформы.

Подкоманда стала именованной (-Command): в группе скрипты принимают только
именованные параметры, слэш-форму в них переводит модель. Позиционный параметр
у пишущего навыка запрещён гардом check-positional-binding. Mandatory при этом
не ставим — обязательный параметр PowerShell запрашивает интерактивно, а в
пакетном запуске это зависание.

Все девять гардов и функциональные тесты db-* зелёные в обоих рантаймах.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-23 13:08:52 +03:00
co-authored by Claude Opus 5
parent 012f5c3c30
commit 1327b4fc5a
16 changed files with 1658 additions and 44 deletions
@@ -204,7 +204,7 @@ function Get-RepositoryArgs {
$a += "/ConfigurationRepositoryF`"$($Repo.Path)`"" $a += "/ConfigurationRepositoryF`"$($Repo.Path)`""
if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" } if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" }
if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" } if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" }
return ,$a return $a
} }
function Protect-Secrets { function Protect-Secrets {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-dump-xml v1.16 — Dump 1C configuration to XML files # db-dump-xml v1.17 — 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
@@ -72,10 +72,95 @@ IBCMD_OWNED_KEYS = [
"--import", "--export", "--apply", "--force", "--create-database", "--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password", "--user", "--password",
] ]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"] V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"] IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
# --- Реквизиты хранилища из .v8-project.json ---
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
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 same_path(a, b):
if not a or not b:
return False
try:
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
except Exception:
return False
def find_project_database(args):
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
pf = _sg_find_v8project(os.getcwd())
if not pf:
return None
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
except Exception:
return None
for db in proj.get("databases") or []:
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
return db
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
if (db["server"].lower() == args.InfoBaseServer.lower()
and db["ref"].lower() == args.InfoBaseRef.lower()):
return db
return None
def resolve_repository_settings(args):
"""Возвращает dict path/user/password/from_registry. Явные -Repository* сильнее реестра."""
db_rec = find_project_database(args)
rec = None
if db_rec:
if args.Extension:
# У расширения СВОЁ хранилище со своим путём (проверено): выбирается парой
# /ConfigurationRepositoryF"<путь расширения>" + -Extension "<Имя>".
for ext in db_rec.get("extensions") or []:
if (ext.get("name") or "").lower() == args.Extension.lower():
rec = ext.get("repository")
break
else:
rec = db_rec.get("repository")
path = args.RepositoryPath or ((rec or {}).get("path") or None)
user = args.RepositoryUser or ((rec or {}).get("user") or None)
# Пустой пароль = отсутствующий: 1С требует опускать ключ целиком, а не передавать пустое значение.
pwd = args.RepositoryPassword or ((rec or {}).get("password") or None)
return {
"path": path.strip().strip('"') if path else None,
"user": user,
"password": pwd,
"from_registry": bool(rec and rec.get("path")),
}
def repository_args(repo):
"""Ключи доступа к хранилищу. Форма — кавычки ВНУТРИ токена, как у /N и /P."""
a = []
if not repo or not repo.get("path"):
return a
a.append('/ConfigurationRepositoryF"%s"' % repo["path"])
if repo.get("user"):
a.append('/ConfigurationRepositoryN"%s"' % repo["user"])
if repo.get("password"):
a.append('/ConfigurationRepositoryP"%s"' % repo["password"])
return a
def arg_key_match(token, key): def arg_key_match(token, key):
"""Token matches a key when it equals it, or starts with it and the next character """Token matches a key when it equals it, or starts with it and the next character
is not a letter — catches glued /N"user" and --password=x, while keeping is not a letter — catches glued /N"user" and --password=x, while keeping
@@ -388,6 +473,9 @@ def main():
parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server") parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server")
parser.add_argument("-UserName", default="", help="1C user name") parser.add_argument("-UserName", default="", help="1C user name")
parser.add_argument("-Password", default="", help="1C user password") parser.add_argument("-Password", default="", help="1C user password")
parser.add_argument("-RepositoryPath", default="")
parser.add_argument("-RepositoryUser", default="")
parser.add_argument("-RepositoryPassword", default="")
parser.add_argument("-ConfigDir", required=True, help="Directory for configuration dump") parser.add_argument("-ConfigDir", required=True, help="Directory for configuration dump")
parser.add_argument( parser.add_argument(
"-Mode", "-Mode",
@@ -396,6 +484,7 @@ def main():
help="Dump mode (default: Changes)", help="Dump mode (default: Changes)",
) )
parser.add_argument("-Objects", default="", help="Comma-separated metadata object names (for Partial mode)") parser.add_argument("-Objects", default="", help="Comma-separated metadata object names (for Partial mode)")
parser.add_argument("-ObjectsFile", default="")
parser.add_argument("-Extension", default="", help="Extension name to dump") parser.add_argument("-Extension", default="", help="Extension name to dump")
parser.add_argument("-AllExtensions", action="store_true", help="Dump all extensions") parser.add_argument("-AllExtensions", action="store_true", help="Dump all extensions")
parser.add_argument( parser.add_argument(
@@ -443,8 +532,19 @@ def main():
sys.exit(1) sys.exit(1)
# --- Validate Partial mode --- # --- Validate Partial mode ---
# Список объектов приходит либо строкой, либо файлом: файл нужен, чтобы не перепечатывать
# то, что уже напечатал другой навык (например /db-repo update со списком полученных объектов).
if args.ObjectsFile:
if not os.path.exists(args.ObjectsFile):
print("Error: -ObjectsFile not found: %s" % args.ObjectsFile, file=sys.stderr)
sys.exit(1)
with open(args.ObjectsFile, encoding="utf-8-sig") as f:
from_file = [s.strip() for s in f.read().splitlines()
if s.strip() and not s.strip().startswith("#")]
inline = [s.strip() for s in args.Objects.split(",") if s.strip()]
args.Objects = ",".join(inline + from_file)
if args.Mode == "Partial" and not args.Objects: if args.Mode == "Partial" and not args.Objects:
print("Error: -Objects required for Partial mode", file=sys.stderr) print("Error: -Objects or -ObjectsFile required for Partial mode", file=sys.stderr)
sys.exit(1) sys.exit(1)
# --- Create output dir if needed --- # --- Create output dir if needed ---
@@ -513,6 +613,11 @@ def main():
if args.Password: if args.Password:
arguments.append(f'/P"{args.Password}"') arguments.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
arguments += ["/DumpConfigToFiles", f'"{args.ConfigDir}"'] arguments += ["/DumpConfigToFiles", f'"{args.ConfigDir}"']
arguments += ["-Format", args.Format] arguments += ["-Format", args.Format]
@@ -551,7 +656,7 @@ def main():
arguments.extend(quote_if_needed(a) for a in extra_args) arguments.extend(quote_if_needed(a) for a in extra_args)
# --- Execute --- # --- Execute ---
print(f"Running: 1cv8.exe {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName)}") print(f"Running: 1cv8.exe {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName, repo['password'])}")
result = run_v8(v8path, arguments) result = run_v8(v8path, arguments)
exit_code = result.returncode exit_code = result.returncode
@@ -1,4 +1,4 @@
# db-load-git v1.22 — Load Git changes into 1C database # db-load-git v1.23 — Load Git changes into 1C database
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется. # NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
<# <#
@@ -216,7 +216,31 @@ function Get-RepositoryArgs {
$a += "/ConfigurationRepositoryF`"$($Repo.Path)`"" $a += "/ConfigurationRepositoryF`"$($Repo.Path)`""
if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" } if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" }
if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" } if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" }
return ,$a return $a
}
# Сообщения платформы про хранилище конфигурации называют причину, но не действие. Действие
# дописываем сами: без него модель упирается в отказ и не знает, чем его лечить.
function Write-RepositoryHints {
param([string]$LogText)
if (-not $LogText) { return }
if ($LogText -match 'текущая конфигурация помещена в хранилище') {
Write-Host "[hint] полная загрузка в базу, подключённую к хранилищу, невозможна." -ForegroundColor Yellow
Write-Host " Используйте -Mode Partial, предварительно захватив объекты: /db-repo lock" -ForegroundColor Yellow
}
foreach ($m in [regex]::Matches($LogText, 'объект метаданных ([^\s]+) не захвачен в хранилище')) {
$obj = $m.Groups[1].Value
if ($obj -eq 'Configuration') {
Write-Host "[hint] не захвачен корень конфигурации — он нужен, чтобы добавить или удалить объект:" -ForegroundColor Yellow
Write-Host " /db-repo lock <база> -Objects `"Конфигурация`"" -ForegroundColor Yellow
} else {
Write-Host "[hint] объект не захвачен в хранилище: /db-repo lock <база> -Objects `"$obj`"" -ForegroundColor Yellow
}
}
if ($LogText -match 'Соединение с хранилищем конфигурации не установлено') {
Write-Host "[hint] база подключена к хранилищу, но его реквизиты неизвестны." -ForegroundColor Yellow
Write-Host " Добавьте `"repository`" в запись базы в .v8-project.json (см. /db-list)." -ForegroundColor Yellow
}
} }
function Protect-Secrets { function Protect-Secrets {
@@ -816,6 +840,7 @@ try {
} }
} }
Write-PlatformOutput $__v8.Output Write-PlatformOutput $__v8.Output
Write-RepositoryHints $logContent
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы # Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку # разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-load-git v1.21 — Load Git changes into 1C database # db-load-git v1.23 — 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
@@ -72,10 +72,115 @@ IBCMD_OWNED_KEYS = [
"--import", "--export", "--apply", "--force", "--create-database", "--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password", "--user", "--password",
] ]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"] V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"] IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
# --- Реквизиты хранилища из .v8-project.json ---
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
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 same_path(a, b):
if not a or not b:
return False
try:
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
except Exception:
return False
def find_project_database(args):
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
pf = _sg_find_v8project(os.getcwd())
if not pf:
return None
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
except Exception:
return None
for db in proj.get("databases") or []:
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
return db
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
if (db["server"].lower() == args.InfoBaseServer.lower()
and db["ref"].lower() == args.InfoBaseRef.lower()):
return db
return None
def resolve_repository_settings(args):
"""Возвращает dict path/user/password/from_registry. Явные -Repository* сильнее реестра."""
db_rec = find_project_database(args)
rec = None
if db_rec:
if args.Extension:
# У расширения СВОЁ хранилище со своим путём (проверено): выбирается парой
# /ConfigurationRepositoryF"<путь расширения>" + -Extension "<Имя>".
for ext in db_rec.get("extensions") or []:
if (ext.get("name") or "").lower() == args.Extension.lower():
rec = ext.get("repository")
break
else:
rec = db_rec.get("repository")
path = args.RepositoryPath or ((rec or {}).get("path") or None)
user = args.RepositoryUser or ((rec or {}).get("user") or None)
# Пустой пароль = отсутствующий: 1С требует опускать ключ целиком, а не передавать пустое значение.
pwd = args.RepositoryPassword or ((rec or {}).get("password") or None)
return {
"path": path.strip().strip('"') if path else None,
"user": user,
"password": pwd,
"from_registry": bool(rec and rec.get("path")),
}
def repository_args(repo):
"""Ключи доступа к хранилищу. Форма — кавычки ВНУТРИ токена, как у /N и /P."""
a = []
if not repo or not repo.get("path"):
return a
a.append('/ConfigurationRepositoryF"%s"' % repo["path"])
if repo.get("user"):
a.append('/ConfigurationRepositoryN"%s"' % repo["user"])
if repo.get("password"):
a.append('/ConfigurationRepositoryP"%s"' % repo["password"])
return a
# Сообщения платформы про хранилище конфигурации называют причину, но не действие. Действие
# дописываем сами: без него модель упирается в отказ и не знает, чем его лечить.
def write_repository_hints(log_text):
if not log_text:
return
if "текущая конфигурация помещена в хранилище" in log_text:
print("[hint] полная загрузка в базу, подключённую к хранилищу, невозможна.")
print(" Используйте -Mode Partial, предварительно захватив объекты: /db-repo lock")
for m in re.finditer(r"объект метаданных (\S+) не захвачен в хранилище", log_text):
obj = m.group(1)
if obj == "Configuration":
print("[hint] не захвачен корень конфигурации — он нужен, чтобы добавить или удалить объект:")
print(' /db-repo lock <база> -Objects "Конфигурация"')
else:
print('[hint] объект не захвачен в хранилище: /db-repo lock <база> -Objects "%s"' % obj)
if "Соединение с хранилищем конфигурации не установлено" in log_text:
print("[hint] база подключена к хранилищу, но его реквизиты неизвестны.")
print(' Добавьте "repository" в запись базы в .v8-project.json (см. /db-list).')
def arg_key_match(token, key): def arg_key_match(token, key):
"""Token matches a key when it equals it, or starts with it and the next character """Token matches a key when it equals it, or starts with it and the next character
is not a letter — catches glued /N"user" and --password=x, while keeping is not a letter — catches glued /N"user" and --password=x, while keeping
@@ -460,6 +565,9 @@ def main():
parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server") parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server")
parser.add_argument("-UserName", default="", help="1C user name") parser.add_argument("-UserName", default="", help="1C user name")
parser.add_argument("-Password", default="", help="1C user password") parser.add_argument("-Password", default="", help="1C user password")
parser.add_argument("-RepositoryPath", default="")
parser.add_argument("-RepositoryUser", default="")
parser.add_argument("-RepositoryPassword", default="")
parser.add_argument("-ConfigDir", required=True, help="Directory with XML configuration (git repo)") parser.add_argument("-ConfigDir", required=True, help="Directory with XML configuration (git repo)")
parser.add_argument( parser.add_argument(
"-Source", "-Source",
@@ -704,6 +812,11 @@ def main():
if args.Password: if args.Password:
arguments.append(f'/P"{args.Password}"') arguments.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"'] arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"']
arguments += ["-listFile", f'"{list_file}"'] arguments += ["-listFile", f'"{list_file}"']
arguments += ["-Format", args.Format] arguments += ["-Format", args.Format]
@@ -729,7 +842,7 @@ def main():
# --- Execute --- # --- Execute ---
print("") print("")
print("Executing partial configuration load...") print("Executing partial configuration load...")
print(f"Running: 1cv8.exe {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName)}") print(f"Running: 1cv8.exe {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName, repo['password'])}")
result = run_v8(v8path, arguments) result = run_v8(v8path, arguments)
exit_code = result.returncode exit_code = result.returncode
@@ -754,6 +867,7 @@ def main():
pass pass
print_platform_output(result) print_platform_output(result)
write_repository_hints(log_content)
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы # Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку # разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
@@ -210,7 +210,7 @@ function Get-RepositoryArgs {
$a += "/ConfigurationRepositoryF`"$($Repo.Path)`"" $a += "/ConfigurationRepositoryF`"$($Repo.Path)`""
if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" } if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" }
if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" } if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" }
return ,$a return $a
} }
# Сообщения платформы про хранилище конфигурации называют причину, но не действие. Действие # Сообщения платформы про хранилище конфигурации называют причину, но не действие. Действие
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-load-xml v1.22 — Load 1C configuration from XML files # db-load-xml v1.23 — 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
@@ -72,10 +72,115 @@ IBCMD_OWNED_KEYS = [
"--import", "--export", "--apply", "--force", "--create-database", "--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password", "--user", "--password",
] ]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"] V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"] IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
# --- Реквизиты хранилища из .v8-project.json ---
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
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 same_path(a, b):
if not a or not b:
return False
try:
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
except Exception:
return False
def find_project_database(args):
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
pf = _sg_find_v8project(os.getcwd())
if not pf:
return None
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
except Exception:
return None
for db in proj.get("databases") or []:
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
return db
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
if (db["server"].lower() == args.InfoBaseServer.lower()
and db["ref"].lower() == args.InfoBaseRef.lower()):
return db
return None
def resolve_repository_settings(args):
"""Возвращает dict path/user/password/from_registry. Явные -Repository* сильнее реестра."""
db_rec = find_project_database(args)
rec = None
if db_rec:
if args.Extension:
# У расширения СВОЁ хранилище со своим путём (проверено): выбирается парой
# /ConfigurationRepositoryF"<путь расширения>" + -Extension "<Имя>".
for ext in db_rec.get("extensions") or []:
if (ext.get("name") or "").lower() == args.Extension.lower():
rec = ext.get("repository")
break
else:
rec = db_rec.get("repository")
path = args.RepositoryPath or ((rec or {}).get("path") or None)
user = args.RepositoryUser or ((rec or {}).get("user") or None)
# Пустой пароль = отсутствующий: 1С требует опускать ключ целиком, а не передавать пустое значение.
pwd = args.RepositoryPassword or ((rec or {}).get("password") or None)
return {
"path": path.strip().strip('"') if path else None,
"user": user,
"password": pwd,
"from_registry": bool(rec and rec.get("path")),
}
def repository_args(repo):
"""Ключи доступа к хранилищу. Форма — кавычки ВНУТРИ токена, как у /N и /P."""
a = []
if not repo or not repo.get("path"):
return a
a.append('/ConfigurationRepositoryF"%s"' % repo["path"])
if repo.get("user"):
a.append('/ConfigurationRepositoryN"%s"' % repo["user"])
if repo.get("password"):
a.append('/ConfigurationRepositoryP"%s"' % repo["password"])
return a
# Сообщения платформы про хранилище конфигурации называют причину, но не действие. Действие
# дописываем сами: без него модель упирается в отказ и не знает, чем его лечить.
def write_repository_hints(log_text):
if not log_text:
return
if "текущая конфигурация помещена в хранилище" in log_text:
print("[hint] полная загрузка в базу, подключённую к хранилищу, невозможна.")
print(" Используйте -Mode Partial, предварительно захватив объекты: /db-repo lock")
for m in re.finditer(r"объект метаданных (\S+) не захвачен в хранилище", log_text):
obj = m.group(1)
if obj == "Configuration":
print("[hint] не захвачен корень конфигурации — он нужен, чтобы добавить или удалить объект:")
print(' /db-repo lock <база> -Objects "Конфигурация"')
else:
print('[hint] объект не захвачен в хранилище: /db-repo lock <база> -Objects "%s"' % obj)
if "Соединение с хранилищем конфигурации не установлено" in log_text:
print("[hint] база подключена к хранилищу, но его реквизиты неизвестны.")
print(' Добавьте "repository" в запись базы в .v8-project.json (см. /db-list).')
def arg_key_match(token, key): def arg_key_match(token, key):
"""Token matches a key when it equals it, or starts with it and the next character """Token matches a key when it equals it, or starts with it and the next character
is not a letter — catches glued /N"user" and --password=x, while keeping is not a letter — catches glued /N"user" and --password=x, while keeping
@@ -438,6 +543,9 @@ def main():
parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server") parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server")
parser.add_argument("-UserName", default="", help="1C user name") parser.add_argument("-UserName", default="", help="1C user name")
parser.add_argument("-Password", default="", help="1C user password") parser.add_argument("-Password", default="", help="1C user password")
parser.add_argument("-RepositoryPath", default="")
parser.add_argument("-RepositoryUser", default="")
parser.add_argument("-RepositoryPassword", default="")
parser.add_argument("-ConfigDir", required=True, help="Directory with XML configuration sources") parser.add_argument("-ConfigDir", required=True, help="Directory with XML configuration sources")
parser.add_argument( parser.add_argument(
"-Mode", "-Mode",
@@ -593,6 +701,11 @@ def main():
if args.Password: if args.Password:
arguments.append(f'/P"{args.Password}"') arguments.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"'] arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"']
if args.Mode == "Full": if args.Mode == "Full":
@@ -652,7 +765,7 @@ def main():
arguments.extend(quote_if_needed(a) for a in extra_args) arguments.extend(quote_if_needed(a) for a in extra_args)
# --- Execute --- # --- Execute ---
print(f"Running: 1cv8.exe {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName)}") print(f"Running: 1cv8.exe {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName, repo['password'])}")
result = run_v8(v8path, arguments) result = run_v8(v8path, arguments)
exit_code = result.returncode exit_code = result.returncode
@@ -685,6 +798,7 @@ def main():
print("--- End ---") print("--- End ---")
print_platform_output(result) print_platform_output(result)
write_repository_hints(log_content)
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы # Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку # разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
# про -StrictLog не даём: загрузка уже выполнена, повторять её ради того же текста незачем. # про -StrictLog не даём: загрузка уже выполнена, повторять её ради того же текста незачем.
+6 -6
View File
@@ -76,7 +76,7 @@ allowed-tools:
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" <подкоманда> <параметры> powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command <подкоманда> <параметры>
``` ```
### Рабочий цикл ### Рабочий цикл
@@ -175,19 +175,19 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" <по
```powershell ```powershell
# Захватить справочник вместе с подчинёнными объектами # Захватить справочник вместе с подчинёнными объектами
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" lock -InfoBasePath "C:\Bases\MyDB" -Objects "Справочник.Номенклатура" -WithChildren powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command lock -InfoBasePath "C:\Bases\MyDB" -Objects "Справочник.Номенклатура" -WithChildren
# Захватить корень вместе с новым объектом # Захватить корень вместе с новым объектом
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" lock -InfoBasePath "C:\Bases\MyDB" -Objects "Конфигурация,Справочник.Склады" powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command lock -InfoBasePath "C:\Bases\MyDB" -Objects "Конфигурация,Справочник.Склады"
# Поместить с комментарием, оставив захват # Поместить с комментарием, оставив захват
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" commit -InfoBasePath "C:\Bases\MyDB" -Objects "Справочник.Номенклатура" -Comment "Добавлен реквизит Артикул" -KeepLocked powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command commit -InfoBasePath "C:\Bases\MyDB" -Objects "Справочник.Номенклатура" -Comment "Добавлен реквизит Артикул" -KeepLocked
# Получить изменения из хранилища # Получить изменения из хранилища
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" update -InfoBasePath "C:\Bases\MyDB" powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command update -InfoBasePath "C:\Bases\MyDB"
# Серверная база, расширение # Серверная база, расширение
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" lock -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Extension "МоёРасширение" -Objects "Справочник.Номенклатура" powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-repo.ps1" -Command lock -InfoBaseServer "srv01" -InfoBaseRef "MyApp_Dev" -UserName "Admin" -Extension "МоёРасширение" -Objects "Справочник.Номенклатура"
``` ```
## После выполнения ## После выполнения
+3 -3
View File
@@ -3,7 +3,7 @@
## create — создать хранилище ## create — создать хранилище
```powershell ```powershell
... db-repo.ps1 create -InfoBasePath "C:\Bases\MyDB" -RepositoryPath "C:\Repo\MyApp" -RepositoryUser "Admin" -RepositoryPassword "" ... db-repo.ps1 -Command create -InfoBasePath "C:\Bases\MyDB" -RepositoryPath "C:\Repo\MyApp" -RepositoryUser "Admin" -RepositoryPassword ""
``` ```
| Параметр | Описание | | Параметр | Описание |
@@ -22,7 +22,7 @@
## add-user — создать пользователя ## add-user — создать пользователя
```powershell ```powershell
... db-repo.ps1 add-user -InfoBasePath "C:\Bases\MyDB" -NewUser "Ivanov" -NewUserPassword "" -Rights LockObjects ... db-repo.ps1 -Command add-user -InfoBasePath "C:\Bases\MyDB" -NewUser "Ivanov" -NewUserPassword "" -Rights LockObjects
``` ```
| Право | Что даёт | | Право | Что даёт |
@@ -38,7 +38,7 @@
## copy-users — скопировать пользователей из другого хранилища ## copy-users — скопировать пользователей из другого хранилища
```powershell ```powershell
... db-repo.ps1 copy-users -InfoBasePath "C:\Bases\MyDB" -SourcePath "\\srv01\repo\Other" -SourceUser "Admin" -SourcePassword "" ... db-repo.ps1 -Command copy-users -InfoBasePath "C:\Bases\MyDB" -SourcePath "\\srv01\repo\Other" -SourceUser "Admin" -SourcePassword ""
``` ```
`-SourcePath`, `-SourceUser`, `-SourcePassword` описывают хранилище-**источник**. Удалённые пользователи `-SourcePath`, `-SourceUser`, `-SourcePassword` описывают хранилище-**источник**. Удалённые пользователи
+2 -2
View File
@@ -3,7 +3,7 @@
## connect — подключить ## connect — подключить
```powershell ```powershell
... db-repo.ps1 connect -InfoBasePath "C:\Bases\MyDB" -RepositoryPath "\\srv01\repo\MyApp" -RepositoryUser "Ivanov" -RepositoryPassword "" ... db-repo.ps1 -Command connect -InfoBasePath "C:\Bases\MyDB" -RepositoryPath "\\srv01\repo\MyApp" -RepositoryUser "Ivanov" -RepositoryPassword ""
``` ```
| Параметр | Описание | | Параметр | Описание |
@@ -19,7 +19,7 @@
## disconnect — отключить ## disconnect — отключить
```powershell ```powershell
... db-repo.ps1 disconnect -InfoBasePath "C:\Bases\MyDB" ... db-repo.ps1 -Command disconnect -InfoBasePath "C:\Bases\MyDB"
``` ```
**Спроси подтверждение у пользователя.** Операция необратима. Если пользователь аутентифицируется в хранилище, отключение отражается и **Спроси подтверждение у пользователя.** Операция необратима. Если пользователь аутентифицируется в хранилище, отключение отражается и
+2 -2
View File
@@ -3,7 +3,7 @@
## report — отчёт по версиям ## report — отчёт по версиям
```powershell ```powershell
... db-repo.ps1 report -InfoBasePath "C:\Bases\MyDB" -OutputFile "C:\tmp\repo.txt" ... db-repo.ps1 -Command report -InfoBasePath "C:\Bases\MyDB" -OutputFile "C:\tmp\repo.txt"
``` ```
| Параметр | Описание | | Параметр | Описание |
@@ -35,7 +35,7 @@
## dump-cfg — выгрузить версию в CF ## dump-cfg — выгрузить версию в CF
```powershell ```powershell
... db-repo.ps1 dump-cfg -InfoBasePath "C:\Bases\MyDB" -OutputFile "C:\tmp\v120.cf" -Version 120 ... db-repo.ps1 -Command dump-cfg -InfoBasePath "C:\Bases\MyDB" -OutputFile "C:\tmp\v120.cf" -Version 120
``` ```
Без `-Version` (или при `-1`) выгружается последняя версия. Без `-Version` (или при `-1`) выгружается последняя версия.
+3 -3
View File
@@ -3,7 +3,7 @@
## set-label — метка на версию ## set-label — метка на версию
```powershell ```powershell
... db-repo.ps1 set-label -InfoBasePath "C:\Bases\MyDB" -Label "Релиз 1.2" -Version 120 -Comment "Передано в тест" ... db-repo.ps1 -Command set-label -InfoBasePath "C:\Bases\MyDB" -Label "Релиз 1.2" -Version 120 -Comment "Передано в тест"
``` ```
Без `-Version` метка ставится на последнюю версию. Несуществующая версия — ошибка. Без `-Version` метка ставится на последнюю версию. Несуществующая версия — ошибка.
@@ -11,7 +11,7 @@
## optimize — оптимизация хранения ## optimize — оптимизация хранения
```powershell ```powershell
... db-repo.ps1 optimize -InfoBasePath "C:\Bases\MyDB" ... db-repo.ps1 -Command optimize -InfoBasePath "C:\Bases\MyDB"
``` ```
Оптимизирует хранение данных в хранилище. Операция долгая. Оптимизирует хранение данных в хранилище. Операция долгая.
@@ -19,7 +19,7 @@
## clear-cache — очистка кеша ## clear-cache — очистка кеша
```powershell ```powershell
... db-repo.ps1 clear-cache -InfoBasePath "C:\Bases\MyDB" -CacheScope local ... db-repo.ps1 -Command clear-cache -InfoBasePath "C:\Bases\MyDB" -CacheScope local
``` ```
| `-CacheScope` | Что чистит | | `-CacheScope` | Что чистит |
+8 -2
View File
@@ -1,4 +1,4 @@
# db-repo v1.3 — 1C configuration repository operations # db-repo v1.4 — 1C configuration repository operations
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# NB: движок только 1cv8 — ibcmd работу с хранилищем не поддерживает (нет такого режима). # NB: движок только 1cv8 — ibcmd работу с хранилищем не поддерживает (нет такого режима).
<# <#
@@ -31,7 +31,9 @@
[CmdletBinding(PositionalBinding=$false)] [CmdletBinding(PositionalBinding=$false)]
param( param(
[Parameter(Mandatory=$true, Position=0)] # Не Mandatory: обязательный параметр PowerShell запрашивает интерактивно, а в пакетном
# запуске это зависание. Пустое значение проверяем сами.
[Parameter(Mandatory=$false)]
[string]$Command, [string]$Command,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
@@ -946,6 +948,10 @@ function Write-RepoVerdict {
# =============================== main =============================== # =============================== main ===============================
if (-not $Command) {
Write-Host "Error: -Command is required. Known: $(($script:CommandKeys.Keys | Sort-Object) -join ', ')" -ForegroundColor Red
exit 1
}
$cmd = Resolve-Command $Command $cmd = Resolve-Command $Command
if (-not $InfoBasePath -and -not ($InfoBaseServer -and $InfoBaseRef)) { if (-not $InfoBasePath -and -not ($InfoBaseServer -and $InfoBaseRef)) {
File diff suppressed because it is too large Load Diff
@@ -197,7 +197,7 @@ function Get-RepositoryArgs {
$a += "/ConfigurationRepositoryF`"$($Repo.Path)`"" $a += "/ConfigurationRepositoryF`"$($Repo.Path)`""
if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" } if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" }
if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" } if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" }
return ,$a return $a
} }
function Protect-Secrets { function Protect-Secrets {
+96 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# db-update v1.16 — Update 1C database configuration # db-update v1.17 — 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
@@ -72,10 +72,95 @@ IBCMD_OWNED_KEYS = [
"--import", "--export", "--apply", "--force", "--create-database", "--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password", "--user", "--password",
] ]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"] V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"] IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
# --- Реквизиты хранилища из .v8-project.json ---
# Модель их не передаёт: скрипт сопоставляет параметры соединения с записью в databases[]
# и берёт repository оттуда. Тот же приём, что в cf-edit.py (сопоставление по configSrc).
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 same_path(a, b):
if not a or not b:
return False
try:
return os.path.abspath(a).rstrip("\\/").lower() == os.path.abspath(b).rstrip("\\/").lower()
except Exception:
return False
def find_project_database(args):
"""Запись базы в реестре, соответствующая переданному соединению. None, если не найдена."""
pf = _sg_find_v8project(os.getcwd())
if not pf:
return None
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
except Exception:
return None
for db in proj.get("databases") or []:
if args.InfoBasePath and db.get("path") and same_path(db["path"], args.InfoBasePath):
return db
if args.InfoBaseServer and args.InfoBaseRef and db.get("server") and db.get("ref"):
if (db["server"].lower() == args.InfoBaseServer.lower()
and db["ref"].lower() == args.InfoBaseRef.lower()):
return db
return None
def resolve_repository_settings(args):
"""Возвращает dict path/user/password/from_registry. Явные -Repository* сильнее реестра."""
db_rec = find_project_database(args)
rec = None
if db_rec:
if args.Extension:
# У расширения СВОЁ хранилище со своим путём (проверено): выбирается парой
# /ConfigurationRepositoryF"<путь расширения>" + -Extension "<Имя>".
for ext in db_rec.get("extensions") or []:
if (ext.get("name") or "").lower() == args.Extension.lower():
rec = ext.get("repository")
break
else:
rec = db_rec.get("repository")
path = args.RepositoryPath or ((rec or {}).get("path") or None)
user = args.RepositoryUser or ((rec or {}).get("user") or None)
# Пустой пароль = отсутствующий: 1С требует опускать ключ целиком, а не передавать пустое значение.
pwd = args.RepositoryPassword or ((rec or {}).get("password") or None)
return {
"path": path.strip().strip('"') if path else None,
"user": user,
"password": pwd,
"from_registry": bool(rec and rec.get("path")),
}
def repository_args(repo):
"""Ключи доступа к хранилищу. Форма — кавычки ВНУТРИ токена, как у /N и /P."""
a = []
if not repo or not repo.get("path"):
return a
a.append('/ConfigurationRepositoryF"%s"' % repo["path"])
if repo.get("user"):
a.append('/ConfigurationRepositoryN"%s"' % repo["user"])
if repo.get("password"):
a.append('/ConfigurationRepositoryP"%s"' % repo["password"])
return a
def arg_key_match(token, key): def arg_key_match(token, key):
"""Token matches a key when it equals it, or starts with it and the next character """Token matches a key when it equals it, or starts with it and the next character
is not a letter — catches glued /N"user" and --password=x, while keeping is not a letter — catches glued /N"user" and --password=x, while keeping
@@ -438,6 +523,9 @@ def main():
parser.add_argument("-InfoBaseRef", default="") parser.add_argument("-InfoBaseRef", default="")
parser.add_argument("-UserName", default="") parser.add_argument("-UserName", default="")
parser.add_argument("-Password", default="") parser.add_argument("-Password", default="")
parser.add_argument("-RepositoryPath", default="")
parser.add_argument("-RepositoryUser", default="")
parser.add_argument("-RepositoryPassword", default="")
parser.add_argument("-Extension", default="") parser.add_argument("-Extension", default="")
parser.add_argument("-AllExtensions", action="store_true") parser.add_argument("-AllExtensions", action="store_true")
parser.add_argument("-Dynamic", default="", choices=["", "+", "-"]) parser.add_argument("-Dynamic", default="", choices=["", "+", "-"])
@@ -530,6 +618,11 @@ def main():
if args.Password: if args.Password:
arguments.append(f'/P"{args.Password}"') arguments.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
arguments.append("/UpdateDBCfg") arguments.append("/UpdateDBCfg")
# --- Options --- # --- Options ---
@@ -553,7 +646,7 @@ def main():
arguments.extend(quote_if_needed(a) for a in extra_args) arguments.extend(quote_if_needed(a) for a in extra_args)
# --- Execute --- # --- Execute ---
print(f"Running: 1cv8.exe {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName)}") print(f"Running: 1cv8.exe {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName, repo['password'])}")
result = run_v8(v8path, arguments) result = run_v8(v8path, arguments)
exit_code = result.returncode exit_code = result.returncode
+40 -8
View File
@@ -67,8 +67,12 @@ const FAMILIES = [
{ {
name: 'support-guard: find_v8project', py: '_sg_find_v8project', ps1: 'Find-V8Project', name: 'support-guard: find_v8project', py: '_sg_find_v8project', ps1: 'Find-V8Project',
variants: [ variants: [
// Имя с префиксом _sg_ историческое: функция просто ищет .v8-project.json обходом
// вверх. Группа db-* использует её же, чтобы найти запись базы и взять реквизиты
// хранилища — задача одна, поэтому семья общая, а не вторая с тем же телом.
{ id: 'full', authority: 'cf-edit', { id: 'full', authority: 'cf-edit',
consumers: ['form-add', 'form-compile', 'form-edit', 'help-add', 'interface-edit', 'meta-compile', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-repo', 'db-update',
'form-add', 'form-compile', 'form-edit', 'help-add', 'interface-edit', 'meta-compile',
'meta-edit', 'meta-remove', 'mxl-compile', 'role-compile', 'skd-compile', 'skd-edit', 'meta-edit', 'meta-remove', 'mxl-compile', 'role-compile', 'skd-compile', 'skd-edit',
'subsystem-compile', 'subsystem-edit', 'template-add', 'xdto-compile', 'xdto-edit'] }, 'subsystem-compile', 'subsystem-edit', 'template-add', 'xdto-compile', 'xdto-edit'] },
], ],
@@ -246,6 +250,8 @@ const FAMILIES = [
{ id: 'base', authority: 'db-create', { id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git', consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-run', 'db-update', 'epf-build', 'epf-dump'] }, 'db-load-xml', 'db-run', 'db-update', 'epf-build', 'epf-dump'] },
{ id: 'v8-only', authority: 'db-repo', consumers: [],
why: 'хранилище конфигурации ibcmd не поддерживает вовсе — нет такого режима, поэтому ветки ibcmd нет; вместо неё проверка усечённых ключей /ConfigurationRepository*, которые платформа не считает ошибкой, а запускает конфигуратор интерактивно' },
], ],
}, },
{ {
@@ -254,7 +260,7 @@ const FAMILIES = [
// db-run запускает Предприятие и не ждёт процесс — общей обвязки запуска не использует. // db-run запускает Предприятие и не ждёт процесс — общей обвязки запуска не использует.
{ id: 'base', authority: 'db-create', { id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git', consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-update', 'epf-build', 'epf-dump'] }, 'db-load-xml', 'db-repo', 'db-update', 'epf-build', 'epf-dump'] },
], ],
}, },
{ {
@@ -262,7 +268,7 @@ const FAMILIES = [
variants: [ variants: [
{ id: 'base', authority: 'db-create', { id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git', consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-update', 'epf-build', 'epf-dump'] }, 'db-load-xml', 'db-repo', 'db-update', 'epf-build', 'epf-dump'] },
], ],
}, },
{ {
@@ -270,7 +276,7 @@ const FAMILIES = [
variants: [ variants: [
{ id: 'base', authority: 'db-dump-cf', { id: 'base', authority: 'db-dump-cf',
consumers: ['db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git', consumers: ['db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-run', 'db-update', 'epf-build', 'epf-dump'] }, 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump'] },
], ],
}, },
{ {
@@ -278,7 +284,7 @@ const FAMILIES = [
variants: [ variants: [
{ id: 'base', authority: 'db-create', { id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git', consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] }, 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] },
], ],
}, },
{ {
@@ -286,7 +292,7 @@ const FAMILIES = [
variants: [ variants: [
{ id: 'base', authority: 'db-create', { id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git', consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] }, 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] },
], ],
}, },
{ {
@@ -294,10 +300,36 @@ const FAMILIES = [
variants: [ variants: [
{ id: 'base', authority: 'db-create', { id: 'base', authority: 'db-create',
consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git', consumers: ['db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git',
'db-load-xml', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] }, 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump', 'web-publish'] },
], ],
}, },
{
name: 'repository: load hints', py: 'write_repository_hints', ps1: 'Write-RepositoryHints',
variants: [{ id: 'base', authority: 'db-load-xml', consumers: ['db-load-git'] }],
},
// ─── Реквизиты хранилища конфигурации ────────────────────────────────────
// База под хранилищем не принимает ни одной операции конфигуратора без реквизитов
// доступа, и модель их не передаёт: скрипт сам сопоставляет параметры соединения с
// записью в databases[]. Блок копируется во все навыки группы, работающие с
// конфигурацией базы.
{
name: 'repository: same_path', py: 'same_path', ps1: 'Test-SamePath',
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update'] }],
},
{
name: 'repository: find_project_database', py: 'find_project_database', ps1: 'Find-ProjectDatabase',
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update'] }],
},
{
name: 'repository: resolve_settings', py: 'resolve_repository_settings', ps1: 'Resolve-RepositorySettings',
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update'] }],
},
{
name: 'repository: args', py: 'repository_args', ps1: 'Get-RepositoryArgs',
variants: [{ id: 'base', authority: 'db-repo', consumers: ['db-dump-xml', 'db-load-git', 'db-load-xml', 'db-update'] }],
},
// ─── Значения свойств-перечислений ─────────────────────────────────────── // ─── Значения свойств-перечислений ───────────────────────────────────────
// Сама функция одинакова в обоих портах; СПИСКИ значений, на которые она опирается, держит // Сама функция одинакова в обоих портах; СПИСКИ значений, на которые она опирается, держит
// отдельный гард check-enum-drift.mjs (авторитет тот же — meta-compile). // отдельный гард check-enum-drift.mjs (авторитет тот же — meta-compile).
@@ -334,7 +366,7 @@ const FAMILIES = [
consumers: [ consumers: [
'cf-edit', 'cf-info', 'cf-init', 'cf-validate', 'cfe-borrow', 'cfe-diff', 'cfe-init', 'cf-edit', 'cf-info', 'cf-init', 'cf-validate', 'cfe-borrow', 'cfe-diff', 'cfe-init',
'cfe-patch-method', 'cfe-validate', 'db-create', 'db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'cfe-patch-method', 'cfe-validate', 'db-create', 'db-dump-cf', 'db-dump-dt', 'db-dump-xml',
'db-load-cf', 'db-load-dt', 'db-load-git', 'db-load-xml', 'db-run', 'db-update', 'epf-build', 'db-load-cf', 'db-load-dt', 'db-load-git', 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build',
'epf-dump', 'epf-init', 'epf-validate', 'erf-init', 'form-add', 'form-compile', 'epf-dump', 'epf-init', 'epf-validate', 'erf-init', 'form-add', 'form-compile',
'form-decompile', 'form-edit', 'form-info', 'form-remove', 'form-validate', 'help-add', 'form-decompile', 'form-edit', 'form-info', 'form-remove', 'form-validate', 'help-add',
'img-grid', 'interface-edit', 'interface-validate', 'meta-decompile', 'meta-edit', 'meta-info', 'img-grid', 'interface-edit', 'interface-validate', 'meta-decompile', 'meta-edit', 'meta-info',