ScriptCollection 4.2.57__py3-none-any.whl → 4.2.59__py3-none-any.whl

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.
@@ -900,7 +900,7 @@ def AddImageToCustomRegistry()->int:
900
900
  sc:ScriptCollectionCore=ScriptCollectionCore()
901
901
  verbosity=int(args.verbosity)
902
902
  sc.log.loglevel=LogLevel(verbosity)
903
- sc.add_image_to_custom_docker_image_registry(args.remotehub,args.imagenameonremotehub,args.ownregistryaddress,args.imagenameonownregistry,args.tag,args.username,args.password,args.removeimagelocally)
903
+ sc.add_image_to_custom_docker_image_registry(args.remotehub,args.imagenameonremotehub,args.ownregistryaddress,args.imagenameonownregistry,args.tag,args.username,args.password)
904
904
  return 0
905
905
 
906
906
  def SyncXlfFiles()->int:
@@ -35,6 +35,12 @@ class VersionEcholon(Enum):
35
35
  LatestPatchOrLatestMinorOrNextMajor = 2
36
36
  LatestVersion = 3
37
37
 
38
+ class Platform(Enum):
39
+ WindowsAMD64 = 0
40
+ LinuxAMD64 = 1
41
+ LinuxARM64 = 2
42
+ MacOSARM64 = 3
43
+
38
44
 
39
45
  class Dependency:
40
46
  dependencyname:str
@@ -1429,4 +1435,102 @@ class GeneralUtilities:
1429
1435
  result= GeneralUtilities.escape_json_property_value(str_value)
1430
1436
  #do other adaptions here if desired/required
1431
1437
  return result
1432
-
1438
+
1439
+
1440
+ @staticmethod
1441
+ @check_arguments
1442
+ def get_current_platform() -> Platform:
1443
+ system = platform.system().lower()
1444
+ machine = platform.machine().lower()
1445
+
1446
+ if system == "windows" and machine in ("x86_64", "amd64"):
1447
+ return Platform.WindowsAMD64
1448
+ elif system == "linux" and machine in ("x86_64", "amd64"):
1449
+ return Platform.LinuxAMD64
1450
+ elif system == "linux" and machine in ("arm64", "aarch64"):
1451
+ return Platform.LinuxARM64
1452
+ elif system == "darwin" and machine in ("x86_64", "amd64"):
1453
+ return Platform.MacOSARM64
1454
+ else:
1455
+ raise ValueError(f"Unsupported platform: {system}/{machine}")
1456
+
1457
+ @staticmethod
1458
+ @check_arguments
1459
+ def platform_to_short_str(platform_value: Platform) -> str:
1460
+ mapping = {
1461
+ Platform.WindowsAMD64: "win-x64",
1462
+ Platform.LinuxAMD64: "linux-x64",
1463
+ Platform.LinuxARM64: "linux-arm64",
1464
+ Platform.MacOSARM64: "osx-arm64",
1465
+ }
1466
+ return mapping[platform_value]
1467
+
1468
+ @staticmethod
1469
+ @check_arguments
1470
+ def platform_from_short_str(platform_str: str) -> Platform:
1471
+ mapping = {
1472
+ "win-x64": Platform.WindowsAMD64,
1473
+ "linux-x64": Platform.LinuxAMD64,
1474
+ "linux-arm64": Platform.LinuxARM64,
1475
+ "osx-arm64": Platform.MacOSARM64,
1476
+ }
1477
+ if platform_str not in mapping:
1478
+ raise ValueError(f"Unsupported platform string: {platform_str}")
1479
+ return mapping[platform_str]
1480
+
1481
+ @staticmethod
1482
+ @check_arguments
1483
+ def platform_to_dash_str(platform_value: Platform) -> str:
1484
+ mapping = {
1485
+ Platform.WindowsAMD64: "Windows-x64",
1486
+ Platform.LinuxAMD64: "Linux-x64",
1487
+ Platform.LinuxARM64: "Linux-arm64",
1488
+ Platform.MacOSARM64: "MacOS-arm64",
1489
+ }
1490
+ return mapping[platform_value]
1491
+
1492
+ @staticmethod
1493
+ @check_arguments
1494
+ def platform_from_dash_str(platform_str: str) -> Platform:
1495
+ mapping = {
1496
+ "Windows-x64": Platform.WindowsAMD64,
1497
+ "Linux-x64": Platform.LinuxAMD64,
1498
+ "Linux-arm64": Platform.LinuxARM64,
1499
+ "MacOS-arm64": Platform.MacOSARM64,
1500
+ }
1501
+ if platform_str not in mapping:
1502
+ raise ValueError(f"Unsupported platform string: {platform_str}")
1503
+ return mapping[platform_str]
1504
+
1505
+ @staticmethod
1506
+ @check_arguments
1507
+ def platform_to_docker_platform_str(platform_value: Platform) -> str:
1508
+ mapping = {
1509
+ Platform.WindowsAMD64: "windows/amd64",
1510
+ Platform.LinuxAMD64: "linux/amd64",
1511
+ Platform.LinuxARM64: "linux/arm64",
1512
+ Platform.MacOSARM64: "linux/arm64", # macOS → linux container
1513
+ }
1514
+ return mapping[platform_value]
1515
+
1516
+ @staticmethod
1517
+ @check_arguments
1518
+ def platform_to_dotnet_runtime_identifier(platform_value: Platform) -> str:
1519
+ mapping = {
1520
+ Platform.WindowsAMD64: "win-x64",
1521
+ Platform.LinuxAMD64: "linux-x64",
1522
+ Platform.LinuxARM64: "linux-arm64",
1523
+ Platform.MacOSARM64: "osx-arm64",
1524
+ }
1525
+ return mapping[platform_value]
1526
+
1527
+ @staticmethod
1528
+ @check_arguments
1529
+ def get_all_platforms() -> list[Platform]:
1530
+ return [
1531
+ Platform.WindowsAMD64,
1532
+ Platform.LinuxAMD64,
1533
+ Platform.LinuxARM64,
1534
+ Platform.MacOSARM64,
1535
+ ]
1536
+
@@ -31,12 +31,12 @@ import qrcode
31
31
  import pycdlib
32
32
  import send2trash
33
33
  from pypdf import PdfReader, PdfWriter
34
- from .GeneralUtilities import GeneralUtilities
34
+ from .GeneralUtilities import GeneralUtilities,Platform
35
35
  from .ProgramRunnerBase import ProgramRunnerBase
36
36
  from .ProgramRunnerPopen import ProgramRunnerPopen
37
37
  from .SCLog import SCLog, LogLevel
38
38
 
39
- version = "4.2.57"
39
+ version = "4.2.59"
40
40
  __version__ = version
41
41
 
42
42
  class VSCodeWorkspaceShellTask:
@@ -227,17 +227,29 @@ class ScriptCollectionCore:
227
227
  result=image in images
