cycode 2.2.0__tar.gz → 2.2.1.dev2__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 (134) hide show
  1. {cycode-2.2.0 → cycode-2.2.1.dev2}/PKG-INFO +3 -3
  2. {cycode-2.2.0 → cycode-2.2.1.dev2}/README.md +2 -2
  3. cycode-2.2.1.dev2/cycode/__init__.py +1 -0
  4. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/main_cli.py +31 -1
  5. cycode-2.2.1.dev2/cycode/cli/commands/version/version_checker.py +209 -0
  6. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/path_utils.py +5 -2
  7. {cycode-2.2.0 → cycode-2.2.1.dev2}/pyproject.toml +1 -1
  8. cycode-2.2.0/cycode/__init__.py +0 -1
  9. {cycode-2.2.0 → cycode-2.2.1.dev2}/LICENCE +0 -0
  10. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/__init__.py +0 -0
  11. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/__init__.py +0 -0
  12. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/ai_remediation/__init__.py +0 -0
  13. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/ai_remediation/ai_remediation_command.py +0 -0
  14. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/auth/__init__.py +0 -0
  15. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/auth/auth_command.py +0 -0
  16. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/auth/auth_manager.py +0 -0
  17. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/auth_common.py +0 -0
  18. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/configure/__init__.py +0 -0
  19. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/configure/configure_command.py +0 -0
  20. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/ignore/__init__.py +0 -0
  21. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/ignore/ignore_command.py +0 -0
  22. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/__init__.py +0 -0
  23. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/report_command.py +0 -0
  24. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/sbom/__init__.py +0 -0
  25. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/sbom/common.py +0 -0
  26. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/sbom/path/__init__.py +0 -0
  27. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/sbom/path/path_command.py +0 -0
  28. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/sbom/repository_url/__init__.py +0 -0
  29. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/sbom/repository_url/repository_url_command.py +0 -0
  30. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/sbom/sbom_command.py +0 -0
  31. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/report/sbom/sbom_report_file.py +0 -0
  32. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/__init__.py +0 -0
  33. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/code_scanner.py +0 -0
  34. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/commit_history/__init__.py +0 -0
  35. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/commit_history/commit_history_command.py +0 -0
  36. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/path/__init__.py +0 -0
  37. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/path/path_command.py +0 -0
  38. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/pre_commit/__init__.py +0 -0
  39. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/pre_commit/pre_commit_command.py +0 -0
  40. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/pre_receive/__init__.py +0 -0
  41. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/pre_receive/pre_receive_command.py +0 -0
  42. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/repository/__init__.py +0 -0
  43. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/repository/repository_command.py +0 -0
  44. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/scan_ci/__init__.py +0 -0
  45. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/scan_ci/ci_integrations.py +0 -0
  46. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/scan_ci/scan_ci_command.py +0 -0
  47. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/scan/scan_command.py +0 -0
  48. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/status/__init__.py +0 -0
  49. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/status/status_command.py +0 -0
  50. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/version/__init__.py +0 -0
  51. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/commands/version/version_command.py +0 -0
  52. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/config.py +0 -0
  53. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/config.yaml +0 -0
  54. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/consts.py +0 -0
  55. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/exceptions/__init__.py +0 -0
  56. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/exceptions/common.py +0 -0
  57. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  58. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
  59. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
  60. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
  61. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/__init__.py +0 -0
  62. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/excluder.py +0 -0
  63. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/iac/__init__.py +0 -0
  64. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  65. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/models/__init__.py +0 -0
  66. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  67. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/path_documents.py +0 -0
  68. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/repository_documents.py +0 -0
  69. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/__init__.py +0 -0
  70. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  71. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
  72. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  73. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  74. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  75. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  76. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
  77. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
  78. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
  79. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  80. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
  81. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
  82. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
  83. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  84. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/sca/sca_code_scanner.py +0 -0
  85. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/walk_ignore.py +0 -0
  86. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/files_collector/zip_documents.py +0 -0
  87. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/main.py +0 -0
  88. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/models.py +0 -0
  89. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/__init__.py +0 -0
  90. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/console_printer.py +0 -0
  91. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/json_printer.py +0 -0
  92. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/printer_base.py +0 -0
  93. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/tables/__init__.py +0 -0
  94. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  95. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/tables/table.py +0 -0
  96. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/tables/table_models.py +0 -0
  97. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/tables/table_printer.py +0 -0
  98. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  99. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/printers/text_printer.py +0 -0
  100. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/sentry.py +0 -0
  101. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/user_settings/__init__.py +0 -0
  102. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/user_settings/base_file_manager.py +0 -0
  103. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/user_settings/config_file_manager.py +0 -0
  104. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/user_settings/configuration_manager.py +0 -0
  105. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/user_settings/credentials_manager.py +0 -0
  106. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/user_settings/jwt_creator.py +0 -0
  107. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/__init__.py +0 -0
  108. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/enum_utils.py +0 -0
  109. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/get_api_client.py +0 -0
  110. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/git_proxy.py +0 -0
  111. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/ignore_utils.py +0 -0
  112. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/jwt_utils.py +0 -0
  113. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/progress_bar.py +0 -0
  114. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/scan_batch.py +0 -0
  115. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/scan_utils.py +0 -0
  116. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/shell_executor.py +0 -0
  117. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/string_utils.py +0 -0
  118. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/task_timer.py +0 -0
  119. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cli/utils/yaml_utils.py +0 -0
  120. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/__init__.py +0 -0
  121. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/auth_client.py +0 -0
  122. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/client_creator.py +0 -0
  123. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/config.py +0 -0
  124. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/config.yaml +0 -0
  125. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/config_dev.py +0 -0
  126. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/cycode_client.py +0 -0
  127. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/cycode_client_base.py +0 -0
  128. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  129. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/cycode_token_based_client.py +0 -0
  130. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/headers.py +0 -0
  131. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/models.py +0 -0
  132. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/report_client.py +0 -0
  133. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/scan_client.py +0 -0
  134. {cycode-2.2.0 → cycode-2.2.1.dev2}/cycode/cyclient/scan_config_base.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cycode
