cycode 3.25.1.dev3__tar.gz → 3.25.1.dev5__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.

Potentially problematic release.


This version of cycode might be problematic. Click here for more details.

Files changed (229) hide show
  1. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/PKG-INFO +1 -1
  2. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/__init__.py +1 -1
  3. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/ides/codex.py +9 -12
  4. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/npm/restore_bun_dependencies.py +10 -3
  5. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +1 -1
  6. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +32 -13
  7. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +10 -3
  8. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +10 -3
  9. cycode-3.25.1.dev5/cycode/cli/files_collector/sca/npm/workspace/__init__.py +49 -0
  10. cycode-3.25.1.dev5/cycode/cli/files_collector/sca/npm/workspace/coverage.py +142 -0
  11. cycode-3.25.1.dev5/cycode/cli/files_collector/sca/npm/workspace/files.py +38 -0
  12. cycode-3.25.1.dev5/cycode/cli/files_collector/sca/npm/workspace/globs.py +116 -0
  13. cycode-3.25.1.dev5/cycode/cli/files_collector/sca/npm/workspace/names.py +16 -0
  14. cycode-3.25.1.dev5/cycode/cli/files_collector/sca/npm/workspace/resolvers.py +294 -0
  15. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/sca_file_collector.py +3 -0
  16. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/path_utils.py +31 -4
  17. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/pyproject.toml +1 -1
  18. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/LICENCE +0 -0
  19. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/README.md +0 -0
  20. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/__main__.py +0 -0
  21. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/__init__.py +0 -0
  22. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/app.py +0 -0
  23. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/__init__.py +0 -0
  24. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/activation_manager.py +0 -0
  25. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
  26. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/command_utils.py +0 -0
  27. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/consts.py +0 -0
  28. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/hooks_manager.py +0 -0
  29. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/ides/__init__.py +0 -0
  30. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +0 -0
  31. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/ides/_skill_utils.py +0 -0
  32. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/ides/base.py +0 -0
  33. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/ides/claude_code.py +0 -0
  34. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/ides/copilot.py +0 -0
  35. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/ides/cursor.py +0 -0
  36. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/install_command.py +0 -0
  37. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
  38. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -0
  39. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/detach.py +0 -0
  40. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/guardrail_config.py +0 -0
  41. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/handlers.py +0 -0
  42. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -0
  43. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/policy.py +0 -0
  44. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/scan_command.py +0 -0
  45. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/types.py +0 -0
  46. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
  47. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/session_start_command.py +0 -0
  48. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/status_command.py +0 -0
  49. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_guardrails/uninstall_command.py +0 -0
  50. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
  51. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
  52. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
  53. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
  54. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/api/__init__.py +0 -0
  55. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/api/api_command.py +0 -0
  56. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/api/openapi_spec.py +0 -0
  57. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/auth/__init__.py +0 -0
  58. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/auth/auth_command.py +0 -0
  59. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/auth/auth_common.py +0 -0
  60. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/auth/auth_manager.py +0 -0
  61. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/auth/models.py +0 -0
  62. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/configure/__init__.py +0 -0
  63. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/configure/configure_command.py +0 -0
  64. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/configure/consts.py +0 -0
  65. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/configure/messages.py +0 -0
  66. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/configure/prompts.py +0 -0
  67. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ignore/__init__.py +0 -0
  68. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/ignore/ignore_command.py +0 -0
  69. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/mcp/__init__.py +0 -0
  70. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/mcp/mcp_command.py +0 -0
  71. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/__init__.py +0 -0
  72. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/report_command.py +0 -0
  73. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/sbom/__init__.py +0 -0
  74. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/sbom/common.py +0 -0
  75. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
  76. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/sbom/path/path_command.py +0 -0
  77. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
  78. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
  79. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
  80. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
  81. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report_import/__init__.py +0 -0
  82. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report_import/report_import_command.py +0 -0
  83. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
  84. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
  85. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/sca_options.py +0 -0
  86. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/__init__.py +0 -0
  87. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/aggregation_report.py +0 -0
  88. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/code_scanner.py +0 -0
  89. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
  90. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
  91. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/commit_range_scanner.py +0 -0
  92. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/detection_excluder.py +0 -0
  93. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/path/__init__.py +0 -0
  94. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/path/path_command.py +0 -0
  95. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
  96. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
  97. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
  98. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
  99. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
  100. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
  101. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
  102. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/repository/__init__.py +0 -0
  103. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
  104. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
  105. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
  106. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
  107. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/scan_command.py +0 -0
  108. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/scan_parameters.py +0 -0
  109. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/scan/scan_result.py +0 -0
  110. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/status/__init__.py +0 -0
  111. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/status/get_cli_status.py +0 -0
  112. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/status/models.py +0 -0
  113. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/status/status_command.py +0 -0
  114. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/apps/status/version_command.py +0 -0
  115. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/cli_types.py +0 -0
  116. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/config.py +0 -0
  117. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/console.py +0 -0
  118. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/consts.py +0 -0
  119. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/exceptions/__init__.py +0 -0
  120. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  121. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
  122. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
  123. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/exceptions/handle_errors.py +0 -0
  124. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
  125. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
  126. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/__init__.py +0 -0
  127. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/commit_range_documents.py +0 -0
  128. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
  129. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/file_excluder.py +0 -0
  130. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/iac/__init__.py +0 -0
  131. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  132. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/models/__init__.py +0 -0
  133. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  134. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/path_documents.py +0 -0
  135. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/repository_documents.py +0 -0
  136. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/__init__.py +0 -0
  137. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  138. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
  139. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  140. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  141. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  142. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  143. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
  144. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
  145. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  146. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
  147. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
  148. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
  149. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/python/restore_pip_dependencies.py +0 -0
  150. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
  151. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
  152. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +0 -0
  153. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
  154. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
  155. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
  156. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  157. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/walk_ignore.py +0 -0
  158. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/files_collector/zip_documents.py +0 -0
  159. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/logger.py +0 -0
  160. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/main.py +0 -0
  161. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/models.py +0 -0
  162. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/__init__.py +0 -0
  163. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/console_printer.py +0 -0
  164. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/json_printer.py +0 -0
  165. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/printer_base.py +0 -0
  166. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/rich_printer.py +0 -0
  167. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/tables/__init__.py +0 -0
  168. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  169. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/tables/table.py +0 -0
  170. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/tables/table_models.py +0 -0
  171. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/tables/table_printer.py +0 -0
  172. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  173. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/text_printer.py +0 -0
  174. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/__init__.py +0 -0
  175. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
  176. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/detection_data.py +0 -0
  177. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
  178. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
  179. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
  180. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/rich_helpers.py +0 -0
  181. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/sca_ossf.py +0 -0
  182. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/printers/utils/sca_policy_details.py +0 -0
  183. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/user_settings/__init__.py +0 -0
  184. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/user_settings/base_file_manager.py +0 -0
  185. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/user_settings/config_file_manager.py +0 -0
  186. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/user_settings/configuration_manager.py +0 -0
  187. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/user_settings/credentials_manager.py +0 -0
  188. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/user_settings/jwt_creator.py +0 -0
  189. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/__init__.py +0 -0
  190. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/binary_utils.py +0 -0
  191. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/enum_utils.py +0 -0
  192. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/get_api_client.py +0 -0
  193. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/git_proxy.py +0 -0
  194. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/host_info.py +0 -0
  195. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/ignore_utils.py +0 -0
  196. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/jwt_utils.py +0 -0
  197. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/progress_bar.py +0 -0
  198. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/scan_batch.py +0 -0
  199. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/scan_utils.py +0 -0
  200. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/shell_executor.py +0 -0
  201. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/string_utils.py +0 -0
  202. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/task_timer.py +0 -0
  203. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/trust_store.py +0 -0
  204. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/url_utils.py +0 -0
  205. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/version_checker.py +0 -0
  206. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cli/utils/yaml_utils.py +0 -0
  207. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/config.py +0 -0
  208. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/__init__.py +0 -0
  209. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/ai_security_manager_client.py +0 -0
  210. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
  211. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/auth_client.py +0 -0
  212. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/base_token_auth_client.py +0 -0
  213. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/cli_activation_client.py +0 -0
  214. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/client_creator.py +0 -0
  215. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/config.py +0 -0
  216. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/config_dev.py +0 -0
  217. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/cycode_client.py +0 -0
  218. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/cycode_client_base.py +0 -0
  219. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  220. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
  221. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/cycode_token_based_client.py +0 -0
  222. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/headers.py +0 -0
  223. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/import_sbom_client.py +0 -0
  224. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/logger.py +0 -0
  225. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/models.py +0 -0
  226. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/report_client.py +0 -0
  227. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/scan_client.py +0 -0
  228. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/cyclient/scan_config_base.py +0 -0
  229. {cycode-3.25.1.dev3 → cycode-3.25.1.dev5}/cycode/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.25.1.dev3