228
228
  return result
229
229
 
230
- @GeneralUtilities.check_arguments
231
- def add_image_to_custom_docker_image_registry(self,remote_hub:str,imagename_on_remote_hub:str,own_registry_address:str,imagename_on_own_registry:str,tag:str,registry_username:str,registry_password:str,remove_locally_in_the_end:bool)->None:
232
- registry_username,registry_password=self.__load_credentials_if_required_and_available(remote_hub,registry_username,registry_password)
233
- source_address=f"{remote_hub}/{imagename_on_remote_hub}:{tag}"
234
- target_address=f"{own_registry_address}/{imagename_on_own_registry}:{tag}"
235
- self.run_program("docker",f"pull {source_address}")
236
- self.run_program("docker",f"tag {source_address} {target_address}")
237
- self.run_program("docker",f"push {target_address}")
238
- if remove_locally_in_the_end:
239
- self.run_program("docker",f"rmi {source_address}")
240
- self.run_program("docker",f"rmi {target_address}")
230
+ def docker_platform_to_slug(self,platform_value: Platform) -> str:
231
+ if platform_value == Platform.LinuxAMD64:
232
+ return "linux-amd64"
233
+ elif platform_value == Platform.LinuxARM64:
234
+ return "linux-arm64"
235
+ raise ValueError(f"Unsupported platform: {platform_value}")
236
+
237
+ @GeneralUtilities.check_arguments
238
+ def add_image_to_custom_docker_image_registry(
239
+ self,
240
+ remote_hub: str,
241
+ imagename_on_remote_hub: str,
242
+ own_registry_address: str,
243
+ imagename_on_own_registry: str,
244
+ tag: str,
245
+ registry_username: str,
246
+ registry_password: str,
247
+ ) -> None:
248
+ registry_username, registry_password = self.__load_credentials_if_required_and_available(remote_hub, registry_username, registry_password)
249
+ source_address = f"{remote_hub}/{imagename_on_remote_hub}:{tag}"
250
+ target_address = f"{own_registry_address}/{imagename_on_own_registry}:{tag}"
251
+ self.run_program("docker", f"buildx imagetools create --tag {target_address} {source_address}")#this does pull and push for each platform
252
+
241
253
 
242
254
  def get_tags_of_images_from_registry(self,registry_base_url:str,image:str,registry_username:str,registry_password:str)->list[str]:
