systemlink-cli 1.5.0__tar.gz → 1.5.1__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 (75) hide show
  1. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/PKG-INFO +1 -1
  2. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/pyproject.toml +1 -1
  3. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/_version.py +1 -1
  4. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skills/slcli/SKILL.md +3 -2
  5. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/webapp_click.py +120 -18
  6. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/LICENSE +0 -0
  7. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/dff-editor/editor.js +0 -0
  8. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/dff-editor/index.html +0 -0
  9. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/__init__.py +0 -0
  10. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/__main__.py +0 -0
  11. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/asset_click.py +0 -0
  12. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/cli_formatters.py +0 -0
  13. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/cli_utils.py +0 -0
  14. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/comment_click.py +0 -0
  15. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/completion_click.py +0 -0
  16. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/config.py +0 -0
  17. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/config_click.py +0 -0
  18. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/dff_click.py +0 -0
  19. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/dff_decorators.py +0 -0
  20. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/example_click.py +0 -0
  21. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/example_loader.py +0 -0
  22. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/example_provisioner.py +0 -0
  23. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/README.md +0 -0
  24. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/_schema/schema-v1.0.json +0 -0
  25. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/demo-complete-workflow/README.md +0 -0
  26. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
  27. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/demo-test-plans/README.md +0 -0
  28. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/demo-test-plans/config.yaml +0 -0
  29. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
  30. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
  31. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
  32. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
  33. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
  34. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
  35. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
  36. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
  37. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
  38. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  39. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/feed_click.py +0 -0
  40. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/file_click.py +0 -0
  41. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/function_click.py +0 -0
  42. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/function_templates.py +0 -0
  43. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/main.py +0 -0
  44. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/mcp_click.py +0 -0
  45. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/mcp_server.py +0 -0
  46. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/notebook_click.py +0 -0
  47. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/platform.py +0 -0
  48. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/policy_click.py +0 -0
  49. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/policy_utils.py +0 -0
  50. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/profiles.py +0 -0
  51. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/response_handlers.py +0 -0
  52. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/routine_click.py +0 -0
  53. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skill_click.py +0 -0
  54. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
  55. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skills/slcli/references/filtering.md +0 -0
  56. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skills/systemlink-webapp/SKILL.md +0 -0
  57. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
  58. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skills/systemlink-webapp/references/layout-patterns.md +0 -0
  59. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
  60. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
  61. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/ssl_trust.py +0 -0
  62. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/system_click.py +0 -0
  63. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/table_utils.py +0 -0
  64. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/tag_click.py +0 -0
  65. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/templates_click.py +0 -0
  66. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/testmonitor_click.py +0 -0
  67. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/universal_handlers.py +0 -0
  68. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/user_click.py +0 -0
  69. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/utils.py +0 -0
  70. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/web_editor.py +0 -0
  71. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/workflow_preview.py +0 -0
  72. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/workflows_click.py +0 -0
  73. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/workitem_click.py +0 -0
  74. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/workspace_click.py +0 -0
  75. {systemlink_cli-1.5.0 → systemlink_cli-1.5.1}/slcli/workspace_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: systemlink-cli
3
- Version: 1.5.0
3
+ Version: 1.5.1
4
4
  Summary: SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates.
5
5
  License-File: LICENSE
6
6
  Author: Fred Visser
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "systemlink-cli"
3
- version = "1.5.0"
3
+ version = "1.5.1"
4
4
  description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
5
5
  authors = ["Fred Visser <fred.visser@emerson.com>"]
6
6
  packages = [{ include = "slcli" }]
@@ -1,4 +1,4 @@
1
1
  """Version information for slcli."""
2
2
 
3
3
  # This file is auto-generated. Do not edit manually.
4
- __version__ = "1.5.0"
4
+ __version__ = "1.5.1"
@@ -682,8 +682,9 @@ conventions described by the `systemlink-webapp` skill.
682
682
 
683
683
  `webapp manifest init` writes `manifest.json` and `nipkg.config.json` using the Plugin Manager
684
684
  field names (`section`, `maintainer`, `homepage`, `xbPlugin`, `slPluginManagerTags`,
685
- `slPluginManagerMinServerVersion`). `webapp pack --config ...` consumes that metadata and writes
686
- the matching control-file fields into the generated `.nipkg`.
685
+ `slPluginManagerMinServerVersion`, `iconFile`). `webapp pack --config ...` consumes that
686
+ metadata, carries the icon into the package, and writes the matching control-file fields into the
687
+ generated `.nipkg`.
687
688
 