3
+ Version: 3.25.1.dev5
4
4
  Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
5
5
  License-Expression: MIT
6
6
  License-File: LICENCE
@@ -5,4 +5,4 @@ import time as _time
5
5
  # end-to-end scan duration from the moment the user actually triggered it.
6
6
  _BOOT_WALL: float = _time.time()
7
7
 
8
- __version__ = '3.25.1.dev3' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
8
+ __version__ = '3.25.1.dev5' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -269,21 +269,18 @@ class Codex(IDE):
269
269
  )
270
270
 
271
271
  def build_hook_response(self, decision: HookDecision) -> dict:
272
- # Codex accepts the same hook response shapes as Claude Code:
273
- # - PROMPT: empty for allow, {"decision": "block", "reason": ...} for deny
274
- # - PreToolUse: hookSpecificOutput.permissionDecision
272
+ # Codex hook response shapes:
273
+ # - allow: empty (no objection). Older Codex runtimes reject an explicit
274
+ # PreToolUse permissionDecision "allow" as unsupported and report a
275
+ # failed hook on every call, so never send it.
276
+ # - PROMPT deny: {"decision": "block", "reason": ...}
277
+ # - PreToolUse deny: hookSpecificOutput.permissionDecision
278
+ if decision.action == DecisionAction.ALLOW:
279
+ return {}
280
+
275
281
  if decision.event_type == AiHookEventType.PROMPT:
276
- if decision.action == DecisionAction.ALLOW:
277
- return {}
278
282
  return {'decision': 'block', 'reason': decision.user_message or ''}
279
283
 
280
- if decision.action == DecisionAction.ALLOW:
281
- return {
282
- 'hookSpecificOutput': {
283
- 'hookEventName': 'PreToolUse',
284
- 'permissionDecision': 'allow',
285
- }
286
- }
287
284
  return {
288
285
  'hookSpecificOutput': {
289
286
  'hookEventName': 'PreToolUse',
@@ -6,15 +6,19 @@ from typing import Optional
6
6
  import typer
7
7
 
8
8
  from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies, build_dep_tree_path
9
+ from cycode.cli.files_collector.sca.npm.workspace import (
10
+ BUN_LOCK_FILE_NAME,
11
+ MANIFEST_FILE_NAME,
12
+ is_covered_workspace_member,
13
+ )
9
14
  from cycode.cli.models import Document
10
- from cycode.cli.utils.path_utils import get_file_content
15
+ from cycode.cli.utils.path_utils import get_file_content, get_scan_roots_from_context
11
16
  from cycode.cli.utils.shell_executor import shell
12
17
  from cycode.logger import get_logger
13
18
 
14
19
  logger = get_logger('Bun Restore Dependencies')
15
20
 
16
- BUN_MANIFEST_FILE_NAME = 'package.json'
17
- BUN_LOCK_FILE_NAME = 'bun.lock'
21
+ BUN_MANIFEST_FILE_NAME = MANIFEST_FILE_NAME
18
22
 
19
23
  # Only Bun >=1.2 produces the text-based `bun.lock` lockfile that we parse.
20
24
  # Older Bun versions emit a binary `bun.lockb`, which is not supported.
@@ -61,6 +65,9 @@ class RestoreBunDependencies(BaseRestoreDependencies):
61
65
  if manifest_dir and (Path(manifest_dir) / BUN_LOCK_FILE_NAME).is_file():
62
66
  return True
63
67
 
68
+ if is_covered_workspace_member(manifest_dir, document.path, get_scan_roots_from_context(self.ctx)):
69
+ return False
70
+
64
71
  return _indicates_bun(document.content)
65
72
 
66
73
  def _is_supported_bun_version(self) -> bool:
@@ -4,6 +4,7 @@ from typing import Optional
4
4
  import typer
5
5
 
6
6
  from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies, build_dep_tree_path
7
+ from cycode.cli.files_collector.sca.npm.workspace import DENO_LOCK_FILE_NAME
7
8
  from cycode.cli.models import Document
8
9
  from cycode.cli.utils.path_utils import get_file_content
9
10
  from cycode.logger import get_logger
@@ -11,7 +12,6 @@ from cycode.logger import get_logger
11
12
  logger = get_logger('Deno Restore Dependencies')
12
13
 
13
14
  DENO_MANIFEST_FILE_NAMES = ('deno.json', 'deno.jsonc')
14
- DENO_LOCK_FILE_NAME = 'deno.lock'
15
15
 
16
16
 
17
17
  class RestoreDenoDependencies(BaseRestoreDependencies):
@@ -3,15 +3,25 @@ from pathlib import Path
3
3
  import typer
4
4
 
5
5
  from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies
6
+ from cycode.cli.files_collector.sca.npm.workspace import (
7
+ BUN_LOCK_FILE_NAME,
8
+ DENO_LOCK_FILE_NAME,
9
+ MANIFEST_FILE_NAME,
10
+ NPM_LOCK_FILE_NAME,
11
+ NPM_SHRINKWRAP_FILE_NAME,
12
+ PNPM_LOCK_FILE_NAME,
13
+ YARN_LOCK_FILE_NAME,
14
+ is_covered_workspace_member,
15
+ )
6
16
  from cycode.cli.models import Document
17
+ from cycode.cli.utils.path_utils import get_scan_roots_from_context
7
18
  from cycode.logger import get_logger
8
19
 
9
20
  logger = get_logger('NPM Restore Dependencies')
10
21
 
11
- NPM_MANIFEST_FILE_NAME = 'package.json'
12
- NPM_LOCK_FILE_NAME = 'package-lock.json'
22
+ NPM_MANIFEST_FILE_NAME = MANIFEST_FILE_NAME
13
23
  # These lockfiles indicate another package manager owns the project — NPM should not run
14
- _ALTERNATIVE_LOCK_FILES = ('yarn.lock', 'pnpm-lock.yaml', 'deno.lock', 'bun.lock')
24
+ _ALTERNATIVE_LOCK_FILES = (YARN_LOCK_FILE_NAME, PNPM_LOCK_FILE_NAME, DENO_LOCK_FILE_NAME, BUN_LOCK_FILE_NAME)
15
25
 
16
26
 
17
27
  class RestoreNpmDependencies(BaseRestoreDependencies):
@@ -24,6 +34,9 @@ class RestoreNpmDependencies(BaseRestoreDependencies):
24
34
  Yarn and pnpm projects are handled by their dedicated handlers, which run before
25
35
  this one in the handler list. This handler is the npm fallback.
26
36
 
37
+ A manifest is also declined when a lockfile further up already resolves it, whichever
38
+ package manager wrote that lockfile; see the workspace package for how that is decided.
39
+
27
40
  NOTE: this guard only excludes a project when an alternative lockfile is *physically
28
41
  present on disk*. It does not inspect the `packageManager`/`engines` signal in
29
42
  package.json. So a project that declares e.g. `packageManager: "bun@..."` (or pnpm)
@@ -37,16 +50,18 @@ class RestoreNpmDependencies(BaseRestoreDependencies):
37
50
  return False
38
51
 
39
52
  manifest_dir = self.get_manifest_dir(document)
40
- if manifest_dir:
41
- for lock_file in _ALTERNATIVE_LOCK_FILES:
42
- if (Path(manifest_dir) / lock_file).is_file():
43
- logger.debug(
44
- 'Skipping npm restore: alternative lockfile detected, %s',
45
- {'path': document.path, 'lockfile': lock_file},
46
- )
47
- return False
53
+ if not manifest_dir:
54
+ return True
55
+
56
+ for lock_file in _ALTERNATIVE_LOCK_FILES:
57
+ if (Path(manifest_dir) / lock_file).is_file():
58
+ logger.debug(
59
+ 'Skipping npm restore: alternative lockfile detected, %s',
60
+ {'path': document.path, 'lockfile': lock_file},
61
+ )
62
+ return False
48
63
 
49
- return True
64
+ return not is_covered_workspace_member(manifest_dir, document.path, get_scan_roots_from_context(self.ctx))
50
65
 
51
66
  def get_commands(self, manifest_file_path: str) -> list[list[str]]:
52
67
  return [
@@ -65,7 +80,11 @@ class RestoreNpmDependencies(BaseRestoreDependencies):
65
80
  return NPM_LOCK_FILE_NAME
66
81
 
67
82
  def get_lock_file_names(self) -> list[str]:
68
- return [NPM_LOCK_FILE_NAME]
83
+ return [NPM_LOCK_FILE_NAME, NPM_SHRINKWRAP_FILE_NAME]
84
+
85
+ def get_restored_lock_file_name(self, restore_file_path: str) -> str:
86
+ name = Path(restore_file_path).name
87
+ return name if name in self.get_lock_file_names() else self.get_lock_file_name()
69
88
 
70
89
  @staticmethod
71
90
  def prepare_manifest_file_path_for_command(manifest_file_path: str) -> str:
@@ -5,14 +5,18 @@ from typing import Optional
5
5
  import typer
6
6
 
7
7
  from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies, build_dep_tree_path
8
+ from cycode.cli.files_collector.sca.npm.workspace import (
9
+ MANIFEST_FILE_NAME,
10
+ PNPM_LOCK_FILE_NAME,
11
+ is_covered_workspace_member,
12
+ )
8
13
  from cycode.cli.models import Document
9
- from cycode.cli.utils.path_utils import get_file_content
14
+ from cycode.cli.utils.path_utils import get_file_content, get_scan_roots_from_context
10
15
  from cycode.logger import get_logger
11
16
 
12
17
  logger = get_logger('Pnpm Restore Dependencies')
13
18
 
14
- PNPM_MANIFEST_FILE_NAME = 'package.json'
15
- PNPM_LOCK_FILE_NAME = 'pnpm-lock.yaml'
19
+ PNPM_MANIFEST_FILE_NAME = MANIFEST_FILE_NAME
16
20
 
17
21
 
18
22
  def _indicates_pnpm(package_json_content: Optional[str]) -> bool:
@@ -44,6 +48,9 @@ class RestorePnpmDependencies(BaseRestoreDependencies):
44
48
  if manifest_dir and (Path(manifest_dir) / PNPM_LOCK_FILE_NAME).is_file():
45
49
  return True
46
50
 
51
+ if is_covered_workspace_member(manifest_dir, document.path, get_scan_roots_from_context(self.ctx)):
52
+ return False
53
+
47
54
  return _indicates_pnpm(document.content)
48
55
 
49
56
  def try_restore_dependencies(self, document: Document) -> Optional[Document]:
@@ -5,14 +5,18 @@ from typing import Optional
5
5
  import typer
6
6
 
7
7
  from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies, build_dep_tree_path
8
+ from cycode.cli.files_collector.sca.npm.workspace import (
9
+ MANIFEST_FILE_NAME,
10
+ YARN_LOCK_FILE_NAME,
11
+ is_covered_workspace_member,
12
+ )
8
13
  from cycode.cli.models import Document
9
- from cycode.cli.utils.path_utils import get_file_content
14
+ from cycode.cli.utils.path_utils import get_file_content, get_scan_roots_from_context
10
15
  from cycode.logger import get_logger
11
16
 
12
17
  logger = get_logger('Yarn Restore Dependencies')
13
18
 
14
- YARN_MANIFEST_FILE_NAME = 'package.json'
15
- YARN_LOCK_FILE_NAME = 'yarn.lock'
19
+ YARN_MANIFEST_FILE_NAME = MANIFEST_FILE_NAME
16
20
 
17
21
 
18
22
  def _indicates_yarn(package_json_content: Optional[str]) -> bool:
@@ -44,6 +48,9 @@ class RestoreYarnDependencies(BaseRestoreDependencies):
44
48
  if manifest_dir and (Path(manifest_dir) / YARN_LOCK_FILE_NAME).is_file():
45
49
  return True
46
50
 
51
+ if is_covered_workspace_member(manifest_dir, document.path, get_scan_roots_from_context(self.ctx)):
52
+ return False
53
+
47
54
  return _indicates_yarn(document.content)
48
55
 
49
56
  def try_restore_dependencies(self, document: Document) -> Optional[Document]:
@@ -0,0 +1,49 @@
1
+ """Deciding whether a lockfile above a manifest already resolves that manifest's dependencies.
2
+
3
+ The lockfile is the authority wherever it can name its members; the manifest's workspaces globs
4
+ are a fallback for the formats that cannot. See resolvers.py for the per-format readers.
5
+ """
6
+
7
+ from cycode.cli.files_collector.sca.npm.workspace import coverage as _coverage
8
+ from cycode.cli.files_collector.sca.npm.workspace import globs as _globs
9
+ from cycode.cli.files_collector.sca.npm.workspace import resolvers as _resolvers
10
+ from cycode.cli.files_collector.sca.npm.workspace.coverage import (
11
+ WorkspaceCoverage,
12
+ find_covering_workspace,
13
+ is_covered_workspace_member,
14
+ )
15
+ from cycode.cli.files_collector.sca.npm.workspace.names import (
16
+ BUN_BINARY_LOCK_FILE_NAME,
17
+ BUN_LOCK_FILE_NAME,
18
+ DENO_LOCK_FILE_NAME,
19
+ MANIFEST_FILE_NAME,
20
+ NPM_LOCK_FILE_NAME,
21
+ NPM_SHRINKWRAP_FILE_NAME,
22
+ PNPM_LOCK_FILE_NAME,
23
+ YARN_LOCK_FILE_NAME,
24
+ )
25
+ from cycode.cli.files_collector.sca.npm.workspace.resolvers import MEMBER_RESOLVERS
26
+
27
+
28
+ def clear_cache() -> None:
29
+ """Drop every memo, so one scan never inherits another scan's view of the filesystem."""
30
+ _resolvers.clear_cache()
31
+ _globs.clear_cache()
32
+ _coverage.clear_cache()
33
+
34
+
35
+ __all__ = [
36
+ 'BUN_BINARY_LOCK_FILE_NAME',
37
+ 'BUN_LOCK_FILE_NAME',
38
+ 'DENO_LOCK_FILE_NAME',
39
+ 'MANIFEST_FILE_NAME',
40
+ 'MEMBER_RESOLVERS',
41
+ 'NPM_LOCK_FILE_NAME',
42
+ 'NPM_SHRINKWRAP_FILE_NAME',
43
+ 'PNPM_LOCK_FILE_NAME',
44
+ 'YARN_LOCK_FILE_NAME',
45
+ 'WorkspaceCoverage',
46
+ 'clear_cache',
47
+ 'find_covering_workspace',
48
+ 'is_covered_workspace_member',
49
+ ]
@@ -0,0 +1,142 @@
1
+ """Walking up from a manifest to the lockfile that already resolves it."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, NamedTuple, Optional
6
+
7
+ from cycode.cli.files_collector.sca.npm.workspace.files import logger, resolved_path
8
+ from cycode.cli.files_collector.sca.npm.workspace.globs import declares_workspace_member
9
+ from cycode.cli.files_collector.sca.npm.workspace.resolvers import MEMBER_RESOLVERS
10
+ from cycode.cli.utils.path_utils import is_sub_path
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterator
14
+
15
+ _GIT_DIR_NAME = '.git'
16
+
17
+ _reported_unscanned_roots: set = set()
18
+
19
+
20
+ def clear_cache() -> None:
21
+ _reported_unscanned_roots.clear()
22
+
23
+
24
+ class WorkspaceCoverage(NamedTuple):
25
+ package_manager: str
26
+ lock_file: Path
27
+
28
+
29
+ def _scan_root_directories(scan_roots: tuple) -> list:
30
+ """The directory each scanned path stands for; scanning a file scans its directory."""
31
+ directories = []
32
+ for scan_root in scan_roots:
33
+ resolved = resolved_path(scan_root)
34
+ if os.path.isfile(resolved):
35
+ resolved = os.path.dirname(resolved)
36
+
37
+ if resolved:
38
+ directories.append(resolved)
39
+
40
+ return directories
41
+
42
+
43
+ def _containing_scan_roots(manifest_dir: Path, scan_roots: tuple) -> list:
44
+ resolved_manifest_dir = resolved_path(manifest_dir)
45
+ return [
46
+ Path(directory)
47
+ for directory in _scan_root_directories(scan_roots)
48
+ if is_sub_path(directory, resolved_manifest_dir)
49
+ ]
50
+
51
+
52
+ def _resolve_walk_boundary(manifest_dir: Path, scan_roots: tuple) -> Optional[Path]:
53
+ for root_dir in manifest_dir.parents:
54
+ if (root_dir / _GIT_DIR_NAME).exists():
55
+ return root_dir
56
+
57
+ containing = _containing_scan_roots(manifest_dir, scan_roots)
58
+ if containing:
59
+ return min(containing, key=lambda scan_root: len(scan_root.parts))
60
+
61
+ if scan_roots:
62
+ return manifest_dir
63
+
64
+ return None
65
+
66
+
67
+ def _workspace_root_candidates(manifest_dir: Path, scan_roots: tuple) -> 'Iterator[Path]':
68
+ boundary = _resolve_walk_boundary(manifest_dir, scan_roots)
69
+ resolved_boundary = resolved_path(boundary) if boundary is not None else None
70
+ if resolved_boundary == resolved_path(manifest_dir):
71
+ return
72
+
73
+ for root_dir in manifest_dir.parents:
74
+ yield root_dir
75
+ if resolved_boundary is not None and resolved_path(root_dir) == resolved_boundary:
76
+ return
77
+
78
+
79
+ def _find_covering_workspace(manifest_dir: Path, scan_roots: tuple) -> Optional[WorkspaceCoverage]:
80
+ for root_dir in _workspace_root_candidates(manifest_dir, scan_roots):
81
+ member_path = manifest_dir.relative_to(root_dir).as_posix()
82
+
83
+ for resolver in MEMBER_RESOLVERS:
84
+ for lock_file_name in resolver.lock_file_names:
85
+ lock_file = root_dir / lock_file_name
86
+ if not lock_file.is_file():
87
+ continue
88
+
89
+ member_names = resolver.resolve(lock_file)
90
+ if member_names is not None:
91
+ if member_path in member_names:
92
+ return WorkspaceCoverage(resolver.package_manager, lock_file)
93
+ continue
94
+
95
+ if resolver.may_use_workspace_globs and declares_workspace_member(root_dir, member_path):
96
+ return WorkspaceCoverage(resolver.package_manager, lock_file)
97
+
98
+ return None
99
+
100
+
101
+ def find_covering_workspace(manifest_dir: Optional[str], scan_roots: tuple = ()) -> Optional[WorkspaceCoverage]:
102
+ if not manifest_dir:
103
+ return None
104
+
105
+ return _find_covering_workspace(Path(manifest_dir), scan_roots)
106
+
107
+
108
+ def _is_inside_scanned_paths(scan_roots: tuple, root_dir: Path) -> bool:
109
+ directories = _scan_root_directories(scan_roots)
110
+ if not directories:
111
+ logger.debug('No scanned paths in context; treating the workspace root as scanned, %s', {'root': str(root_dir)})
112
+ return True
113
+
114
+ resolved_root_dir = resolved_path(root_dir)
115
+ return any(is_sub_path(directory, resolved_root_dir) for directory in directories)
116
+
117
+
118
+ def is_covered_workspace_member(manifest_dir: Optional[str], document_path: str, scan_roots: tuple = ()) -> bool:
119
+ coverage = find_covering_workspace(manifest_dir, scan_roots)
120
+ if coverage is None:
121
+ return False
122
+
123
+ details = {
124
+ 'path': document_path,
125
+ 'root_lockfile': str(coverage.lock_file),
126
+ 'workspace': coverage.package_manager,
127
+ }
128
+ if _is_inside_scanned_paths(scan_roots, coverage.lock_file.parent):
129
+ logger.debug('Skipping restore: the workspace root lockfile already covers this member, %s', details)
130
+ return True
131
+
132
+ report_key = (document_path, str(coverage.lock_file))
133
+ if report_key not in _reported_unscanned_roots:
134
+ _reported_unscanned_roots.add(report_key)
135
+ logger.warning(
136
+ 'The workspace root lockfile is outside the scanned path and will not be collected, '
137
+ 'so this member is restored on its own. Scan the workspace root for the versions it '
138
+ 'actually installs, %s',
139
+ details,
140
+ )
141
+
142
+ return False
@@ -0,0 +1,38 @@
1
+ """Filesystem access shared by the readers: identity for caching, and tolerant parsing."""
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from cycode.cli.utils.path_utils import get_absolute_path
9
+ from cycode.logger import get_logger
10
+
11
+ logger = get_logger('SCA NPM Workspace')
12
+
13
+ FileStamp = tuple[str, int, int]
14
+
15
+
16
+ def resolved_path(path: object) -> str:
17
+ return os.path.realpath(get_absolute_path(str(path)))
18
+
19
+
20
+ def file_stamp(path: Path) -> Optional[FileStamp]:
21
+ try:
22
+ stat_result = path.stat()
23
+ except OSError:
24
+ return None
25
+
26
+ return str(path), stat_result.st_mtime_ns, stat_result.st_size
27
+
28
+
29
+ def read_json_object(path: Path) -> Optional[dict]:
30
+ try:
31
+ content = json.loads(path.read_text(encoding='UTF-8'))
32
+ except FileNotFoundError:
33
+ return None
34
+ except (OSError, ValueError) as e:
35
+ logger.debug('Could not read an npm workspace file, %s', {'path': str(path), 'error': e})
36
+ return None
37
+
38
+ return content if isinstance(content, dict) else None
@@ -0,0 +1,116 @@
1
+ """Matching a member against the manifest's workspaces globs.
2
+
3
+ Only reached for lockfiles that cannot name their own members: classic yarn.lock and the
4
+ binary bun.lockb. Every other format answers from the lockfile itself.
5
+ """
6
+
7
+ import fnmatch
8
+ from functools import lru_cache
9
+ from pathlib import Path
10
+ from typing import NamedTuple
11
+
12
+ from cycode.cli.files_collector.sca.npm.workspace.files import FileStamp, file_stamp, read_json_object
13
+ from cycode.cli.files_collector.sca.npm.workspace.names import MANIFEST_FILE_NAME
14
+
15
+ _MANIFEST_WORKSPACES_SECTION = 'workspaces'
16
+ _MANIFEST_WORKSPACE_PACKAGES_SECTION = 'packages'
17
+ _NEGATION_PREFIX = '!'
18
+ _GLOBSTAR = '**'
19
+ _SEGMENT_MATCH_CACHE_SIZE = 4096
20
+
21
+
22
+ class _WorkspacePatterns(NamedTuple):
23
+ included: tuple[str, ...]
24
+ excluded: tuple[str, ...]
25
+
26
+
27
+ _EMPTY_WORKSPACE_PATTERNS = _WorkspacePatterns((), ())
28
+
29
+ _workspace_patterns_cache: dict[FileStamp, _WorkspacePatterns] = {}
30
+
31
+
32
+ def clear_cache() -> None:
33
+ _workspace_patterns_cache.clear()
34
+
35
+
36
+ def _matches_workspace_pattern(pattern: str, member_path: str) -> bool:
37
+ """Match a workspace glob against a member path, one path segment at a time.
38
+
39
+ fnmatch is wrong for a whole path because its * crosses a separator, but inside one
40
+ segment there is no separator, so it is exactly right there - and it brings character
41
+ classes with it. Only ** needs handling here, because only ** spans segments.
42
+ """
43
+ return _match_segments(tuple(pattern.split('/')), tuple(member_path.split('/')))
44
+
45
+
46
+ @lru_cache(maxsize=_SEGMENT_MATCH_CACHE_SIZE)
47
+ def _match_segments(pattern_segments: tuple, path_segments: tuple) -> bool:
48
+ """Memoised so that several ** in one pattern cannot make this exponential.
49
+
50
+ Each ** tries every split of the remaining path, so without memoisation a pattern such as
51
+ **/**/**/x against a deep tree multiplies out. The arguments are plain tuples of strings,
52
+ so a result can never go stale.
53
+ """
54
+ if not pattern_segments:
55
+ return not path_segments
56
+
57
+ head, rest = pattern_segments[0], pattern_segments[1:]
58
+ if head == _GLOBSTAR:
59
+ # ** spans zero or more segments, and spanning zero is what makes dir/** match dir
60
+ return any(_match_segments(rest, path_segments[index:]) for index in range(len(path_segments) + 1))
61
+
62
+ if not path_segments:
63
+ return False
64
+
65
+ return fnmatch.fnmatchcase(path_segments[0], head) and _match_segments(rest, path_segments[1:])
66
+
67
+
68
+ def _split_workspace_patterns(declared: list) -> _WorkspacePatterns:
69
+ included = []
70
+ excluded = []
71
+ for entry in declared:
72
+ stripped = entry.strip()
73
+ is_excluded = stripped.startswith(_NEGATION_PREFIX)
74
+ normalized = (stripped[1:] if is_excluded else stripped).strip()
75
+ if normalized.startswith('./'):
76
+ normalized = normalized[2:]
77
+
78
+ normalized = normalized.rstrip('/')
79
+ if not normalized:
80
+ continue
81
+
82
+ if is_excluded:
83
+ excluded.append(normalized)
84
+ else:
85
+ included.append(normalized)
86
+
87
+ return _WorkspacePatterns(tuple(included), tuple(excluded))
88
+
89
+
90
+ def _read_manifest_workspace_patterns(root_dir: Path) -> _WorkspacePatterns:
91
+ manifest = root_dir / MANIFEST_FILE_NAME
92
+ stamp = file_stamp(manifest)
93
+ if stamp is None:
94
+ return _EMPTY_WORKSPACE_PATTERNS
95
+
96
+ cached = _workspace_patterns_cache.get(stamp)
97
+ if cached is not None:
98
+ return cached
99
+
100
+ content = read_json_object(manifest)
101
+ workspaces = content.get(_MANIFEST_WORKSPACES_SECTION) if content is not None else None
102
+ if isinstance(workspaces, dict):
103
+ workspaces = workspaces.get(_MANIFEST_WORKSPACE_PACKAGES_SECTION)
104
+
105
+ declared = [entry for entry in workspaces if isinstance(entry, str)] if isinstance(workspaces, list) else []
106
+ patterns = _split_workspace_patterns(declared)
107
+ _workspace_patterns_cache[stamp] = patterns
108
+ return patterns
109
+
110
+
111
+ def declares_workspace_member(root_dir: Path, member_path: str) -> bool:
112
+ patterns = _read_manifest_workspace_patterns(root_dir)
113
+ if not any(_matches_workspace_pattern(pattern, member_path) for pattern in patterns.included):
114
+ return False
115
+
116
+ return not any(_matches_workspace_pattern(pattern, member_path) for pattern in patterns.excluded)
@@ -0,0 +1,16 @@
1
+ """File names and package-manager identifiers shared across the workspace package."""
2
+
3
+ MANIFEST_FILE_NAME = 'package.json'
4
+
5
+ NPM_PACKAGE_MANAGER = 'npm'
6
+ YARN_PACKAGE_MANAGER = 'yarn'
7
+ PNPM_PACKAGE_MANAGER = 'pnpm'
8
+ BUN_PACKAGE_MANAGER = 'bun'
9
+
10
+ NPM_LOCK_FILE_NAME = 'package-lock.json'
11
+ NPM_SHRINKWRAP_FILE_NAME = 'npm-shrinkwrap.json'
12
+ YARN_LOCK_FILE_NAME = 'yarn.lock'
13
+ PNPM_LOCK_FILE_NAME = 'pnpm-lock.yaml'
14
+ BUN_LOCK_FILE_NAME = 'bun.lock'
15
+ BUN_BINARY_LOCK_FILE_NAME = 'bun.lockb'
16
+ DENO_LOCK_FILE_NAME = 'deno.lock'