243
255
  """registry_base_url must be in the format 'https://myregistry.example.com'
@@ -2870,6 +2882,7 @@ OCR-content:
2870
2882
  if remove_images:
2871
2883
  self.run_program_with_retry("docker","image prune -a -f",amount_of_attempts=amount_of_attempts)
2872
2884
  self.run_program_with_retry("docker","builder prune -a -f",amount_of_attempts=amount_of_attempts)
2885
+ self.run_program_with_retry("docker","buildx prune -f",amount_of_attempts=amount_of_attempts,throw_exception_if_exitcode_is_not_zero=False) # buildx prune is not available on every machine.
2873
2886
  self.run_program_with_retry("docker","system df",print_live_output=self.log.loglevel==LogLevel.Debug,amount_of_attempts=amount_of_attempts)
2874
2887
 
2875
2888
  @GeneralUtilities.check_arguments
@@ -3123,6 +3136,7 @@ OCR-content:
3123
3136
  #sync existing files
3124
3137
  self.__sync_xlf2_files(base_file_xml, language_files_with_content)
3125
3138
 
3139
+
3126
3140
  @GeneralUtilities.check_arguments
3127
3141
  def translate_xlf_files_in_folder(self, folder: str, base_language: str, libre_translate_api_server: str):
3128
3142
  """Translates all .xlf files directly in the given folder (non-recursive)."""
@@ -3201,9 +3215,11 @@ OCR-content:
3201
3215
  return results[0]["language"]
3202
3216
 
3203
3217
  @GeneralUtilities.check_arguments
3204
- def get_all_files_in_git_repository(self,repository_folder:str,include_submodules: bool = True) -> list[str]:
3205
- """returns all files in a git-repository except ignored files"""
3206
- cmd = ["ls-files", "--cached", "--exclude-standard"]
3218
+ def get_all_files_in_git_repository(self,repository_folder:str,ignore_ignored_files:bool=True,include_submodules: bool = True) -> list[str]:
3219
+ """Returns a list of all files in a git-repository."""
3220
+ cmd = ["ls-files", "--cached"]
3221
+ if ignore_ignored_files:
3222
+ cmd.append("--exclude-standard")
3207
3223
  if include_submodules:
3208
3224
  cmd.append("--recurse-submodules")
3209
3225
  output=self.run_program_argsasarray("git", cmd,repository_folder)
@@ -3211,10 +3227,10 @@ OCR-content:
3211
3227
  return files
3212
3228
 
3213
3229
  @GeneralUtilities.check_arguments
3214
- def write_file_list_for_repository(self,repository_folder:str,target_file:str="./FileList.txt") -> None:
3230
+ def write_file_list_for_repository(self,repository_folder:str,target_file:str="./FileList.txt",ignore_ignored_files:bool=True,include_submodules: bool = True) -> None:
3215
3231
  if os.path.isabs(target_file):
3216
3232
  target_file=GeneralUtilities.resolve_relative_path(target_file,repository_folder)
3217
3233
  target_file=GeneralUtilities.normalize_path(target_file)
3218
- files=self.get_all_files_in_git_repository(repository_folder)
3234
+ files=self.get_all_files_in_git_repository(repository_folder,ignore_ignored_files,include_submodules)
3219
3235
  GeneralUtilities.ensure_file_exists(target_file)
3220
3236
  GeneralUtilities.write_lines_to_file(target_file, files)
@@ -3,7 +3,7 @@ from urllib import request
3
3
  import time
4
4
  import ssl
5
5
  from datetime import timedelta,datetime
6
- from ...GeneralUtilities import GeneralUtilities
6
+ from ...GeneralUtilities import GeneralUtilities, Platform
7
7
  from ...SCLog import LogLevel
8
8
  from ..TFCPS_CodeUnitSpecific_Base import TFCPS_CodeUnitSpecific_Base,TFCPS_CodeUnitSpecific_Base_CLI
9
9
 
@@ -13,32 +13,36 @@ class TFCPS_CodeUnitSpecific_Docker_Functions(TFCPS_CodeUnitSpecific_Base):
13
13
  def __init__(self,current_file:str,verbosity:LogLevel,targetenvironmenttype:str,use_cache:bool,is_pre_merge:bool):
14
14
  super().__init__(current_file, verbosity,targetenvironmenttype,use_cache,is_pre_merge)
15
15
 
16
+
16
17
  @GeneralUtilities.check_arguments
17
- def build(self,custom_arguments:dict[str,str]) -> None:
18
+ def build(self,platforms:list[Platform],custom_arguments:dict[str,str]) -> None:
18
19
  codeunitname: str =self.get_codeunit_name()
19
- codeunit_folder =self.get_codeunit_folder()
20
+ codeunit_folder =self.get_codeunit_folder()
20
21
  codeunitname_lower = codeunitname.lower()
21
22
  codeunit_file =self.get_codeunit_file()
22
23
  codeunitversion = self.tfcps_Tools_General.get_version_of_codeunit(codeunit_file)
23
- args = ["image", "build", "--pull", "--force-rm", "--progress=plain", "--build-arg", f"TargetEnvironmentType={self.get_target_environment_type()}", "--build-arg", f"CodeUnitName={codeunitname}", "--build-arg", f"CodeUnitVersion={codeunitversion}", "--build-arg", f"CodeUnitOwnerName={self.tfcps_Tools_General.get_codeunit_owner_name(self.get_codeunit_file())}", "--build-arg", f"CodeUnitOwnerEMailAddress={self.tfcps_Tools_General.get_codeunit_owner_emailaddress(self.get_codeunit_file())}"]
24
24
  if custom_arguments is None:
25
25
  custom_arguments=dict[str,str]()
26
- for custom_argument_key, custom_argument_value in custom_arguments.items():
27
- args.append("--build-arg")
28
- args.append(f"{custom_argument_key}={custom_argument_value}")
29
- args = args+["--tag", f"{codeunitname_lower}:latest", "--tag", f"{codeunitname_lower}:{codeunitversion}", "--file", f"{codeunitname}/Dockerfile"]
30
- if not self.use_cache():
31
- args.append("--no-cache")
32
- args.append(".")
33
- codeunit_content_folder = os.path.join(codeunit_folder)
34
- GeneralUtilities.retry_action(lambda: self._protected_sc.run_program_argsasarray("docker", args, codeunit_content_folder, print_errors_as_information=True), 3)
35
- artifacts_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts", codeunit_folder)
36
- app_artifacts_folder = os.path.join(artifacts_folder, "BuildResult_OCIImage")
37
- GeneralUtilities.ensure_directory_does_not_exist(app_artifacts_folder)
38
- GeneralUtilities.ensure_directory_exists(app_artifacts_folder)
39
- self._protected_sc.run_program_argsasarray("docker", ["save", "--output", f"{codeunitname}_v{codeunitversion}.tar", f"{codeunitname_lower}:{codeunitversion}"], app_artifacts_folder, print_errors_as_information=True)
40
- self.copy_source_files_to_output_directory()
26
+ for platform in platforms:
27
+ #builder must be created once before with "docker buildx create --use"
28
+ args = ["buildx","build", "--platform",GeneralUtilities.platform_to_docker_platform_str(platform), "--pull", "--force-rm", "--progress=plain", "--build-arg", f"TargetEnvironmentType={self.get_target_environment_type()}", "--build-arg", f"CodeUnitName={codeunitname}", "--build-arg", f"CodeUnitVersion={codeunitversion}", "--build-arg", f"CodeUnitOwnerName={self.tfcps_Tools_General.get_codeunit_owner_name(self.get_codeunit_file())}", "--build-arg", f"CodeUnitOwnerEMailAddress={self.tfcps_Tools_General.get_codeunit_owner_emailaddress(self.get_codeunit_file())}"]
29
+ for custom_argument_key, custom_argument_value in custom_arguments.items():
30
+ args.append("--build-arg")
31
+ args.append(f"{custom_argument_key}={custom_argument_value}")
32
+ args = args+["--tag", f"{codeunitname_lower}:latest", "--tag", f"{codeunitname_lower}:{codeunitversion}", "--file", f"{codeunitname}/Dockerfile"]
33
+ if not self.use_cache():
34
+ args.append("--no-cache")
35
+ args.append(".")
36
+ codeunit_content_folder = os.path.join(codeunit_folder)
37
+ GeneralUtilities.retry_action(lambda a=args, f=codeunit_content_folder: self._protected_sc.run_program_argsasarray("docker", a, f, print_errors_as_information=True), 3)
38
+ artifacts_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts", codeunit_folder)
39
+ app_artifacts_folder = os.path.join(artifacts_folder, "BuildResult_OCIImage")
40
+ GeneralUtilities.ensure_directory_does_not_exist(app_artifacts_folder)
41
+ GeneralUtilities.ensure_directory_exists(app_artifacts_folder)
42
+
43
+ self._protected_sc.run_program_argsasarray("docker", ["save", "--output", f"{codeunitname}_v{codeunitversion}_{GeneralUtilities.platform_to_dash_str(platform)}.tar", f"{codeunitname_lower}:{codeunitversion}"], app_artifacts_folder, print_errors_as_information=True)
41
44
  self.__generate_sbom_for_docker_image()
45
+ self.copy_source_files_to_output_directory()
42
46
 
43
47
 
44
48
  @GeneralUtilities.check_arguments
@@ -93,10 +97,11 @@ class TFCPS_CodeUnitSpecific_Docker_Functions(TFCPS_CodeUnitSpecific_Base):
93
97
  timeout=timedelta(seconds=120)
94
98
  if environment_variables is None:
95
99
  environment_variables={}
100
+ current_platform = GeneralUtilities.get_current_platform()
96
101
  oci_image_artifacts_folder :str= GeneralUtilities.resolve_relative_path("Other/Artifacts/BuildResult_OCIImage", self.get_codeunit_folder())
97
102
  container_name:str=f"{self.get_codeunit_name()}finaltest".lower()
98
103
  self.tfcps_Tools_General.ensure_containers_are_not_running([container_name])
99
- self.tfcps_Tools_General.load_docker_image(oci_image_artifacts_folder)
104
+ self.tfcps_Tools_General.load_docker_image(oci_image_artifacts_folder,current_platform)
100
105
  codeunit_file:str=os.path.join(self.get_codeunit_folder(),f"{self.get_codeunit_name()}.codeunit.xml")
101
106
  image=f"{self.get_codeunit_name()}:{self.tfcps_Tools_General.get_version_of_codeunit(codeunit_file)}".lower()
102
107
  argument=f"run -d --name {container_name}"
@@ -157,7 +157,7 @@ class TFCPS_CodeUnitSpecific_NodeJS_Functions(TFCPS_CodeUnitSpecific_Base):
157
157
  @GeneralUtilities.check_arguments
158
158
  def get_available_cultures_for_angular_app(self)->None:
159
159
  return self._protected_sc.get_available_cultures_for_angular_app(self.get_codeunit_folder()+"/angular.json")
160
-
160
+
161
161
  @GeneralUtilities.check_arguments
162
162
  def __ensure_translations_exist(self,languages:list[str])->None:
163
163
  base_file=os.path.join(self.get_codeunit_folder(),"Other","Resources","Translations",f"messages.xlf")
@@ -166,7 +166,6 @@ class TFCPS_CodeUnitSpecific_NodeJS_Functions(TFCPS_CodeUnitSpecific_Base):
166
166
  if not os.path.isfile(target_file):
167
167
  GeneralUtilities.ensure_file_exists(target_file)
168
168
  GeneralUtilities.write_text_to_file(target_file, GeneralUtilities.read_text_from_file(base_file))
169
-
170
169
  #set new attribute
171
170
  tree = ET.parse(target_file)
172
171
  root = tree.getroot()
@@ -197,12 +196,22 @@ class TFCPS_CodeUnitSpecific_NodeJS_Functions(TFCPS_CodeUnitSpecific_Base):
197
196
  self._protected_sc.sync_xlf2_files("messages",languages,os.path.join(self.get_codeunit_folder(),"Other","Resources","Translations"))
198
197
 
199
198
  @GeneralUtilities.check_arguments
200
- def translate_safe(self)->None:
201
- pass#TODO if ~/.ScriptCollection/TranslationService.txt exists: use this and call translate(...)
202
-
199
+ def translate_safe(self,base_language:str="en")->None:
200
+ """Translates XLF files if a translation service is configured. The translation service can be configured by creating a file at ~/.ScriptCollection/TranslationServiceProperties.txt with the content 'LibreTranslateAPI=your_api_server_url'."""
201
+ translationservice_file:str=self._protected_sc.get_global_cache_folder()+"/TranslationServiceProperties.txt"
202
+ api_server:str=None
203
+ if os.path.isfile(translationservice_file):
204
+ lines=GeneralUtilities.read_nonempty_lines_from_file(translationservice_file)
205
+ for line in lines:
206
+ if line.startswith("LibreTranslateAPI="):
207
+ api_server=line.replace("LibreTranslateAPI=","").strip()
208
+ GeneralUtilities.assert_not_null(api_server,"No translation service configured. Please create a file at ~/.ScriptCollection/TranslationServiceProperties.txt with the content 'LibreTranslateAPI=your_api_server_url' to enable automatic translation of XLF files.")
209
+ self.translate(api_server,base_language)
210
+
203
211
  @GeneralUtilities.check_arguments
204
- def translate(self,api_server:str)->None:
205
- pass#TODO if Other/Resources/Translations exists: call sc.translate_messages_in_folder(...)
212
+ def translate(self,api_server:str,base_language:str="en")->None:
213
+ folder:str=os.path.join(self.get_codeunit_folder(),"Other","Resources","Translations")
214
+ self._protected_sc.translate_xlf_files_in_folder(folder, base_language, api_server)
206
215
 
207
216
  class TFCPS_CodeUnitSpecific_NodeJS_CLI:
208
217
 
@@ -23,8 +23,7 @@ class TFCPS_CodeUnitSpecific_Python_Functions(TFCPS_CodeUnitSpecific_Base):
23
23
  def generate_bom_for_python_project(self) -> None:
24
24
  codeunit_folder: str=self.get_codeunit_folder()
25
25
  codeunitname: str=self.get_codeunit_name()
26
- repository_folder = os.path.dirname(codeunit_folder)
27
-
26
+ repository_folder = os.path.dirname(codeunit_folder)
28
27
  codeunitversion = self.tfcps_Tools_General.get_version_of_codeunit(self.get_codeunit_file())
29
28
  bom_folder = "Other/Artifacts/BOM"
30
29
  bom_folder_full = os.path.join(codeunit_folder, bom_folder)
@@ -40,7 +39,7 @@ class TFCPS_CodeUnitSpecific_Python_Functions(TFCPS_CodeUnitSpecific_Base):
40
39
 
41
40
  GeneralUtilities.ensure_file_exists(bom_file_json)
42
41
  GeneralUtilities.write_text_to_file(bom_file_json, result[1])
43
- cyclonedx_exe=self.tfcps_Tools_General.ensure_cyclonedxcli_is_available(repository_folder,not self.use_cache())
42
+ cyclonedx_exe=self.tfcps_Tools_General.ensure_cyclonedxcli_is_available(not self.use_cache())
44
43
  self._protected_sc.run_program(cyclonedx_exe, f"convert --input-file ./{codeunitname}/{bom_file_relative_json} --input-format json --output-file ./{codeunitname}/{bom_file_relative_xml} --output-format xml", repository_folder)
45
44
  self._protected_sc.format_xml_file(bom_file_xml)
46
45
  GeneralUtilities.ensure_file_does_not_exist(bom_file_json)
@@ -16,7 +16,7 @@ import urllib.request
16
16
  from packaging import version
17
17
  import requests
18
18
  from lxml import etree
19
- from ..GeneralUtilities import GeneralUtilities
19
+ from ..GeneralUtilities import GeneralUtilities,Platform
20
20
  from ..ScriptCollectionCore import ScriptCollectionCore,VSCodeWorkspaceShellTask
21
21
  from ..SCLog import LogLevel
22
22
  from ..OCIImages.AbstractImageHandler import AbstractImageHandler
@@ -38,7 +38,7 @@ class TFCPS_Tools_General:
38
38
  return GeneralUtilities.string_to_boolean(str(root.xpath('//cps:codeunit/@enabled', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0]))
39
39
 
40
40
  @GeneralUtilities.check_arguments
41
- def ensure_cyclonedxcli_is_available(self, target_folder: str,enforce_update:bool) -> str:
41
+ def ensure_cyclonedxcli_is_available(self,enforce_update:bool) -> str:
42
42
  local_filename = "cyclonedx-cli"
43
43
  filename_on_github: str
44
44
  if GeneralUtilities.current_system_is_windows():
@@ -46,20 +46,20 @@ class TFCPS_Tools_General:
46
46
  local_filename = local_filename+".exe"
47
47
  else:
48
48
  filename_on_github = "cyclonedx-linux-x64"
49
- return self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "CycloneDX", "cyclonedx-cli", "CycloneDXCLI", local_filename, lambda latest_version: filename_on_github,enforce_update=enforce_update)
49
+ return self.ensure_file_from_github_assets_is_available_with_retry("CycloneDX", "cyclonedx-cli", "CycloneDXCLI",local_filename,lambda latest_version: filename_on_github,enforce_update=enforce_update)
50
50
 
51
51
  @GeneralUtilities.check_arguments
52
- def ensure_file_from_github_assets_is_available_with_retry(self, target_folder: str, githubuser: str, githubprojectname: str, resource_name: str, local_filename: str, get_filename_on_github, amount_of_attempts: int = 5,enforce_update:bool=False) -> str:
53
- return GeneralUtilities.retry_action(lambda: self.ensure_file_from_github_assets_is_available(target_folder, githubuser, githubprojectname, resource_name, local_filename, get_filename_on_github,enforce_update), amount_of_attempts)
52
+ def ensure_file_from_github_assets_is_available_with_retry(self, githubuser: str, githubprojectname: str, local_resource_name: str, local_filename: str, get_filename_on_github, amount_of_attempts: int = 5,enforce_update:bool=False) -> str:
53
+ return GeneralUtilities.retry_action(lambda: self.ensure_file_from_github_assets_is_available(githubuser, githubprojectname, local_resource_name, local_filename, get_filename_on_github,enforce_update), amount_of_attempts)
54
54
 
55
55
  @GeneralUtilities.check_arguments
56
- def ensure_file_from_github_assets_is_available(self, target_folder: str, githubuser: str, githubprojectname: str, resource_name: str, local_filename: str, get_filename_on_github,enforce_update:bool) -> str:
56
+ def ensure_file_from_github_assets_is_available(self,githubuser: str, githubprojectname: str, local_resource_name: str, local_filename: str, get_filename_on_github,enforce_update:bool) -> str:
57
57
  #TODO use or remove target_folder-parameter
58
- resource_folder =os.path.join( self.__sc.get_global_cache_folder(),"Tools",resource_name)
58
+ resource_folder =os.path.join( self.__sc.get_global_cache_folder(),"Tools",local_resource_name)
59
59
  file = f"{resource_folder}/{local_filename}"
60
60
  file_exists = os.path.isfile(file)
61
61
  if not file_exists:
62
- self.__sc.log.log(f"Download Asset \"{githubuser}/{githubprojectname}: {resource_name}\" from GitHub to global cache...", LogLevel.Information)
62
+ self.__sc.log.log(f"Download Asset \"{githubuser}/{githubprojectname}: {local_resource_name}\" from GitHub to global cache...", LogLevel.Information)
63
63
  GeneralUtilities.ensure_folder_exists_and_is_empty(resource_folder)
64
64
  headers = { 'User-Agent': 'Mozilla/5.0'}
65
65
  self.__add_github_api_key_if_available(headers)
@@ -459,7 +459,7 @@ class TFCPS_Tools_General:
459
459
  target_original_sbom_file_relative = os.path.dirname(target_sbom_file_relative)+"/"+os.path.basename(target_sbom_file_relative)+".original.xml"
460
460
  os.rename(os.path.join(repository_folder, target_sbom_file_relative), os.path.join(repository_folder, target_original_sbom_file_relative))
461
461
 
462
- cyclonedx_exe:str=self.ensure_cyclonedxcli_is_available(repository_folder,not use_cache)
462
+ cyclonedx_exe:str=self.ensure_cyclonedxcli_is_available(not use_cache)
463
463
  self.__sc.run_program(cyclonedx_exe, f"merge --input-files {source_sbom_file_relative} {target_original_sbom_file_relative} --output-file {target_sbom_file_relative}", repository_folder)
464
464
  GeneralUtilities.ensure_file_does_not_exist(os.path.join(repository_folder, target_original_sbom_file_relative))
465
465
  self.__sc.format_xml_file(os.path.join(repository_folder, target_sbom_file_relative))
@@ -496,21 +496,20 @@ class TFCPS_Tools_General:
496
496
  def generate_svg_files_from_plantuml_files_for_repository(self, repository_folder: str,use_cache:bool) -> None:
497
497
  self.__sc.log.log("Generate svg-files from plantuml-files...")
498
498
  self.__sc.assert_is_git_repository(repository_folder)
499
- plantuml_jar_file=self.ensure_plantuml_is_available(repository_folder,not use_cache)
499
+ plantuml_jar_file=self.ensure_plantuml_is_available(not use_cache)
500
500
  target_folder = os.path.join(repository_folder, "Other", "Reference")
501
501
  self.__generate_svg_files_from_plantuml(target_folder, plantuml_jar_file)
502
502
 
503
503
  @GeneralUtilities.check_arguments
504
504
  def generate_svg_files_from_plantuml_files_for_codeunit(self, codeunit_folder: str,use_cache:bool) -> None:
505
505
  self.assert_is_codeunit_folder(codeunit_folder)
506
- repository_folder = os.path.dirname(codeunit_folder)
507
- plantuml_jar_file=self.ensure_plantuml_is_available(repository_folder,not use_cache)
506
+ plantuml_jar_file=self.ensure_plantuml_is_available(not use_cache)
508
507
  target_folder = os.path.join(codeunit_folder, "Other", "Reference")
509
508
  self.__generate_svg_files_from_plantuml(target_folder, plantuml_jar_file)
510
509
 
511
510
  @GeneralUtilities.check_arguments
512
- def ensure_plantuml_is_available(self, target_folder: str,enforce_update:bool) -> str:
513
- return self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "plantuml", "plantuml", "PlantUML", "plantuml.jar", lambda latest_version: "plantuml.jar",enforce_update=enforce_update)
511
+ def ensure_plantuml_is_available(self,enforce_update:bool) -> str:
512
+ return self.ensure_file_from_github_assets_is_available_with_retry("plantuml", "plantuml", "PlantUML", "plantuml.jar", lambda latest_version: "plantuml.jar",enforce_update=enforce_update)
514
513
 
515
514
  @GeneralUtilities.check_arguments
516
515
  def __generate_svg_files_from_plantuml(self, diagrams_files_folder: str, plantuml_jar_file: str) -> None:
@@ -578,7 +577,7 @@ class TFCPS_Tools_General:
578
577
  target_folder_extracted = os.path.join(self.__sc.get_global_cache_folder(),"Tools",resource_name)
579
578
  update:bool=not os.path.isdir(target_folder_extracted) or GeneralUtilities.folder_is_empty(target_folder_extracted) or enforce_update
580
579
  if update:
581
- downloaded_file=self.ensure_file_from_github_assets_is_available_with_retry(target_folder_unextracted, "trufflesecurity", "trufflehog", resource_name+"_Unextracted", zip_filename, lambda latest_version: f"trufflehog_{latest_version[1:]}_{osname_in_github_asset}_amd64.tar.gz",enforce_update=enforce_update)
580
+ downloaded_file=self.ensure_file_from_github_assets_is_available_with_retry( "trufflesecurity", "trufflehog", resource_name+"_Unextracted", zip_filename, lambda latest_version: f"trufflehog_{latest_version[1:]}_{osname_in_github_asset}_amd64.tar.gz",enforce_update=enforce_update)
582
581
  #TODO add option to also download arm-version
583
582
  local_zip_file: str = downloaded_file
584
583
  GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder_extracted)
@@ -639,33 +638,50 @@ class TFCPS_Tools_General:
639
638
 
640
639
  @GeneralUtilities.check_arguments
641
640
  def ensure_androidappbundletool_is_available(self, target_folder: str,enforce_update:bool) -> str:
642
- return self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "google", "bundletool", "AndroidAppBundleTool", "bundletool.jar", lambda latest_version: f"bundletool-all-{latest_version}.jar",enforce_update=enforce_update)
641
+ return self.ensure_file_from_github_assets_is_available_with_retry( "google", "bundletool", "AndroidAppBundleTool", "bundletool.jar", lambda latest_version: f"bundletool-all-{latest_version}.jar",enforce_update=enforce_update)
643
642
 
644
643
  @GeneralUtilities.check_arguments
645
644
  def ensure_mediamtx_is_available(self, target_folder: str,enforce_update:bool) -> None:
646
- def download_and_extract(osname: str, osname_in_github_asset: str, extension: str):
647
- resource_name: str = f"MediaMTX_{osname}"
648
- zip_filename: str = f"{resource_name}.{extension}"
645
+ def download_and_extract(osname: str, osname_in_github_asset: str, extension: str,architecture:Platform):
646
+ resource_name: str = f"MediaMTX_{GeneralUtilities.platform_to_dash_str(architecture)}"
649
647
  resource_folder: str = os.path.join(target_folder, "Other", "Resources", resource_name)
650
648
  target_folder_extracted = os.path.join(resource_folder, "MediaMTX")
651
649
  update:bool=not os.path.isdir(target_folder_extracted) or GeneralUtilities.folder_is_empty(target_folder_extracted) or enforce_update
652
650
  if update:
653
- self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "bluenviron", "mediamtx", resource_name, zip_filename, lambda latest_version: f"mediamtx_{latest_version}_{osname_in_github_asset}_amd64.{extension}",enforce_update=enforce_update)
654
- local_zip_file: str = os.path.join(resource_folder, f"{resource_name}.{extension}")
651
+ platform_str:str=None
652
+ match architecture:
653
+ case Platform.WindowsAMD64:
654
+ platform_str = "windows_amd64"
655
+ case Platform.LinuxARM64:
656
+ platform_str = "linux_arm64"
657
+ case Platform.LinuxAMD64:
658
+ platform_str = "linux_amd64"
659
+ case Platform.MacOSARM64:
660
+ platform_str = "darwin_arm64"
661
+ case _:
662
+ raise ValueError(f"Unknown platform: {str(architecture)}")
663
+
664
+ resource_filename_name_remote:str=f"mediamtx_{platform_str}.{extension}"
665
+ resource_name_local:str=f"MediaMTCX_{platform_str}"
666
+ global_cache_file=os.path.join( self.__sc.get_global_cache_folder(),"Tools",resource_name_local,resource_filename_name_remote)
667
+ if (not os.path.isfile( global_cache_file )) or enforce_update:
668
+ self.ensure_file_from_github_assets_is_available_with_retry( "bluenviron", "mediamtx", resource_name_local, resource_filename_name_remote, lambda latest_version: f"mediamtx_{latest_version}_{platform_str}.{extension}",enforce_update=enforce_update)
669
+ GeneralUtilities.assert_file_exists(global_cache_file)
670
+ GeneralUtilities.assert_file_exists(global_cache_file)
655
671
  GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder_extracted)
656
672
  if extension == "zip":
657
- with zipfile.ZipFile(local_zip_file, 'r') as zip_ref:
673
+ with zipfile.ZipFile(global_cache_file, 'r') as zip_ref:
658
674
  zip_ref.extractall(target_folder_extracted)
659
675
  elif extension == "tar.gz":
660
- with tarfile.open(local_zip_file, "r:gz") as tar:
676
+ with tarfile.open(global_cache_file, "r:gz") as tar:
661
677
  tar.extractall(path=target_folder_extracted)
662
678
  else:
663
679
  raise ValueError(f"Unknown extension: \"{extension}\"")
664
- GeneralUtilities.ensure_file_does_not_exist(local_zip_file)
665
680
 
666
- download_and_extract("Windows", "windows", "zip")
667
- download_and_extract("Linux", "linux", "tar.gz")
668
- download_and_extract("MacOS", "darwin", "tar.gz")
681
+ download_and_extract("Windows", "windows", "zip",Platform.WindowsAMD64)
682
+ download_and_extract("Linux", "linux", "tar.gz",Platform.LinuxAMD64)
683
+ download_and_extract("Linux", "linux", "tar.gz",Platform.LinuxARM64)
684
+ download_and_extract("MacOS", "darwin", "tar.gz",Platform.MacOSARM64)
669
685
 
670
686
  @GeneralUtilities.check_arguments
671
687
  def clone_repository_as_resource(self, local_repository_folder: str, remote_repository_link: str, resource_name: str, repository_subname: str = None,use_cache:bool=True) -> None:
@@ -1144,9 +1160,8 @@ class TFCPS_Tools_General:
1144
1160
  if write_to_file:
1145
1161
  GeneralUtilities.write_text_to_file(version_file, latest_version_function)
1146
1162
 
1147
-
1148
1163
  @GeneralUtilities.check_arguments
1149
- def push_docker_build_artifact(self, push_artifacts_file: str, registry: str, push_readme: bool, repository_folder_name: str, remote_image_name: str = None) -> None:
1164
+ def push_docker_build_artifact(self, push_artifacts_file: str, registry: str, push_readme: bool, repository_folder_name: str, remote_image_name: str = None,default_platform:Platform=None) -> None:
1150
1165
  folder_of_this_file = os.path.dirname(push_artifacts_file)
1151
1166
  filename = os.path.basename(push_artifacts_file)
1152
1167
  codeunitname_regex: str = "([a-zA-Z0-9]+)"
@@ -1160,27 +1175,44 @@ class TFCPS_Tools_General:
1160
1175
  codeunit_folder = os.path.join(repository_folder, codeunitname)
1161
1176
  artifacts_folder = os.path.join(repository_folder,codeunitname, "Other", "Artifacts")
1162
1177
  applicationimage_folder = os.path.join(artifacts_folder, "BuildResult_OCIImage")
1163
- image_file = self.__sc.find_file_by_extension(applicationimage_folder, "tar")
1164
- image_filename = os.path.basename(image_file)
1165
1178
  codeunit_version = self.get_version_of_codeunit(os.path.join(codeunit_folder, f"{codeunitname}.codeunit.xml"))
1166
1179
  if remote_image_name is None:
1167
1180
  remote_image_name = codeunitname
1168
- remote_image_name = remote_image_name.lower()
1169
- local_image_name = codeunitname.lower()
1170
- remote_repo = f"{registry}/{remote_image_name}"
1171
- remote_image_latest = f"{remote_repo}:latest"
1172
- remote_image_version = f"{remote_repo}:{codeunit_version}"
1173
- self.__sc.log.log("Load image...")
1174
- self.__sc.run_program("docker", f"load --input {image_filename}", applicationimage_folder)
1175
- self.__sc.log.log("Tag image...")
1176
- self.__sc.run_program_with_retry("docker", f"tag {local_image_name}:{codeunit_version} {remote_image_latest}")
1177
- self.__sc.run_program_with_retry("docker", f"tag {local_image_name}:{codeunit_version} {remote_image_version}")
1178
- self.__sc.log.log("Push image...")
1179
- self.__sc.run_program_with_retry("docker", f"push {remote_image_latest}")
1180
- self.__sc.run_program_with_retry("docker", f"push {remote_image_version}")
1181
- if push_readme:
1182
- self.__sc.run_program_with_retry("docker-pushrm", f"{remote_repo}", codeunit_folder)
1183
1181
 
1182
+ def push_image(tar_file:str,tag:str,remote_image_name:str):
1183
+ GeneralUtilities.assert_condition(tag==tag.lower(), f"Tag \"{tag}\" must be in lower-case.")
1184
+ remote_image_name = remote_image_name.lower()
1185
+ local_image_name = codeunitname.lower()
1186
+ remote_repo = f"{registry}/{remote_image_name}"
1187
+ remote_image_version = f"{remote_repo}:{tag}"
1188
+ self.__sc.log.log("Load image...")
1189
+ self.__sc.run_program("docker", f"load --input {tar_file}", applicationimage_folder)
1190
+ self.__sc.log.log("Tag image...")
1191
+ self.__sc.run_program_with_retry("docker", f"tag {local_image_name}:{tag} {remote_image_version}")
1192
+ self.__sc.log.log("Push image...")
1193
+ self.__sc.run_program_with_retry("docker", f"push {remote_image_version}")
1194
+ if push_readme:
1195
+ self.__sc.run_program_with_retry("docker-pushrm", f"{remote_repo}", codeunit_folder)
1196
+
1197
+ default_tar_image_file:str=None
1198
+ for image_file in GeneralUtilities.get_direct_files_of_folder(applicationimage_folder):
1199
+ image_filename = os.path.basename(image_file)
1200
+ platform:Platform=self.platform_from_filename(image_filename)
1201
+ tag=codeunit_version+"-"+GeneralUtilities.platform_to_short_str(platform).lower()
1202
+ if platform == default_platform:
1203
+ default_tar_image_file:str=image_file
1204
+ push_image(image_file,tag,remote_image_name)
1205
+ if default_tar_image_file is not None:
1206
+ push_image(default_tar_image_file,"latest",remote_image_name)
1207
+ push_image(default_tar_image_file,codeunit_version,remote_image_name)
1208
+
1209
+ @GeneralUtilities.check_arguments
1210
+ def platform_from_filename(self,filename: str) -> Platform:
1211
+ match = re.search(r'_([^_]+)\.tar', filename)
1212
+ if not match:
1213
+ raise ValueError(f"Cannot extract platform from filename: {filename}")
1214
+ return GeneralUtilities.platform_from_dash_str(match.group(1))
1215
+
1184
1216
  def prepare_building_codeunits(self,repository_folder:str,use_cache:bool,generate_development_certificate:bool):
1185
1217
  if generate_development_certificate:
1186
1218
  self.ensure_certificate_authority_for_development_purposes_is_generated(repository_folder)
@@ -1207,10 +1239,14 @@ class TFCPS_Tools_General:
1207
1239
  self.__sc.run_program("docker", f"container rm -f {container_name}", throw_exception_if_exitcode_is_not_zero=False)
1208
1240
 
1209
1241
  @GeneralUtilities.check_arguments
1210
- def load_docker_image(self, oci_image_artifacts_folder:str) -> None:
1211
- image_filename = os.path.basename(self.__sc.find_file_by_extension(oci_image_artifacts_folder, "tar"))
1212
- self.__sc.log.log("Load docker-image...")
1213
- self.__sc.run_program("docker", f"load -i {image_filename}", oci_image_artifacts_folder)
1242
+ def load_docker_image(self, oci_image_artifacts_folder:str,platform:Platform) -> None:
1243
+ for file in GeneralUtilities.get_direct_files_of_folder(oci_image_artifacts_folder):
1244
+ if file.endswith(f"_{GeneralUtilities.platform_to_dash_str(platform)}.tar"):
1245
+ image_filename = file
1246
+ self.__sc.log.log("Load docker-image...")
1247
+ self.__sc.run_program("docker", f"load -i {image_filename}", oci_image_artifacts_folder)
1248
+ return
1249
+ raise ValueError(f"No docker-image found for platform {GeneralUtilities.platform_to_dash_str(platform)} in folder {oci_image_artifacts_folder}.")
1214
1250
 
1215
1251
  @GeneralUtilities.check_arguments
1216
1252
  def start_dockerfile_example(self, current_file: str,remove_old_container: bool, remove_volumes_folder: bool, env_file: str) -> None:
@@ -1225,7 +1261,7 @@ class TFCPS_Tools_General:
1225
1261
  self.__sc.log.log(f"Ensure container of {docker_compose_file} do not exist...")
1226
1262
  oci_image_artifacts_folder = GeneralUtilities.resolve_relative_path("../../../../Artifacts/BuildResult_OCIImage", folder_of_current_file)
1227
1263
  self.ensure_containers_are_not_running(container_names_to_remove)
1228
- self.load_docker_image(oci_image_artifacts_folder)
1264
+ self.load_docker_image(oci_image_artifacts_folder,GeneralUtilities.get_current_platform())
1229
1265
  example_name = os.path.basename(folder_of_current_file)
1230
1266
  codeunit_name = os.path.basename(GeneralUtilities.resolve_relative_path("../../../../..", folder_of_current_file))
1231
1267
  if remove_volumes_folder:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ScriptCollection
3
- Version: 4.2.57
3
+ Version: 4.2.59
4
4
  Summary: The ScriptCollection is the place for reusable scripts.
5
5
  Home-page: https://github.com/anionDev/ScriptCollection
6
6
  Author: Marius Göcke
@@ -1,7 +1,7 @@
1
1
  ScriptCollection/AnionBuildPlatform.py,sha256=K-PHarX802A0PU8uRu0GNcEZiXujFoXHACe-X9YJsAQ,11711
2
2
  ScriptCollection/CertificateUpdater.py,sha256=Pa6eyjQSx7IIvj4PQVMI0IwMs01KQrNSB7Qa-7lRfBs,9375
3
- ScriptCollection/Executables.py,sha256=O2apj_jYLmYy9_0iK3SFgez4EvfthIW1JsAmlGwp-8M,44534
4
- ScriptCollection/GeneralUtilities.py,sha256=J136V1Xb5-Q8VCKzms3oTo6teMjYPA4sMds8lD8R1PE,61183
3
+ ScriptCollection/Executables.py,sha256=qpo0g5peWdlK5uLIUCyLDB9c3JCk0ETbtmOJXZwuHh4,44510
4
+ ScriptCollection/GeneralUtilities.py,sha256=_7R_t46G1u8JNeUEl4HsgQ2mBGARFR6nIX_NUY2VNQY,64846
5
5
  ScriptCollection/HTTPMaintenanceOverheadHelper.py,sha256=TToNtyO1XzsMbBsTBf3o0xgOK0v4Jf03qw2Z0xb2nCk,2007
6
6
  ScriptCollection/ProcessesRunner.py,sha256=o5raxIt3lknNPoPrjNzJ2bprRPJ3SnL0rrR7crraD7E,1523
7
7
  ScriptCollection/ProgramRunnerBase.py,sha256=4A2eQgSg_rRgQcgSi-LYtUlM-uSQEpS7qFWn0tWt4uo,2171
@@ -9,7 +9,7 @@ ScriptCollection/ProgramRunnerMock.py,sha256=uTu-aFle1W_oKjeQEmuPsFPQpvo0kRf2FrR
9
9
  ScriptCollection/ProgramRunnerPopen.py,sha256=BPY7-ZMIlqT7JOKz8qlB5c0laF2Js-ijzqk09GxZC48,3821
10
10
  ScriptCollection/ProgramRunnerSudo.py,sha256=_khC3xuTdrPoLluBJZWfldltmmuKltABJPcbjZSFW-4,4835
11
11
  ScriptCollection/SCLog.py,sha256=8TRy1LeYMsPOIuWUcnUNNbO5pd-cNBS-3cn-kdzP8FU,4768
12
- ScriptCollection/ScriptCollectionCore.py,sha256=jjGd7cbFB-FpsXsymVoFcF_M9wXIZ2AIttcRfiXl8IU,174895
12
+ ScriptCollection/ScriptCollectionCore.py,sha256=8U9gYLcex7DcrafJ6zP6_CN6YP5ebd-aLh5givt8SHo,175443
13
13
  ScriptCollection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  ScriptCollection/OCIImages/AbstractImageHandler.py,sha256=83qDMILwxhH9DbC0sb358Vu8PXEysmJJyap_6gECZqs,1627
15
15
  ScriptCollection/OCIImages/OCIImageManager.py,sha256=aBogkSXNDyi8NO11N-s03nuFJEv7PyJ-wjHuYYeZfvs,6662
@@ -30,9 +30,9 @@ ScriptCollection/TFCPS/TFCPS_Generic.py,sha256=O-0guM_LJCcZmPZJhMgTvXD2RXUJEBWWv
30
30
  ScriptCollection/TFCPS/TFCPS_MergeToMain.py,sha256=-Ev9D3bZDlUk2WFQhcmvzQ3FCS97OdsVUd0koAdmpZc,7474
31
31
  ScriptCollection/TFCPS/TFCPS_MergeToStable.py,sha256=Ajfy2pLajTuU6UpwItHt4C2a-gLF3gPc4z6BktL3Cio,22163
32
32
  ScriptCollection/TFCPS/TFCPS_PreBuildCodeunitsScript.py,sha256=f0Uq1cA_4LvmL72cal0crrbKF6PcxL13D9wBKuQ1YBw,2328
33
- ScriptCollection/TFCPS/TFCPS_Tools_General.py,sha256=WV67pHLrhD_qEjUqM-8tUpEdhQ4brwVentfIf7AMOP4,93942
33
+ ScriptCollection/TFCPS/TFCPS_Tools_General.py,sha256=zkdO9kHgPY-BHb13E9TbEIy06JD3EnKe2CWTx-6i5tM,96034
34
34
  ScriptCollection/TFCPS/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- ScriptCollection/TFCPS/Docker/TFCPS_CodeUnitSpecific_Docker.py,sha256=BPJkoy2KVgB_38cAk5qjM4s4FpJvOkTp467nrvANIIU,10606
35
+ ScriptCollection/TFCPS/Docker/TFCPS_CodeUnitSpecific_Docker.py,sha256=VE1jAEP7trndm8u_5UR7YgWbHOvZJvRDaKUmPvtT4CM,11052
36
36
  ScriptCollection/TFCPS/Docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationBase.py,sha256=bT6Gd5pQpZCw4OQz6HWkPCSn5z__eUUEisABLDSxd0o,200
38
38
  ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationGenerate.py,sha256=QyjOfMY22JWCvKjMelHiDWbJiWqotOfebpJpgDUaoO4,237
@@ -43,12 +43,12 @@ ScriptCollection/TFCPS/Flutter/TFCPS_CodeUnitSpecific_Flutter.py,sha256=U8oBAOLR
43
43
  ScriptCollection/TFCPS/Flutter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
44
  ScriptCollection/TFCPS/Go/TFCPS_CodeUnitSpecific_Go.py,sha256=kyx26AnT1-LySFA46wfJ9yZUKYdMWTD0U2XZfSQbuB0,3497
45
45
  ScriptCollection/TFCPS/Go/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
- ScriptCollection/TFCPS/NodeJS/TFCPS_CodeUnitSpecific_NodeJS.py,sha256=Kkyyof6DML7jUdES5GmIXQ_yVMJ1xnJbZmbIAqls3IE,11816
46
+ ScriptCollection/TFCPS/NodeJS/TFCPS_CodeUnitSpecific_NodeJS.py,sha256=kL37qJNwH6E-CAJqcHbdnc0K0uGjHQ8XD1RXZOdcw1M,12850
47
47
  ScriptCollection/TFCPS/NodeJS/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- ScriptCollection/TFCPS/Python/TFCPS_CodeUnitSpecific_Python.py,sha256=nLw_eSUd_56jjgfcAvtUyzecSZ14mYmNJl0iu-1YNVk,13496
48
+ ScriptCollection/TFCPS/Python/TFCPS_CodeUnitSpecific_Python.py,sha256=KFKDdfV9DFHE5n7TI6m1Ra1Qw2JwL_JQjneBfqcRQ_w,13467
49
49
  ScriptCollection/TFCPS/Python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- scriptcollection-4.2.57.dist-info/METADATA,sha256=iMa7-_9uZdnhT-dv-aA7iTfCkOk5d8-Os0jqzuyFT1Y,7690
51
- scriptcollection-4.2.57.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
52
- scriptcollection-4.2.57.dist-info/entry_points.txt,sha256=27XwAJEcaMEc1be0Ec1vKHCbiU4Ziu8jKL-SqsrYOIQ,4680
53
- scriptcollection-4.2.57.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
54
- scriptcollection-4.2.57.dist-info/RECORD,,
50
+ scriptcollection-4.2.59.dist-info/METADATA,sha256=2AVlFcG9tMUQIWyeWjV0L4xuq46zScDA77h04vgkmco,7690
51
+ scriptcollection-4.2.59.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
52
+ scriptcollection-4.2.59.dist-info/entry_points.txt,sha256=27XwAJEcaMEc1be0Ec1vKHCbiU4Ziu8jKL-SqsrYOIQ,4680
53
+ scriptcollection-4.2.59.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
54
+ scriptcollection-4.2.59.dist-info/RECORD,,