688
689
  ### skill — AI skill installation
689
690
 
@@ -6,6 +6,7 @@ management (list, get, delete, publish, open).
6
6
 
7
7
  import io
8
8
  import re
9
+ import shutil
9
10
  import sys
10
11
  import tarfile
11
12
  import tempfile
@@ -40,6 +41,7 @@ _PACKAGE_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]*$")
40
41
  _VERSION_PATTERN = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$")
41
42
  _MAINTAINER_PATTERN = re.compile(r"^[^<>]+\s<[^<>@\s]+@[^<>@\s]+>$")
42
43
  _XB_PLUGIN_VALUES = ("webapp", "notebook", "dashboard", "routine", "bundle")
44
+ _ALLOWED_ICON_EXTENSIONS = frozenset({".svg", ".png", ".jpg", ".jpeg", ".webp", ".gif", ".ico"})
43
45
  _MAX_PACKAGE_LENGTH = 100
44
46
  _MAX_DISPLAY_NAME_LENGTH = 200
45
47
  _MAX_DESCRIPTION_LENGTH = 5000
@@ -179,6 +181,62 @@ def _default_angular_build_dir(directory: Path) -> str:
179
181
  return f"dist/{_default_angular_project_name(directory)}/browser"
180
182
 
181
183
 
184
+ def _resolve_local_path(path_value: str, base_dir: Optional[Path] = None) -> Path:
185
+ """Resolve a local path against a base directory or the current working directory."""
186
+ path = Path(path_value).expanduser()
187
+ if not path.is_absolute():
188
+ anchor = base_dir if base_dir is not None else Path.cwd()
189
+ path = anchor / path
190
+ return path.resolve()
191
+
192
+
193
+ def _validate_icon_file_value(icon_file: str, base_dir: Optional[Path] = None) -> Optional[str]:
194
+ """Validate the icon asset referenced by Plugin Manager metadata."""
195
+ if not icon_file:
196
+ return "iconFile is required"
197
+
198
+ resolved_icon = _resolve_local_path(icon_file, base_dir)
199
+ if not resolved_icon.exists() or not resolved_icon.is_file():
200
+ return f"iconFile does not exist or is not a file: {icon_file}"
201
+ if resolved_icon.suffix.lower() not in _ALLOWED_ICON_EXTENSIONS:
202
+ allowed_extensions = ", ".join(sorted(ext.lstrip(".") for ext in _ALLOWED_ICON_EXTENSIONS))
203
+ return f"iconFile must be one of: {allowed_extensions}"
204
+ return None
205
+
206
+
207
+ def _prepare_icon_file_for_directory(
208
+ icon_file: str, directory: Path, force: bool
209
+ ) -> tuple[Path, str]:
210
+ """Validate an icon asset and return its source path plus stored manifest filename."""
211
+ icon_error = _validate_icon_file_value(icon_file)
212
+ if icon_error:
213
+ click.echo(f"✗ {icon_error}", err=True)
214
+ sys.exit(ExitCodes.INVALID_INPUT)
215
+
216
+ source_icon = _resolve_local_path(icon_file)
217
+ target_icon = directory / source_icon.name
218
+
219
+ if target_icon.exists() and target_icon.resolve() != source_icon:
220
+ if not force:
221
+ click.echo(
222
+ f"✗ {target_icon.name} already exists in {directory}. Use --force to overwrite it.",
223
+ err=True,
224
+ )
225
+ sys.exit(ExitCodes.INVALID_INPUT)
226
+
227
+ return source_icon, target_icon.name
228
+
229
+
230
+ def _copy_icon_file_to_directory(source_icon: Path, directory: Path) -> bool:
231
+ """Copy an icon asset into the manifest directory when it is not already there."""
232
+ target_icon = directory / source_icon.name
233
+ if target_icon.resolve() == source_icon:
234
+ return False
235
+
236
+ shutil.copy2(source_icon, target_icon)
237
+ return True
238
+
239
+
182
240
  def _normalize_plugin_manager_metadata(raw_metadata: Dict[str, Any]) -> Dict[str, Any]:
183
241
  """Normalize legacy App Store keys to Plugin Manager keys."""
184
242
  metadata = dict(raw_metadata)
