elemctl 0.43.2__tar.gz → 0.44.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {elemctl-0.43.2 → elemctl-0.44.0}/PKG-INFO +1 -1
  2. {elemctl-0.43.2 → elemctl-0.44.0}/docs/SPEC.md +9 -1
  3. {elemctl-0.43.2 → elemctl-0.44.0}/docs/SPEC.ru.md +9 -1
  4. {elemctl-0.43.2 → elemctl-0.44.0}/docs/changelog.md +9 -0
  5. {elemctl-0.43.2 → elemctl-0.44.0}/docs/changelog.ru.md +9 -0
  6. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/__init__.py +1 -1
  7. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/i18n.py +27 -0
  8. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/mcp_server.py +3 -1
  9. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/probe.py +60 -1
  10. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl.egg-info/PKG-INFO +1 -1
  11. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_i18n.py +3 -0
  12. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_probe.py +100 -1
  13. {elemctl-0.43.2 → elemctl-0.44.0}/.env.example +0 -0
  14. {elemctl-0.43.2 → elemctl-0.44.0}/LICENSE +0 -0
  15. {elemctl-0.43.2 → elemctl-0.44.0}/MANIFEST.in +0 -0
  16. {elemctl-0.43.2 → elemctl-0.44.0}/NOTICE +0 -0
  17. {elemctl-0.43.2 → elemctl-0.44.0}/ORIGIN.md +0 -0
  18. {elemctl-0.43.2 → elemctl-0.44.0}/README.md +0 -0
  19. {elemctl-0.43.2 → elemctl-0.44.0}/docs/cli.md +0 -0
  20. {elemctl-0.43.2 → elemctl-0.44.0}/docs/cli.ru.md +0 -0
  21. {elemctl-0.43.2 → elemctl-0.44.0}/docs/config.md +0 -0
  22. {elemctl-0.43.2 → elemctl-0.44.0}/docs/config.ru.md +0 -0
  23. {elemctl-0.43.2 → elemctl-0.44.0}/docs/index.md +0 -0
  24. {elemctl-0.43.2 → elemctl-0.44.0}/docs/index.ru.md +0 -0
  25. {elemctl-0.43.2 → elemctl-0.44.0}/docs/library.md +0 -0
  26. {elemctl-0.43.2 → elemctl-0.44.0}/docs/library.ru.md +0 -0
  27. {elemctl-0.43.2 → elemctl-0.44.0}/docs/mcp.md +0 -0
  28. {elemctl-0.43.2 → elemctl-0.44.0}/docs/mcp.ru.md +0 -0
  29. {elemctl-0.43.2 → elemctl-0.44.0}/docs/platform.md +0 -0
  30. {elemctl-0.43.2 → elemctl-0.44.0}/docs/platform.ru.md +0 -0
  31. {elemctl-0.43.2 → elemctl-0.44.0}/pyproject.toml +0 -0
  32. {elemctl-0.43.2 → elemctl-0.44.0}/setup.cfg +0 -0
  33. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/__main__.py +0 -0
  34. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/auth.py +0 -0
  35. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/build.py +0 -0
  36. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/cli.py +0 -0
  37. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/client.py +0 -0
  38. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/config.py +0 -0
  39. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/deploy.py +0 -0
  40. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/errors.py +0 -0
  41. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/plugins.py +0 -0
  42. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/schema.py +0 -0
  43. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/selfupdate.py +0 -0
  44. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/transport.py +0 -0
  45. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl/versions.py +0 -0
  46. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl.egg-info/SOURCES.txt +0 -0
  47. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl.egg-info/dependency_links.txt +0 -0
  48. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl.egg-info/entry_points.txt +0 -0
  49. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl.egg-info/requires.txt +0 -0
  50. {elemctl-0.43.2 → elemctl-0.44.0}/src/elemctl.egg-info/top_level.txt +0 -0
  51. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_build.py +0 -0
  52. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_changelog_link.py +0 -0
  53. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_cli.py +0 -0
  54. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_cli_docs.py +0 -0
  55. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_client.py +0 -0
  56. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_config.py +0 -0
  57. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_conventions.py +0 -0
  58. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_deploy.py +0 -0
  59. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_docs_coverage.py +0 -0
  60. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_extract_adapter.py +0 -0
  61. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_mcp.py +0 -0
  62. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_plugins.py +0 -0
  63. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_rebuild_docs.py +0 -0
  64. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_release_notes.py +0 -0
  65. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_schema.py +0 -0
  66. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_selfupdate.py +0 -0
  67. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_transport.py +0 -0
  68. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_user_lists.py +0 -0
  69. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_versions.py +0 -0
  70. {elemctl-0.43.2 → elemctl-0.44.0}/tests/test_workflows.py +0 -0
  71. {elemctl-0.43.2 → elemctl-0.44.0}/tools/extract_adapter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elemctl