3
- Version: 2.2.0
3
+ Version: 2.2.1.dev2
4
4
  Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
5
5
  Home-page: https://github.com/cycodehq/cycode-cli
6
6
  License: MIT
@@ -458,11 +458,11 @@ To limit the results of the `sca` scan to a specific severity threshold, add the
458
458
 
459
459
  Consider the following example. The following command will scan the repository for SCA policy violations that have a severity of Medium or higher:
460
460
 
461
- `cycode scan -t sca --security-threshold MEDIUM repository ~/home/git/codebase`
461
+ `cycode scan -t sca --severity-threshold MEDIUM repository ~/home/git/codebase`
462
462
 
463
463
  or:
464
464
 
465
- `cycode scan --scan-type sca --security-threshold MEDIUM repository ~/home/git/codebase`
465
+ `cycode scan --scan-type sca --severity-threshold MEDIUM repository ~/home/git/codebase`
466
466
 
467
467
  ### Path Scan
468
468
 
@@ -417,11 +417,11 @@ To limit the results of the `sca` scan to a specific severity threshold, add the
417
417
 
418
418
  Consider the following example. The following command will scan the repository for SCA policy violations that have a severity of Medium or higher:
419
419
 
420
- `cycode scan -t sca --security-threshold MEDIUM repository ~/home/git/codebase`
420
+ `cycode scan -t sca --severity-threshold MEDIUM repository ~/home/git/codebase`
421
421
 
422
422
  or:
423
423
 
424
- `cycode scan --scan-type sca --security-threshold MEDIUM repository ~/home/git/codebase`
424
+ `cycode scan --scan-type sca --severity-threshold MEDIUM repository ~/home/git/codebase`
425
425
 
426
426
  ### Path Scan
427
427
 
@@ -0,0 +1 @@
1
+ __version__ = '2.2.1.dev2' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -3,6 +3,7 @@ from typing import Optional
3
3
 
4
4
  import click
5
5
 
6
+ from cycode import __version__
6
7
  from cycode.cli.commands.ai_remediation.ai_remediation_command import ai_remediation_command
7
8
  from cycode.cli.commands.auth.auth_command import auth_command
8
9
  from cycode.cli.commands.configure.configure_command import configure_command
@@ -10,6 +11,7 @@ from cycode.cli.commands.ignore.ignore_command import ignore_command
10
11
  from cycode.cli.commands.report.report_command import report_command
11
12
  from cycode.cli.commands.scan.scan_command import scan_command
12
13
  from cycode.cli.commands.status.status_command import status_command
14
+ from cycode.cli.commands.version.version_checker import version_checker
13
15
  from cycode.cli.commands.version.version_command import version_command
14
16
  from cycode.cli.consts import (
15
17
  CLI_CONTEXT_SETTINGS,
@@ -48,6 +50,12 @@ from cycode.cyclient.models import UserAgentOptionScheme
48
50
  default=False,
49
51
  help='Do not show the progress meter.',
50
52
  )
53
+ @click.option(
54
+ '--no-update-notifier',
55
+ is_flag=True,
56
+ default=False,
57
+ help='Do not check CLI for updates.',
58
+ )
51
59
  @click.option(
52
60
  '--output',
53
61
  '-o',
@@ -63,7 +71,12 @@ from cycode.cyclient.models import UserAgentOptionScheme
63
71
  )
64
72
  @click.pass_context
65
73
  def main_cli(
66
- context: click.Context, verbose: bool, no_progress_meter: bool, output: str, user_agent: Optional[str]
74
+ context: click.Context,
75
+ verbose: bool,
76
+ no_progress_meter: bool,
77
+ no_update_notifier: bool,
78
+ output: str,
79
+ user_agent: Optional[str],
67
80
  ) -> None:
68
81
  init_sentry()
69
82
  add_breadcrumb('cycode')