@@ -191,7 +249,9 @@ def _normalize_plugin_manager_metadata(raw_metadata: Dict[str, Any]) -> Dict[str
191
249
 
192
250
 
193
251
  def _validate_plugin_manager_metadata(
194
- raw_metadata: Dict[str, Any], require_build_dir: bool = False
252
+ raw_metadata: Dict[str, Any],
253
+ require_build_dir: bool = False,
254
+ base_dir: Optional[Path] = None,
195
255
  ) -> Dict[str, str]:
196
256
  """Validate and normalize Plugin Manager manifest metadata."""
197
257
  from urllib.parse import urlparse
@@ -250,6 +310,9 @@ def _validate_plugin_manager_metadata(
250
310
  errors.append("nipkgFile must end with .nipkg")
251
311
  if require_build_dir and not build_dir:
252
312
  errors.append("buildDir is required when packing from config without a folder argument")
313
+ icon_error = _validate_icon_file_value(icon_file, base_dir)
314
+ if icon_error:
315
+ errors.append(icon_error)
253
316
 
254
317
  if errors:
255
318
  click.echo("✗ Invalid plugin manager metadata:", err=True)
@@ -278,14 +341,16 @@ def _validate_plugin_manager_metadata(
278
341
  validated["buildDir"] = build_dir
279
342
  if build_command:
280
343
  validated["buildCommand"] = build_command
281
- if icon_file:
282
- validated["iconFile"] = icon_file
344
+ validated["iconFile"] = icon_file
283
345
 
284
346
  return validated
285
347
 
286
348
 
287
349
  def _pack_folder_to_nipkg(
288
- folder: Path, output: Optional[Path] = None, metadata: Optional[Dict[str, str]] = None
350
+ folder: Path,
351
+ output: Optional[Path] = None,
352
+ metadata: Optional[Dict[str, str]] = None,
353
+ icon_source: Optional[Path] = None,
289
354
  ) -> Path:
290
355
  """Pack a folder into a .nipkg (ar) file and return the output path.
291
356
 
@@ -351,6 +416,8 @@ def _pack_folder_to_nipkg(
351
416
  control_fields["XB-SlPluginManagerMinServerVersion"] = metadata[
352
417
  "slPluginManagerMinServerVersion"
353
418
  ]
419
+ if metadata.get("iconFile"):
420
+ control_fields["XB-SlPluginManagerIcon"] = Path(metadata["iconFile"]).name
354
421
  else:
355
422
  control_fields = {
356
423
  "Package": package_name,
@@ -374,10 +441,21 @@ def _pack_folder_to_nipkg(
374
441
 
375
442
  # Create data.tar.gz in-memory containing the folder contents at the root
376
443
  data_buf = io.BytesIO()
377
- with tarfile.open(fileobj=data_buf, mode="w:gz") as dtf:
378
- # tarfile.add will handle directories and files; preserve relative paths
379
- dtf.add(str(folder), arcname=".")
380
- data_bytes = data_buf.getvalue()
444
+ with tempfile.TemporaryDirectory() as temp_dir:
445
+ payload_folder = folder
446
+ if metadata is not None and icon_source is not None:
447
+ icon_name = Path(metadata["iconFile"]).name
448
+ folder_icon = folder.resolve() / icon_name
449
+ resolved_icon_source = icon_source.resolve()
450
+ if not folder_icon.exists() or folder_icon.resolve() != resolved_icon_source:
451
+ payload_folder = Path(temp_dir) / folder.name
452
+ shutil.copytree(folder, payload_folder)
453
+ shutil.copy2(resolved_icon_source, payload_folder / icon_name)
454
+
455
+ with tarfile.open(fileobj=data_buf, mode="w:gz") as dtf:
456
+ # tarfile.add will handle directories and files; preserve relative paths
457
+ dtf.add(str(payload_folder), arcname=".")
458
+ data_bytes = data_buf.getvalue()
381
459
 
382
460
  # debian-binary content
383
461
  debian_bin = b"2.0\n"
@@ -467,6 +545,7 @@ def _build_webapp_manifest_and_config(
467
545
  build_dir: str,
468
546
  build_command: str,
469
547
  icon_file: str,
548
+ icon_validation_base_dir: Path,
470
549
  ) -> tuple[Dict[str, str], Dict[str, str]]:
471
550
  """Build validated submission manifest and nipkg config payloads."""
472
551
  manifest = _validate_plugin_manager_metadata(
@@ -483,15 +562,15 @@ def _build_webapp_manifest_and_config(
483
562
  "slPluginManagerTags": tags,
484
563
  "slPluginManagerMinServerVersion": min_server_version,
485
564
  "nipkgFile": _default_nipkg_filename(package_name, version),
486
- }
565
+ "iconFile": icon_file,
566
+ },
567
+ base_dir=icon_validation_base_dir,
487
568
  )
488
569
 
489
570
  pack_config = dict(manifest)
490
571
  pack_config.pop("nipkgFile", None)
491
572
  pack_config["buildDir"] = build_dir
492
573
  pack_config["buildCommand"] = build_command
493
- if icon_file:
494
- pack_config["iconFile"] = icon_file
495
574
 
496
575
  return manifest, pack_config
497
576
 
@@ -622,7 +701,8 @@ slcli webapp manifest init . \\
622
701
  --description "A dashboard for monitoring fleet health and calibration status." \\
623
702
  --section Dashboard \\
624
703
  --maintainer "Your Name <you@example.com>" \\
625
- --license MIT
704
+ --license MIT \\
705
+ --icon-file ./icon.svg
626
706
 
627
707
  slcli webapp pack --config nipkg.config.json
628
708
  ```
@@ -730,7 +810,11 @@ def register_webapp_commands(cli: Any) -> None:
730
810
  show_default=True,
731
811
  help="Build command written to nipkg.config.json",
732
812
  )
733
- @click.option("--icon-file", default="", help="Icon path written to nipkg.config.json")
813
+ @click.option(
814
+ "--icon-file",
815
+ required=True,
816
+ help="Path to the icon asset; copied into the manifest directory as iconFile",
817
+ )
734
818
  @click.option("--force", is_flag=True, help="Overwrite existing manifest files")
735
819
  def init_manifest(
736
820
  directory: Path,
@@ -772,6 +856,10 @@ def register_webapp_commands(cli: Any) -> None:
772
856
  )
773
857
  sys.exit(ExitCodes.INVALID_INPUT)
774
858
 
859
+ source_icon, manifest_icon_file = _prepare_icon_file_for_directory(
860
+ icon_file, directory, force
861
+ )
862
+
775
863
  manifest, pack_config = _build_webapp_manifest_and_config(
776
864
  package_name=package_name,
777
865
  version=version,
@@ -786,11 +874,19 @@ def register_webapp_commands(cli: Any) -> None:
786
874
  min_server_version=min_server_version,
787
875
  build_dir=build_dir,
788
876
  build_command=build_command,
789
- icon_file=icon_file,
877
+ icon_file=manifest_icon_file,
878
+ icon_validation_base_dir=source_icon.parent,
790
879
  )
791
880
 
792
- save_json_file(manifest, str(manifest_path))
793
- save_json_file(pack_config, str(config_path))
881
+ copied_icon = _copy_icon_file_to_directory(source_icon, directory)
882
+ try:
883
+ save_json_file(manifest, str(manifest_path))
884
+ save_json_file(pack_config, str(config_path))
885
+ except Exception:
886
+ if copied_icon:
887
+ (directory / manifest_icon_file).unlink(missing_ok=True)
888
+ raise
889
+
794
890
  format_success(
795
891
  "Created Plugin Manager manifest files",
796
892
  {
@@ -841,7 +937,9 @@ def register_webapp_commands(cli: Any) -> None:
841
937
  click.echo("✗ Config file must contain a JSON object.", err=True)
842
938
  sys.exit(ExitCodes.INVALID_INPUT)
843
939
  metadata = _validate_plugin_manager_metadata(
844
- raw_data, require_build_dir=resolved_folder is None
940
+ raw_data,
941
+ require_build_dir=resolved_folder is None,
942
+ base_dir=config_path.parent,
845
943
  )
846
944
 
847
945
  if resolved_folder is None:
@@ -866,7 +964,11 @@ def register_webapp_commands(cli: Any) -> None:
866
964
  sys.exit(ExitCodes.INVALID_INPUT)
867
965
 
868
966
  out = Path(output) if output else None
869
- result = _pack_folder_to_nipkg(resolved_folder, out, metadata)
967
+ icon_source: Optional[Path] = None
968
+ if metadata is not None and config_path is not None:
969
+ icon_source = _resolve_local_path(metadata["iconFile"], config_path.parent)
970
+
971
+ result = _pack_folder_to_nipkg(resolved_folder, out, metadata, icon_source)
870
972
  format_success("Packed folder", {"Path": str(result)})
871
973
  except SystemExit:
872
974
  raise
File without changes