3
- Version: 0.43.2
3
+ Version: 0.44.0
4
4
  Summary: CLI, MCP server and library for the Console API v2 of the 1C:Enterprise.Element platform (1cmycloud): applications, builds from source, one-command deploy with verification, a compile probe
5
5
  Author: KeyFire
6
6
  License-Expression: MIT
@@ -293,7 +293,15 @@ Commands, with the significant flags in parentheses:
293
293
  - `probe [--project-dir --output --build-version --name --space-id --keep
294
294
  --require-clean]` – an isolated compilation check of the sources: build ->
295
295
  upload -> a throwaway application (that is the compilation, section 6.10) ->
296
- errors with file and position -> cleanup. `ELEMENT_APP_ID` and
296
+ errors with file and position -> cleanup. Before the build the manifest is
297
+ checked for what the server needs to take a probe: `Представление`
298
+ (`Presentation`), without which the console refuses the upload with a bare 500;
299
+ `ЯзыкРазработки` (`DevelopmentLanguage`), a required property without which no
300
+ application is created; and `ЯзыкиЛокализации` (`LocalizationLanguages`)
301
+ whenever `ЯзыкПоУмолчанию` (`DefaultLanguage`) is set, since the server refuses
302
+ that pair without naming the cause. A missing key stops the probe before
303
+ anything is built or uploaded, and the error names every such key with the
304
+ line to add, in the spelling the manifest uses. `ELEMENT_APP_ID` and
297
305
  `ELEMENT_PROJECT_ID` are deliberately left unused: the probe must not be able to
298
306
  reach the working application, and the target project is chosen by the platform
299
307
  out of the vendor and the name of the manifest (section 6.8). The default build
@@ -668,7 +668,15 @@ stdout перенаправляется в stderr, и в настоящий stdo
668
668
  - `probe [--project-dir --output --build-version --name --space-id --keep
669
669
  --require-clean]` - изолированная проверка компиляции исходников: сборка ->
670
670
  загрузка -> одноразовое приложение (это и есть компиляция, п. 6.10) -> ошибки
671
- с файлом и позицией -> уборка. `ELEMENT_APP_ID` и `ELEMENT_PROJECT_ID`
671
+ с файлом и позицией -> уборка. До сборки манифест проверяется на то, без чего
672
+ сервер пробник не примет. Это `Представление` (`Presentation`): без него
673
+ консоль отвечает на загрузку 500 без объяснения. Это `ЯзыкРазработки`
674
+ (`DevelopmentLanguage`): обязательное свойство, без него приложение не
675
+ создаётся. И это `ЯзыкиЛокализации` (`LocalizationLanguages`), когда задан
676
+ `ЯзыкПоУмолчанию` (`DefaultLanguage`): такую пару сервер отклоняет, не называя
677
+ причины. Недостающий ключ останавливает пробник до сборки и загрузки, а ошибка
678
+ называет каждый такой ключ вместе со строкой, которую нужно добавить, в
679
+ написании самого манифеста. `ELEMENT_APP_ID` и `ELEMENT_PROJECT_ID`
672
680
  намеренно не используются: у пробника не должно быть возможности достать