@@ -85,3 +98,20 @@ def main_cli(
85
98
  if user_agent:
86
99
  user_agent_option = UserAgentOptionScheme().loads(user_agent)
87
100
  CycodeClientBase.enrich_user_agent(user_agent_option.user_agent_suffix)
101
+
102
+ if not no_update_notifier:
103
+ context.call_on_close(lambda: check_latest_version_on_close())
104
+
105
+
106
+ @click.pass_context
107
+ def check_latest_version_on_close(context: click.Context) -> None:
108
+ output = context.obj.get('output')
109
+ # don't print anything if the output is JSON
110
+ if output == 'json':
111
+ return
112
+
113
+ # we always want to check the latest version for "version" and "status" commands
114
+ should_use_cache = context.invoked_subcommand not in {'version', 'status'}
115
+ version_checker.check_and_notify_update(
116
+ current_version=__version__, use_color=context.color, use_cache=should_use_cache
117
+ )
@@ -0,0 +1,209 @@
1
+ import os
2
+ import re
3
+ import time
4
+ from pathlib import Path
5
+ from typing import List, Optional, Tuple
6
+
7
+ import click
8
+
9
+ from cycode.cli.user_settings.configuration_manager import ConfigurationManager
10
+ from cycode.cli.utils.path_utils import get_file_content
11
+ from cycode.cyclient.cycode_client_base import CycodeClientBase
12
+
13
+
14
+ def _compare_versions(
15
+ current_parts: List[int],
16
+ latest_parts: List[int],
17
+ current_is_pre: bool,
18
+ latest_is_pre: bool,
19
+ latest_version: str,
20
+ ) -> Optional[str]:
21
+ """Compare version numbers and determine if an update is needed.
22
+
23
+ Implements version comparison logic with special handling for pre-release versions:
24
+ - Won't suggest downgrading from stable to pre-release
25
+ - Will suggest upgrading from pre-release to stable of the same version
26
+
27
+ Args:
28
+ current_parts: List of numeric version components for the current version
29
+ latest_parts: List of numeric version components for the latest version
30
+ current_is_pre: Whether the current version is pre-release
31
+ latest_is_pre: Whether the latest version is pre-release
32
+ latest_version: The full latest version string
33
+
34
+ Returns:
35
+ str | None: The latest version string if an update is recommended,
36
+ None if no update is needed
37
+ """
38
+ # If current is stable and latest is pre-release, don't suggest update
39
+ if not current_is_pre and latest_is_pre:
40
+ return None
41
+
42
+ # Compare version numbers
43
+ for current, latest in zip(current_parts, latest_parts):
44
+ if latest > current:
45
+ return latest_version
46
+ if current > latest:
47
+ return None
48
+
49
+ # If all numbers are equal, suggest update if current is pre-release and latest is stable
50
+ if current_is_pre and not latest_is_pre:
51
+ return latest_version
52
+
53
+ return None
54
+
55
+
56
+ class VersionChecker(CycodeClientBase):
57
+ PYPI_API_URL = 'https://pypi.org/pypi'
58
+ PYPI_PACKAGE_NAME = 'cycode'
59
+
60
+ GIT_CHANGELOG_URL_PREFIX = 'https://github.com/cycodehq/cycode-cli/releases/tag/v'
61
+
62
+ DAILY = 24 * 60 * 60 # 24 hours in seconds
63
+ WEEKLY = DAILY * 7
64
+
65
+ def __init__(self) -> None:
66
+ """Initialize the VersionChecker.
67
+
68
+ Sets up the version checker with PyPI API URL and configure the cache file location
69
+ using the global configuration directory.
70
+ """
71
+ super().__init__(self.PYPI_API_URL)
72
+
73
+ configuration_manager = ConfigurationManager()
74
+ config_dir = configuration_manager.global_config_file_manager.get_config_directory_path()
75
+ self.cache_file = Path(config_dir) / '.version_check'
76
+
77
+ def get_latest_version(self) -> Optional[str]:
78
+ """Fetch the latest version of the package from PyPI.
79
+
80
+ Makes an HTTP request to PyPI's JSON API to get the latest version information.
81
+
82
+ Returns:
83
+ str | None: The latest version string if successful, None if the request fails
84
+ or the version information is not available.
85
+ """
86
+ try:
87
+ response = self.get(f'{self.PYPI_PACKAGE_NAME}/json')
88
+ data = response.json()
89
+ return data.get('info', {}).get('version')
90
+ except Exception:
91
+ return None
92
+
93
+ @staticmethod
94
+ def _parse_version(version: str) -> Tuple[List[int], bool]:
95
+ """Parse version string into components and identify if it's a pre-release.
96
+
97
+ Extracts numeric version components and determines if the version is a pre-release
98
+ by checking for 'dev' in the version string.
99
+
100
+ Args:
101
+ version: The version string to parse (e.g., '1.2.3' or '1.2.3dev4')
102
+
103
+ Returns:
104
+ tuple: A tuple containing:
105
+ - List[int]: List of numeric version components
106
+ - bool: True if this is a pre-release version, False otherwise
107
+ """
108
+ version_parts = [int(x) for x in re.findall(r'\d+', version)]
109
+ is_prerelease = 'dev' in version
110
+
111
+ return version_parts, is_prerelease
112
+
113
+ def _should_check_update(self, is_prerelease: bool) -> bool:
114
+ """Determine if an update check should be performed based on the last check time.
115
+
116
+ Implements a time-based caching mechanism where update checks are performed:
117
+ - Daily for pre-release versions
118
+ - Weekly for stable versions
119
+
120
+ Args:
121
+ is_prerelease: Whether the current version is a pre-release
122
+
123
+ Returns:
124
+ bool: True if an update check should be performed, False otherwise
125
+ """
126
+ if not os.path.exists(self.cache_file):
127
+ return True
128
+
129
+ file_content = get_file_content(self.cache_file)
130
+ if file_content is None:
131
+ return True
132
+
133
+ try:
134
+ last_check = float(file_content.strip())
135
+ except ValueError:
136
+ return True
137
+
138
+ duration = self.DAILY if is_prerelease else self.WEEKLY
139
+ return time.time() - last_check >= duration
140
+
141
+ def _update_last_check(self) -> None:
142
+ """Update the timestamp of the last update check.
143
+
144
+ Creates the cache directory if it doesn't exist and write the current timestamp
145
+ to the cache file. Silently handle any IO errors that might occur during the process.
146
+ """
147
+ try:
148
+ os.makedirs(os.path.dirname(self.cache_file), exist_ok=True)
149
+ with open(self.cache_file, 'w', encoding='UTF-8') as f:
150
+ f.write(str(time.time()))
151
+ except IOError:
152
+ pass
153
+
154
+ def check_for_update(self, current_version: str, use_cache: bool = True) -> Optional[str]:
155
+ """Check if an update is available for the current version.
156
+
157
+ Respects the update check frequency (daily/weekly) based on the version type
158
+
159
+ Args:
160
+ current_version: The current version string of the CLI
161
+ use_cache: If True, use the cached timestamp to determine if an update check is needed
162
+
163
+ Returns:
164
+ str | None: The latest version string if an update is recommended,
165
+ None if no update is needed or if check should be skipped
166
+ """
167
+ current_parts, current_is_pre = self._parse_version(current_version)
168
+
169
+ # Check if we should perform the update check based on frequency
170
+ if use_cache and not self._should_check_update(current_is_pre):
171
+ return None
172
+
173
+ latest_version = self.get_latest_version()
174
+ if not latest_version:
175
+ return None
176
+
177
+ # Update the last check timestamp
178
+ use_cache and self._update_last_check()
179
+
180
+ latest_parts, latest_is_pre = self._parse_version(latest_version)
181
+ return _compare_versions(current_parts, latest_parts, current_is_pre, latest_is_pre, latest_version)
182
+
183
+ def check_and_notify_update(self, current_version: str, use_color: bool = True, use_cache: bool = True) -> None:
184
+ """Check for updates and display a notification if a new version is available.
185
+
186
+ Performs the version check and displays a formatted message with update instructions
187
+ if a newer version is available. The message includes:
188
+ - Current and new version numbers
189
+ - Link to the changelog
190
+ - Command to perform the update
191
+
192
+ Args:
193
+ current_version: Current version of the CLI
194
+ use_color: If True, use colored output in the terminal
195
+ use_cache: If True, use the cached timestamp to determine if an update check is needed
196
+ """
197
+ latest_version = self.check_for_update(current_version, use_cache)
198
+ should_update = bool(latest_version)
199
+ if should_update:
200
+ update_message = (
201
+ '\nNew version of cycode available! '
202
+ f"{click.style(current_version, fg='yellow')} → {click.style(latest_version, fg='bright_blue')}\n"
203
+ f"Changelog: {click.style(f'{self.GIT_CHANGELOG_URL_PREFIX}{latest_version}', fg='bright_blue')}\n"
204
+ f"Run {click.style('pip install --upgrade cycode', fg='green')} to update\n"
205
+ )
206
+ click.echo(update_message, color=use_color)
207
+
208
+
209
+ version_checker = VersionChecker()
@@ -1,13 +1,16 @@
1
1
  import json
2
2
  import os
3
3
  from functools import lru_cache
4
- from typing import AnyStr, List, Optional
4
+ from typing import TYPE_CHECKING, AnyStr, List, Optional, Union
5
5
 
6
6
  import click
7
7
  from binaryornot.helpers import is_binary_string
8
8
 
9
9
  from cycode.cyclient import logger
10
10
 
11
+ if TYPE_CHECKING:
12
+ from os import PathLike
13
+
11
14
 
12
15
  @lru_cache(maxsize=None)
13
16
  def is_sub_path(path: str, sub_path: str) -> bool:
@@ -73,7 +76,7 @@ def join_paths(path: str, filename: str) -> str:
73
76
  return os.path.join(path, filename)
74
77
 
75
78
 
76
- def get_file_content(file_path: str) -> Optional[AnyStr]:
79
+ def get_file_content(file_path: Union[str, 'PathLike']) -> Optional[AnyStr]:
77
80
  try:
78
81
  with open(file_path, 'r', encoding='UTF-8') as f:
79
82
  return f.read()
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cycode"
3
- version = "2.2.0" # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
3
+ version = "2.2.1.dev2" # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
4
4
  description = "Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning."
5
5
  keywords=["secret-scan", "cycode", "devops", "token", "secret", "security", "cycode", "code"]
6
6
  authors = ["Cycode <support@cycode.com>"]
@@ -1 +0,0 @@
1
- __version__ = '2.2.0' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes