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)`""
if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" }
if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" }
return ,$a
return $a
}
function Protect-Secrets {
@@ -1,5 +1,5 @@
#!/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
import argparse
@@ -72,10 +72,95 @@ IBCMD_OWNED_KEYS = [
"--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password",
]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
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):
"""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
@@ -388,6 +473,9 @@ def main():
parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server")
parser.add_argument("-UserName", default="", help="1C user name")
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(
"-Mode",
@@ -396,6 +484,7 @@ def main():
help="Dump mode (default: Changes)",
)
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("-AllExtensions", action="store_true", help="Dump all extensions")
parser.add_argument(
@@ -443,8 +532,19 @@ def main():
sys.exit(1)
# --- 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:
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)
# --- Create output dir if needed ---
@@ -513,6 +613,11 @@ def main():
if args.Password:
arguments.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
arguments += ["/DumpConfigToFiles", f'"{args.ConfigDir}"']
arguments += ["-Format", args.Format]
@@ -551,7 +656,7 @@ def main():
arguments.extend(quote_if_needed(a) for a in extra_args)
# --- 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)
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
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
<#
@@ -216,7 +216,31 @@ function Get-RepositoryArgs {
$a += "/ConfigurationRepositoryF`"$($Repo.Path)`""
if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" }
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 {
@@ -816,6 +840,7 @@ try {
}
}
Write-PlatformOutput $__v8.Output
Write-RepositoryHints $logContent
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
@@ -1,5 +1,5 @@
#!/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
import argparse
@@ -72,10 +72,115 @@ IBCMD_OWNED_KEYS = [
"--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password",
]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
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):
"""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
@@ -460,6 +565,9 @@ def main():
parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server")
parser.add_argument("-UserName", default="", help="1C user name")
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(
"-Source",
@@ -704,6 +812,11 @@ def main():
if args.Password:
arguments.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"']
arguments += ["-listFile", f'"{list_file}"']
arguments += ["-Format", args.Format]
@@ -729,7 +842,7 @@ def main():
# --- Execute ---
print("")
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)
exit_code = result.returncode
@@ -754,6 +867,7 @@ def main():
pass
print_platform_output(result)
write_repository_hints(log_content)
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
@@ -210,7 +210,7 @@ function Get-RepositoryArgs {
$a += "/ConfigurationRepositoryF`"$($Repo.Path)`""
if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" }
if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" }
return ,$a
return $a
}
# Сообщения платформы про хранилище конфигурации называют причину, но не действие. Действие
@@ -1,5 +1,5 @@
#!/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
import argparse
@@ -72,10 +72,115 @@ IBCMD_OWNED_KEYS = [
"--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password",
]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
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):
"""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
@@ -438,6 +543,9 @@ def main():
parser.add_argument("-InfoBaseRef", default="", help="Infobase name on server")
parser.add_argument("-UserName", default="", help="1C user name")
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(
"-Mode",
@@ -593,6 +701,11 @@ def main():
if args.Password:
arguments.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"']
if args.Mode == "Full":
@@ -652,7 +765,7 @@ def main():
arguments.extend(quote_if_needed(a) for a in extra_args)
# --- 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)
exit_code = result.returncode
@@ -685,6 +798,7 @@ def main():
print("--- End ---")
print_platform_output(result)
write_repository_hints(log_content)
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
# про -StrictLog не даём: загрузка уже выполнена, повторять её ради того же текста незачем.
+6 -6
View File
@@ -76,7 +76,7 @@ allowed-tools:
## Команда
```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.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 — создать хранилище
```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 — создать пользователя
```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 — скопировать пользователей из другого хранилища
```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` описывают хранилище-**источник**. Удалённые пользователи
+2 -2
View File
@@ -3,7 +3,7 @@
## connect — подключить
```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 — отключить
```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 — отчёт по версиям
```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
```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`) выгружается последняя версия.
+3 -3
View File
@@ -3,7 +3,7 @@
## set-label — метка на версию
```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` метка ставится на последнюю версию. Несуществующая версия — ошибка.
@@ -11,7 +11,7 @@
## optimize — оптимизация хранения
```powershell
... db-repo.ps1 optimize -InfoBasePath "C:\Bases\MyDB"
... db-repo.ps1 -Command optimize -InfoBasePath "C:\Bases\MyDB"
```
Оптимизирует хранение данных в хранилище. Операция долгая.
@@ -19,7 +19,7 @@
## clear-cache — очистка кеша
```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` | Что чистит |
+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
# NB: движок только 1cv8 — ibcmd работу с хранилищем не поддерживает (нет такого режима).
<#
@@ -31,7 +31,9 @@
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$true, Position=0)]
# Не Mandatory: обязательный параметр PowerShell запрашивает интерактивно, а в пакетном
# запуске это зависание. Пустое значение проверяем сами.
[Parameter(Mandatory=$false)]
[string]$Command,
[Parameter(Mandatory=$false)]
@@ -946,6 +948,10 @@ function Write-RepoVerdict {
# =============================== 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
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)`""
if ($Repo.User) { $a += "/ConfigurationRepositoryN`"$($Repo.User)`"" }
if ($Repo.Password) { $a += "/ConfigurationRepositoryP`"$($Repo.Password)`"" }
return ,$a
return $a
}
function Protect-Secrets {
+96 -3
View File
@@ -1,5 +1,5 @@
#!/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
import argparse
@@ -72,10 +72,95 @@ IBCMD_OWNED_KEYS = [
"--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password",
]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP", "/ConfigurationRepositoryP"]
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):
"""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
@@ -438,6 +523,9 @@ def main():
parser.add_argument("-InfoBaseRef", default="")
parser.add_argument("-UserName", 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("-AllExtensions", action="store_true")
parser.add_argument("-Dynamic", default="", choices=["", "+", "-"])
@@ -530,6 +618,11 @@ def main():
if args.Password:
arguments.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
arguments.append("/UpdateDBCfg")
# --- Options ---
@@ -553,7 +646,7 @@ def main():
arguments.extend(quote_if_needed(a) for a in extra_args)
# --- 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)
exit_code = result.returncode