673
681
  рабочее приложение, а проект-цель выбирает сама платформа по поставщику и
674
682
  имени манифеста (п. 6.8). Версия сборки по умолчанию - `{база}-probe-{токен}`.
@@ -21,6 +21,15 @@ entry either – say what the behaviour was, not which id or response field was
21
21
  The link is written by `python scripts/changelog-link.py <number>`, which rebuilds the generated
22
22
  pages of the site in the same run – writing it by hand is how the mirrors get left behind.
23
23
 
24
+ ## 2026-09-24 – 0.44.0
25
+
26
+ ### Added
27
+ - **`probe` checks the manifest before it builds anything.** A probe project without
28
+ `Представление` was refused by the console with a bare 500, one without `ЯзыкРазработки`
29
+ never got an application, and a `ЯзыкПоУмолчанию` without `ЯзыкиЛокализации` was refused
30
+ with no cause named. Now the probe stops before the build and names each missing key with
31
+ the line to add. ([#41](https://github.com/keyfire/elemctl/pull/41))
32
+
24
33
  ## 2026-09-23 – 0.43.2
25
34
 
26
35
  ### Changed
@@ -21,6 +21,15 @@ sidebar:
21
21
  Ссылку пишет `python scripts/changelog-link.py <номер>` – он же тут же пересобирает
22
22
  сгенерированные страницы сайта: правка руками как раз и оставляет зеркала позади.
23
23
 
24
+ ## 24.09.2026 – 0.44.0
25
+
26
+ ### Добавлено
27
+ - **`probe` проверяет манифест до сборки.** Проект пробника без `Представление` консоль
28
+ отклоняла ответом 500 без объяснения, без `ЯзыкРазработки` приложение не создавалось, а
29
+ `ЯзыкПоУмолчанию` без `ЯзыкиЛокализации` сервер отклонял, не называя причины. Теперь
30
+ пробник останавливается до сборки и называет каждый недостающий ключ вместе со строкой,
31
+ которую нужно добавить. ([#41](https://github.com/keyfire/elemctl/pull/41))
32
+
24
33
  ## 23.09.2026 – 0.43.2
25
34
 
26
35
  ### Изменено
@@ -18,7 +18,7 @@ from .errors import (
18
18
  TransportError,
19
19
  )
20
20
 
21
- __version__ = "0.43.2"
21
+ __version__ = "0.44.0"
22
22
 
23
23
  __all__ = [
24
24
  "ApiError",
@@ -933,6 +933,33 @@ MESSAGES = {
933
933
  "ru": "уборка: {problem}",
934
934
  "en": "cleanup: {problem}",
935
935
  },
936
+ "probe.manifest-incomplete": {
937
+ "ru": "{file}: сервер не примет этот пробник, поэтому он остановлен до сборки. {problems}",
938
+ "en": "{file}: the server will not take this probe, so it stopped before the build. "
939
+ "{problems}",
940
+ },
941
+ "probe.manifest-no-presentation": {
942
+ "ru": "Нет Представление (Presentation): без него консоль отвечает на загрузку сборки "
943
+ "500 \"Internal exception\" и причины не называет. Нужна строка \"{example}\".",
944
+ "en": "There is no Представление (Presentation): without it the console answers the "
945
+ "upload with a 500 \"Internal exception\" and names no cause. The line "
946
+ "\"{example}\" is needed.",
947
+ },
948
+ "probe.manifest-no-development-language": {
949
+ "ru": "Нет ЯзыкРазработки (DevelopmentLanguage): это обязательное свойство проекта, и без "
950
+ "него приложение не создаётся. Нужна строка \"{example}\".",
951
+ "en": "There is no ЯзыкРазработки (DevelopmentLanguage): it is a required property of "
952
+ "the project, and no application is created without it. The line \"{example}\" "
953
+ "is needed.",
954
+ },
955
+ "probe.manifest-no-localization-languages": {
956
+ "ru": "ЯзыкПоУмолчанию (DefaultLanguage) задан без ЯзыкиЛокализации "
957
+ "(LocalizationLanguages): такой проект сервер отклоняет, не называя причины. "
958
+ "Нужна строка \"{example}\".",
959
+ "en": "ЯзыкПоУмолчанию (DefaultLanguage) is set without ЯзыкиЛокализации "
960
+ "(LocalizationLanguages): the server refuses such a project and names no cause. "
961
+ "The line \"{example}\" is needed.",
962
+ },
936
963
  # -- help: argparse help texts (cli.py) ---------------------------------------
937
964
  # CLI help strings. Key: cli.help.<command> or cli.help.<command>-<flag>.
938
965
  # Metavars that already read as English (NAME/APP_ID/FILE) are left untranslated.
@@ -713,7 +713,9 @@ def create_server(config=None, *, overrides=None, env_file=None):
713
713
  окружения намеренно не используются. Итог - поле ok; ошибки в errors
714
714
  (файл, строка, колонка, окружение, текст), исходные сообщения платформы -
715
715
  в messages, итог уборки - в cleanup. keep=true оставляет приложение и
716
- сборку на стенде для разбора руками.
716
+ сборку на стенде для разбора руками. До сборки проверяется Проект.yaml: без
717
+ Представление, без ЯзыкРазработки или с ЯзыкПоУмолчанию без ЯзыкиЛокализации
718
+ сервер пробник не примет, и инструмент сразу называет недостающий ключ.
717
719
  """
718
720
  lines: list[str] = []
719
721
  report = probe_project(
@@ -28,7 +28,13 @@ import uuid
28
28
  from dataclasses import dataclass, field
29
29
 
30
30
  from . import i18n
31
- from .build import build_assembly, find_project_dir, read_project_meta
31
+ from .build import (
32
+ build_assembly,
33
+ descriptor_value,
34
+ find_project_dir,
35
+ parse_flat_yaml,
36
+ read_project_meta,
37
+ )
32
38
  from .client import extract_assembly_id, extract_project_id
33
39
  from .errors import ApiError, ElemctlError
34
40
 
@@ -183,6 +189,52 @@ def compatibility_refusal(messages):
183
189
  return ""
184
190
 
185
191
 
192
+ def manifest_problems(project_file):
193
+ """What the manifest lacks for the server to take a probe; [] when nothing.
194
+
195
+ A hand-written probe project tends to carry only the name, the vendor and the version,
196
+ and the server answers each missing key in its own unhelpful way. Without
197
+ `Представление` the console refuses the upload with a bare 500 and names the field only
198
+ in its own event log. `ЯзыкРазработки` is a required property of the project
199
+ descriptor: without it no application is created, so a probe can never come out ok. A
200
+ `ЯзыкПоУмолчанию` without `ЯзыкиЛокализации` is refused with no word about the cause at
201
+ all. Every key is read in both spellings, and the example of a fix is written in the
202
+ spelling the manifest itself uses; the project's own name stands in for a presentation.
203
+ """
204
+ values = parse_flat_yaml(project_file.read_text(encoding="utf-8-sig"))
205
+ english = "Name" in values and "Имя" not in values
206
+
207
+ def spelled(russian, english_key):
208
+ return english_key if english else russian
209
+
210
+ language = spelled("Русский", "English")
211
+ problems = []
212
+ if not descriptor_value(values, "Представление", "Presentation"):
213
+ name = descriptor_value(values, "Имя", "Name")
214
+ problems.append(i18n.t(
215
+ "probe.manifest-no-presentation",
216
+ example=f"{spelled('Представление', 'Presentation')}: {name}",
217
+ ))
218
+ if not descriptor_value(values, "ЯзыкРазработки", "DevelopmentLanguage"):
219
+ problems.append(i18n.t(
220
+ "probe.manifest-no-development-language",
221
+ example=f"{spelled('ЯзыкРазработки', 'DevelopmentLanguage')}: {language}",
222
+ ))
223
+ # The list may be written inline or as a nested block; a block leaves the value of the
224
+ # key itself empty, so the key counts by its presence, and only an empty inline list
225
+ # counts as no list.
226
+ languages = [
227
+ values[key] for key in ("ЯзыкиЛокализации", "LocalizationLanguages") if key in values
228
+ ]
229
+ no_list = all(value.replace(" ", "") == "[]" for value in languages)
230
+ if descriptor_value(values, "ЯзыкПоУмолчанию", "DefaultLanguage") and no_list:
231
+ problems.append(i18n.t(
232
+ "probe.manifest-no-localization-languages",
233
+ example=f"{spelled('ЯзыкиЛокализации', 'LocalizationLanguages')}: [{language}]",
234
+ ))
235
+ return problems
236
+
237
+
186
238
  def _all_lines(messages):
187
239
  """Every non-empty line of the messages, in order."""
188
240
  return [
@@ -219,6 +271,13 @@ def probe_project(
219
271
  token = uuid.uuid4().hex[:8]
220
272
 
221
273
  meta = read_project_meta(find_project_dir(project_dir) if project_dir else find_project_dir())
274
+ # Checked before anything is built or uploaded: each of these ends the run on the
275
+ # server anyway, and the server's own answer does not say which key was missing.
276
+ problems = manifest_problems(meta.project_file)
277
+ if problems:
278
+ raise ElemctlError(i18n.t(
279
+ "probe.manifest-incomplete", file=meta.project_file, problems=" ".join(problems)
280
+ ))
222
281
  report = ProbeReport(
223
282
  project_dir=str(meta.project_dir),
224
283
  vendor=meta.vendor,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elemctl
3
- Version: 0.43.2
3
+ Version: 0.44.0
4
4
  Summary: CLI, MCP server and library for the Console API v2 of the 1C:Enterprise.Element platform (1cmycloud): applications, builds from source, one-command deploy with verification, a compile probe
5
5
  Author: KeyFire
6
6
  License-Expression: MIT
@@ -198,6 +198,9 @@ def test_no_russian_string_literals_outside_the_catalog():
198
198
  "Реквизиты", "Измерения", "Ид", "Тип", "Длина", "МаксимальнаяДлина",
199
199
  # The primitive type names the guard canonicalizes across the spellings.
200
200
  "Строка", "Число", "Булево", "Дата", "ДатаВремя", "Время",
201
+ # The language keys of a descriptor and the value of the language enumeration:
202
+ # the probe checks the keys and quotes them in the line it asks to add.
203
+ "ЯзыкРазработки", "ЯзыкПоУмолчанию", "ЯзыкиЛокализации", "Русский",
201
204
  }
202
205
 
203
206
  # Constants that RECOGNIZE the platform's own wording rather than produce ours:
@@ -5,7 +5,20 @@ from __future__ import annotations
5
5
  import pytest
6
6
 
7
7
  from elemctl.errors import ApiError, ElemctlError
8
- from elemctl.probe import PROBE_PREFIX, parse_compilation_errors, probe_project
8
+ from elemctl.probe import PROBE_PREFIX, manifest_problems, parse_compilation_errors, probe_project
9
+
10
+
11
+ @pytest.fixture
12
+ def project_factory(project_factory):
13
+ """A probe project carries the manifest keys the server asks for, unless a test drops one."""
14
+
15
+ def make(*args, **kwargs):
16
+ kwargs.setdefault("presentation", "Пробник")
17
+ kwargs.setdefault("language", "Русский")
18
+ return project_factory(*args, **kwargs)
19
+
20
+ return make
21
+
9
22
 
10
23
  # A task message exactly as the platform gives it: the first line carries the
11
24
  # platform's own prefix, the rest are bare.
@@ -403,3 +416,89 @@ def test_the_english_wording_of_the_refusal_is_recognized(project_factory, tmp_p
403
416
  report = probe_project(client, project_dir=project_factory(), output_dir=tmp_path / "dist")
404
417
 
405
418
  assert report.compatibility_refused == "99.0"
419
+
420
+
421
+ def _append(project_dir, text):
422
+ descriptor = project_dir / "Проект.yaml"
423
+ descriptor.write_text(descriptor.read_text(encoding="utf-8") + text, encoding="utf-8")
424
+
425
+
426
+ def test_a_manifest_without_a_presentation_stops_the_probe_before_the_build(
427
+ project_factory, tmp_path
428
+ ):
429
+ """Without the presentation the console refused the upload with a bare 500.
430
+
431
+ The field was named only in the event log of the console, so the probe names it before
432
+ it builds or uploads anything.
433
+ """
434
+ client = FakeProbeClient()
435
+
436
+ with pytest.raises(ElemctlError) as caught:
437
+ probe_project(
438
+ client, project_dir=project_factory(presentation=""), output_dir=tmp_path / "dist"
439
+ )
440
+
441
+ assert "Представление (Presentation)" in str(caught.value)
442
+ assert '"Представление: crm"' in str(caught.value)
443
+ assert client.calls == []
444
+ assert not (tmp_path / "dist").exists()
445
+
446
+
447
+ def test_a_manifest_without_a_development_language_stops_the_probe(project_factory, tmp_path):
448
+ """The application is never created without it, so the probe could never come out ok."""
449
+ client = FakeProbeClient()
450
+
451
+ with pytest.raises(ElemctlError) as caught:
452
+ probe_project(
453
+ client, project_dir=project_factory(language=""), output_dir=tmp_path / "dist"
454
+ )
455
+
456
+ assert "ЯзыкРазработки (DevelopmentLanguage)" in str(caught.value)
457
+ assert '"ЯзыкРазработки: Русский"' in str(caught.value)
458
+ assert client.calls == []
459
+
460
+
461
+ def test_every_missing_key_is_named_at_once(project_factory):
462
+ problems = manifest_problems(project_factory(presentation="", language="") / "Проект.yaml")
463
+
464
+ assert len(problems) == 2
465
+
466
+
467
+ def test_a_default_language_needs_the_list_of_localization_languages(project_factory):
468
+ """The server refused such a project and pointed at nothing.
469
+
470
+ The list may be written inline or as a nested block.
471
+ """
472
+ project_dir = project_factory()
473
+ _append(project_dir, "ЯзыкПоУмолчанию: Русский\n")
474
+ descriptor = project_dir / "Проект.yaml"
475
+
476
+ problems = manifest_problems(descriptor)
477
+ assert len(problems) == 1 and "ЯзыкиЛокализации (LocalizationLanguages)" in problems[0]
478
+
479
+ _append(project_dir, "ЯзыкиЛокализации: []\n")
480
+ assert len(manifest_problems(descriptor)) == 1
481
+
482
+ block = descriptor.read_text(encoding="utf-8").replace(
483
+ "ЯзыкиЛокализации: []\n", "ЯзыкиЛокализации:\n - Русский\n"
484
+ )
485
+ descriptor.write_text(block, encoding="utf-8")
486
+ assert manifest_problems(descriptor) == []
487
+
488
+
489
+ def test_an_english_manifest_is_answered_in_its_own_spelling(tmp_path):
490
+ project_dir = tmp_path / "repo" / "acme" / "crm"
491
+ project_dir.mkdir(parents=True)
492
+ descriptor = project_dir / "Project.yaml"
493
+ descriptor.write_text("Name: crm\nVendor: acme\nVersion: 1.0\n", encoding="utf-8")
494
+
495
+ problems = manifest_problems(descriptor)
496
+
497
+ assert '"Presentation: crm"' in problems[0]
498
+ assert '"DevelopmentLanguage: English"' in problems[1]
499
+
500
+ descriptor.write_text(
501
+ "Name: crm\nVendor: acme\nVersion: 1.0\nPresentation: CRM\nDevelopmentLanguage: English\n",
502
+ encoding="utf-8",
503
+ )
504
+ assert manifest_problems(descriptor) == []
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes