mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-03 00:30:52 +03:00
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:
co-authored by
Claude Opus 5
parent
012f5c3c30
commit
1327b4fc5a
@@ -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 не даём: загрузка уже выполнена, повторять её ради того же текста незачем.
|
||||||
|
|||||||
@@ -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,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` описывают хранилище-**источник**. Удалённые пользователи
|
||||||
|
|||||||
@@ -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"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Спроси подтверждение у пользователя.** Операция необратима. Если пользователь аутентифицируется в хранилище, отключение отражается и
|
**Спроси подтверждение у пользователя.** Операция необратима. Если пользователь аутентифицируется в хранилище, отключение отражается и
|
||||||
|
|||||||
@@ -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,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` | Что чистит |
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user