sqlclean 0.0.1__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. sqlclean-0.1.1/.github/RELEASING.md +85 -0
  2. sqlclean-0.1.1/.github/demos/check-fix.gif +0 -0
  3. sqlclean-0.1.1/.github/scripts/assert_cli_wheel_tags.sh +30 -0
  4. sqlclean-0.1.1/.github/scripts/classify_changes.py +55 -0
  5. sqlclean-0.1.1/.github/scripts/release_metadata_check.py +469 -0
  6. sqlclean-0.1.1/.github/scripts/simulate_release.sh +37 -0
  7. sqlclean-0.1.1/.github/sqlclean-logo-dark.png +0 -0
  8. sqlclean-0.1.1/.github/sqlclean-logo-light.png +0 -0
  9. sqlclean-0.1.1/.github/sqlclean-logo.png +0 -0
  10. sqlclean-0.1.1/.github/workflows/publish.yml +182 -0
  11. sqlclean-0.1.1/.github/workflows/pull-request-metadata.yml +98 -0
  12. sqlclean-0.1.1/.github/workflows/release-please.yml +278 -0
  13. sqlclean-0.1.1/.github/workflows/verify.yml +216 -0
  14. sqlclean-0.1.1/.github/workflows/version-guard.yml +67 -0
  15. sqlclean-0.1.1/.github/workflows/website.yml +45 -0
  16. sqlclean-0.1.1/.gitignore +25 -0
  17. sqlclean-0.1.1/.pre-commit-config.yaml +55 -0
  18. sqlclean-0.1.1/.pre-commit-hooks.yaml +23 -0
  19. sqlclean-0.1.1/.python-version +1 -0
  20. sqlclean-0.1.1/.release-please-manifest.json +3 -0
  21. sqlclean-0.1.1/CHANGELOG.md +38 -0
  22. sqlclean-0.1.1/Cargo.lock +844 -0
  23. sqlclean-0.1.1/Cargo.toml +60 -0
  24. sqlclean-0.1.1/LICENSE +201 -0
  25. sqlclean-0.1.1/Makefile +51 -0
  26. sqlclean-0.1.1/PKG-INFO +200 -0
  27. sqlclean-0.1.1/README.md +176 -0
  28. sqlclean-0.1.1/benchmarks/linters/README.md +49 -0
  29. sqlclean-0.1.1/benchmarks/linters/bench.sh +98 -0
  30. sqlclean-0.1.1/benchmarks/linters/convert_duckshop.py +73 -0
  31. sqlclean-0.1.1/benchmarks/linters/rules/layers.py +61 -0
  32. sqlclean-0.1.1/benchmarks/linters/summarize.py +95 -0
  33. sqlclean-0.1.1/crates/sqlclean/Cargo.toml +26 -0
  34. sqlclean-0.1.1/crates/sqlclean/src/constants.rs +15 -0
  35. sqlclean-0.1.1/crates/sqlclean/src/lib.rs +9 -0
  36. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/_helpers/collection.rs +236 -0
  37. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/_helpers/mod.rs +3 -0
  38. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/_helpers/statements.rs +38 -0
  39. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/_helpers/tokens.rs +234 -0
  40. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/main/file_facts.rs +8 -0
  41. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/main/mod.rs +2 -0
  42. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/main/statement_ends.rs +6 -0
  43. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/mod.rs +8 -0
  44. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/models.rs +81 -0
  45. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/tests/helpers.rs +4 -0
  46. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/tests/test_file_facts.rs +281 -0
  47. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/tests/test_types.rs +27 -0
  48. sqlclean-0.1.1/crates/sqlclean/src/sql_facts/tests.rs +6 -0
  49. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/additional.rs +1873 -0
  50. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/engine.rs +1978 -0
  51. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/formatter.rs +352 -0
  52. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/formatter_syntax.rs +459 -0
  53. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/inline_relations.rs +223 -0
  54. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/join_predicates.rs +694 -0
  55. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/mod.rs +10 -0
  56. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/parsing.rs +44 -0
  57. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/printed_layout.rs +1079 -0
  58. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/terminal_shape.rs +303 -0
  59. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/token_layout.rs +815 -0
  60. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/constants.rs +61 -0
  61. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/batch_engine.rs +19 -0
  62. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/catalogue.rs +6 -0
  63. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/cross_join_subject.rs +15 -0
  64. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/default_rules.rs +6 -0
  65. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/engine.rs +5 -0
  66. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/fill_finding_path.rs +9 -0
  67. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/format_requests.rs +8 -0
  68. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/formatter.rs +5 -0
  69. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/lint.rs +8 -0
  70. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/mod.rs +11 -0
  71. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/parse_sql.rs +8 -0
  72. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/validate_dialect.rs +13 -0
  73. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/mod.rs +9 -0
  74. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/models.rs +191 -0
  75. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/fixtures/FORMAT_CORPUS_NOTICE.md +32 -0
  76. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/fixtures/format_corpus.json +5354 -0
  77. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/helpers.rs +304 -0
  78. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_authored_tokens.rs +416 -0
  79. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_batch_lint.rs +43 -0
  80. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_comment_formatter.rs +101 -0
  81. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_cross_joins.rs +286 -0
  82. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_empty_fixture_lint.rs +61 -0
  83. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_format_corpus.rs +61 -0
  84. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_function_call_spacing.rs +117 -0
  85. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_idempotency_reprint.rs +48 -0
  86. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_layout_rules.rs +294 -0
  87. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_line_wrap.rs +121 -0
  88. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_lint_exemptions.rs +130 -0
  89. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_set_operation_formatter.rs +59 -0
  90. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_sql_lint.rs +1969 -0
  91. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_types.rs +182 -0
  92. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_unused_table_alias.rs +317 -0
  93. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_values_formatter.rs +53 -0
  94. sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests.rs +34 -0
  95. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/cte_columns.rs +457 -0
  96. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/declared_keys.rs +50 -0
  97. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/json_parses.rs +201 -0
  98. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/keys.rs +437 -0
  99. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/literals.rs +71 -0
  100. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/ranking.rs +218 -0
  101. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/removal_safety.rs +102 -0
  102. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/setting_help.rs +26 -0
  103. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/syntax.rs +124 -0
  104. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/constants.rs +202 -0
  105. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/main/declared_relation_keys.rs +38 -0
  106. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/main/diagnostics.rs +32 -0
  107. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/main/mod.rs +2 -0
  108. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/mod.rs +27 -0
  109. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/models.rs +47 -0
  110. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/helpers.rs +116 -0
  111. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/test_json_parses.rs +282 -0
  112. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/test_quality_rules.rs +403 -0
  113. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/test_quality_scale.rs +43 -0
  114. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/test_types.rs +38 -0
  115. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests.rs +10 -0
  116. sqlclean-0.1.1/crates/sqlclean/src/sql_quality/types.rs +6 -0
  117. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/_helpers/builtin_functions.rs +151 -0
  118. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/_helpers/mod.rs +3 -0
  119. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/_helpers/reserved_keywords.rs +43 -0
  120. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/_helpers/token_context.rs +30 -0
  121. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/constants.rs +35 -0
  122. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/builtin_function_names.rs +6 -0
  123. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/canonical_tokens.rs +25 -0
  124. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/case_folding.rs +23 -0
  125. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/is_unquoted_word.rs +11 -0
  126. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/mod.rs +10 -0
  127. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/name_positions.rs +16 -0
  128. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/projection_spans.rs +41 -0
  129. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/query_fingerprint.rs +25 -0
  130. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/token_texts.rs +16 -0
  131. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/mod.rs +9 -0
  132. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/models.rs +7 -0
  133. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/bigquery.txt +99 -0
  134. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/databricks.txt +4 -0
  135. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/duckdb.txt +77 -0
  136. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/generic.txt +28 -0
  137. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/postgres.txt +80 -0
  138. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/snowflake.txt +65 -0
  139. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/tsql.txt +186 -0
  140. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/tests/test_canonical_tokens.rs +218 -0
  141. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/tests/test_types.rs +7 -0
  142. sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/tests.rs +4 -0
  143. sqlclean-0.1.1/crates/sqlclean-cli/Cargo.toml +32 -0
  144. sqlclean-0.1.1/crates/sqlclean-cli/build.rs +5 -0
  145. sqlclean-0.1.1/crates/sqlclean-cli/skill/SKILL.md +61 -0
  146. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/CONTENTS.md +39 -0
  147. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/benchmarks.md +80 -0
  148. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/check.md +170 -0
  149. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/facts.md +78 -0
  150. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/fix.md +99 -0
  151. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/format.md +77 -0
  152. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/rules.md +122 -0
  153. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/skills.md +61 -0
  154. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/configuration.md +188 -0
  155. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/custom-rules/fixes-and-options.md +160 -0
  156. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/custom-rules/overview.md +194 -0
  157. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/custom-rules/statement-facts.md +186 -0
  158. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/custom-rules/testing.md +183 -0
  159. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/embedding.md +160 -0
  160. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/formatting.md +200 -0
  161. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/index.md +83 -0
  162. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/integrations.md +116 -0
  163. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/quickstart.md +223 -0
  164. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/rules/overview.md +273 -0
  165. sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/rules/reference.md +275 -0
  166. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/_helpers/entries.rs +13 -0
  167. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/_helpers/mod.rs +1 -0
  168. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/constants.rs +4 -0
  169. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/main/cache_key.rs +14 -0
  170. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/main/mod.rs +1 -0
  171. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/mod.rs +9 -0
  172. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/models.rs +105 -0
  173. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/tests/test_result_cache.rs +128 -0
  174. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/tests/test_types.rs +17 -0
  175. sqlclean-0.1.1/crates/sqlclean-cli/src/caching/tests.rs +4 -0
  176. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/builtin.rs +88 -0
  177. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/catalogue.rs +109 -0
  178. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/custom.rs +239 -0
  179. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/exemptions.rs +97 -0
  180. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/mod.rs +6 -0
  181. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/orchestration.rs +179 -0
  182. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/selection.rs +39 -0
  183. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/constants.rs +15 -0
  184. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/main/check_sources.rs +14 -0
  185. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/main/mod.rs +2 -0
  186. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/main/rule_catalogue.rs +18 -0
  187. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/mod.rs +6 -0
  188. sqlclean-0.1.1/crates/sqlclean-cli/src/checking/models.rs +77 -0
  189. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/check_command.rs +33 -0
  190. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/diffs.rs +25 -0
  191. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/dispatch.rs +58 -0
  192. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/facts_command.rs +19 -0
  193. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/fix_command.rs +70 -0
  194. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/format_command.rs +55 -0
  195. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/mod.rs +8 -0
  196. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/rules_command.rs +77 -0
  197. sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/skills_command.rs +140 -0
  198. sqlclean-0.1.1/crates/sqlclean-cli/src/command/constants.rs +20 -0
  199. sqlclean-0.1.1/crates/sqlclean-cli/src/command/main/mod.rs +1 -0
  200. sqlclean-0.1.1/crates/sqlclean-cli/src/command/main/run.rs +10 -0
  201. sqlclean-0.1.1/crates/sqlclean-cli/src/command/mod.rs +6 -0
  202. sqlclean-0.1.1/crates/sqlclean-cli/src/command/models.rs +110 -0
  203. sqlclean-0.1.1/crates/sqlclean-cli/src/config/_helpers/file_schema.rs +47 -0
  204. sqlclean-0.1.1/crates/sqlclean-cli/src/config/_helpers/loading.rs +65 -0
  205. sqlclean-0.1.1/crates/sqlclean-cli/src/config/_helpers/mod.rs +3 -0
  206. sqlclean-0.1.1/crates/sqlclean-cli/src/config/_helpers/validation.rs +62 -0
  207. sqlclean-0.1.1/crates/sqlclean-cli/src/config/constants.rs +8 -0
  208. sqlclean-0.1.1/crates/sqlclean-cli/src/config/main/load_config.rs +10 -0
  209. sqlclean-0.1.1/crates/sqlclean-cli/src/config/main/mod.rs +1 -0
  210. sqlclean-0.1.1/crates/sqlclean-cli/src/config/mod.rs +9 -0
  211. sqlclean-0.1.1/crates/sqlclean-cli/src/config/models.rs +82 -0
  212. sqlclean-0.1.1/crates/sqlclean-cli/src/config/tests/test_load_config.rs +123 -0
  213. sqlclean-0.1.1/crates/sqlclean-cli/src/config/tests/test_types.rs +16 -0
  214. sqlclean-0.1.1/crates/sqlclean-cli/src/config/tests.rs +4 -0
  215. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/_helpers/matching.rs +60 -0
  216. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/_helpers/mod.rs +2 -0
  217. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/_helpers/walking.rs +77 -0
  218. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/main/discover_files.rs +11 -0
  219. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/main/mod.rs +2 -0
  220. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/main/path_patterns.rs +8 -0
  221. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/mod.rs +8 -0
  222. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/models.rs +11 -0
  223. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/tests/test_discover_files.rs +67 -0
  224. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/tests/test_types.rs +5 -0
  225. sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/tests.rs +4 -0
  226. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/_helpers/edit_selection.rs +56 -0
  227. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/_helpers/mod.rs +2 -0
  228. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/_helpers/passes.rs +74 -0
  229. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/constants.rs +2 -0
  230. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/main/fix_sources.rs +16 -0
  231. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/main/mod.rs +1 -0
  232. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/mod.rs +9 -0
  233. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/models.rs +14 -0
  234. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/tests/helpers.rs +27 -0
  235. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/tests/test_edit_selection.rs +37 -0
  236. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/tests/test_types.rs +5 -0
  237. sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/tests.rs +6 -0
  238. sqlclean-0.1.1/crates/sqlclean-cli/src/formatting/constants.rs +2 -0
  239. sqlclean-0.1.1/crates/sqlclean-cli/src/formatting/main/format_source.rs +65 -0
  240. sqlclean-0.1.1/crates/sqlclean-cli/src/formatting/main/mod.rs +1 -0
  241. sqlclean-0.1.1/crates/sqlclean-cli/src/formatting/mod.rs +4 -0
  242. sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/_helpers/interpreter.rs +26 -0
  243. sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/_helpers/mod.rs +2 -0
  244. sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/_helpers/process.rs +57 -0
  245. sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/constants.rs +7 -0
  246. sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/main/mod.rs +1 -0
  247. sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/main/run_host.rs +8 -0
  248. sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/mod.rs +6 -0
  249. sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/models.rs +59 -0
  250. sqlclean-0.1.1/crates/sqlclean-cli/src/lib.rs +14 -0
  251. sqlclean-0.1.1/crates/sqlclean-cli/src/main.rs +9 -0
  252. sqlclean-0.1.1/crates/sqlclean-cli/src/positions/main/apply_edits.rs +18 -0
  253. sqlclean-0.1.1/crates/sqlclean-cli/src/positions/main/mod.rs +1 -0
  254. sqlclean-0.1.1/crates/sqlclean-cli/src/positions/mod.rs +7 -0
  255. sqlclean-0.1.1/crates/sqlclean-cli/src/positions/models.rs +37 -0
  256. sqlclean-0.1.1/crates/sqlclean-cli/src/positions/tests/test_positions.rs +78 -0
  257. sqlclean-0.1.1/crates/sqlclean-cli/src/positions/tests/test_types.rs +12 -0
  258. sqlclean-0.1.1/crates/sqlclean-cli/src/positions/tests.rs +4 -0
  259. sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/_helpers/location.rs +32 -0
  260. sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/_helpers/machine.rs +68 -0
  261. sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/_helpers/mod.rs +3 -0
  262. sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/_helpers/text.rs +110 -0
  263. sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/main/mod.rs +1 -0
  264. sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/main/render_findings.rs +20 -0
  265. sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/mod.rs +5 -0
  266. sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/models.rs +24 -0
  267. sqlclean-0.1.1/fensu.toml +53 -0
  268. sqlclean-0.1.1/packages/sqlclean-cli/README.md +3 -0
  269. sqlclean-0.1.1/packages/sqlclean-cli/pyproject.toml +37 -0
  270. sqlclean-0.1.1/pyproject.toml +89 -0
  271. sqlclean-0.1.1/release-please-config.json +76 -0
  272. sqlclean-0.1.1/scripts/__init__.py +0 -0
  273. sqlclean-0.1.1/scripts/check_docs_skill.py +15 -0
  274. sqlclean-0.1.1/scripts/docs_skill/__init__.py +0 -0
  275. sqlclean-0.1.1/scripts/docs_skill/_helpers/__init__.py +0 -0
  276. sqlclean-0.1.1/scripts/docs_skill/_helpers/mdx.py +85 -0
  277. sqlclean-0.1.1/scripts/docs_skill/_helpers/navigation.py +65 -0
  278. sqlclean-0.1.1/scripts/docs_skill/_helpers/output.py +47 -0
  279. sqlclean-0.1.1/scripts/docs_skill/_helpers/render.py +110 -0
  280. sqlclean-0.1.1/scripts/docs_skill/constants.py +18 -0
  281. sqlclean-0.1.1/scripts/docs_skill/main/__init__.py +0 -0
  282. sqlclean-0.1.1/scripts/docs_skill/main/check.py +41 -0
  283. sqlclean-0.1.1/scripts/docs_skill/main/generate.py +30 -0
  284. sqlclean-0.1.1/scripts/docs_skill/models.py +21 -0
  285. sqlclean-0.1.1/scripts/generate_docs_skill.py +15 -0
  286. sqlclean-0.1.1/src/sqlclean/__init__.py +39 -0
  287. sqlclean-0.1.1/src/sqlclean/hosting/__init__.py +0 -0
  288. sqlclean-0.1.1/src/sqlclean/hosting/_helpers/__init__.py +0 -0
  289. sqlclean-0.1.1/src/sqlclean/hosting/_helpers/evaluation.py +109 -0
  290. sqlclean-0.1.1/src/sqlclean/hosting/_helpers/loading.py +64 -0
  291. sqlclean-0.1.1/src/sqlclean/hosting/_helpers/requests.py +116 -0
  292. sqlclean-0.1.1/src/sqlclean/hosting/_helpers/sql_files.py +126 -0
  293. sqlclean-0.1.1/src/sqlclean/hosting/constants.py +9 -0
  294. sqlclean-0.1.1/src/sqlclean/hosting/exceptions.py +11 -0
  295. sqlclean-0.1.1/src/sqlclean/hosting/main/__init__.py +0 -0
  296. sqlclean-0.1.1/src/sqlclean/hosting/main/build_sql_file.py +12 -0
  297. sqlclean-0.1.1/src/sqlclean/hosting/main/evaluate_definition.py +17 -0
  298. sqlclean-0.1.1/src/sqlclean/hosting/main/serve.py +15 -0
  299. sqlclean-0.1.1/src/sqlclean/py.typed +0 -0
  300. sqlclean-0.1.1/src/sqlclean/rule_testing/__init__.py +0 -0
  301. sqlclean-0.1.1/src/sqlclean/rule_testing/_helpers/__init__.py +0 -0
  302. sqlclean-0.1.1/src/sqlclean/rule_testing/_helpers/harness.py +66 -0
  303. sqlclean-0.1.1/src/sqlclean/rule_testing/_helpers/native_facts.py +38 -0
  304. sqlclean-0.1.1/src/sqlclean/rule_testing/constants.py +6 -0
  305. sqlclean-0.1.1/src/sqlclean/rule_testing/exceptions.py +7 -0
  306. sqlclean-0.1.1/src/sqlclean/rule_testing/main/__init__.py +0 -0
  307. sqlclean-0.1.1/src/sqlclean/rule_testing/main/evaluate_rule.py +11 -0
  308. sqlclean-0.1.1/src/sqlclean/rule_testing/models.py +42 -0
  309. sqlclean-0.1.1/src/sqlclean/rules/__init__.py +0 -0
  310. sqlclean-0.1.1/src/sqlclean/rules/_helpers/__init__.py +0 -0
  311. sqlclean-0.1.1/src/sqlclean/rules/_helpers/definition.py +107 -0
  312. sqlclean-0.1.1/src/sqlclean/rules/classes/__init__.py +0 -0
  313. sqlclean-0.1.1/src/sqlclean/rules/classes/rule_context.py +99 -0
  314. sqlclean-0.1.1/src/sqlclean/rules/classes/rule_option.py +128 -0
  315. sqlclean-0.1.1/src/sqlclean/rules/constants.py +12 -0
  316. sqlclean-0.1.1/src/sqlclean/rules/exceptions.py +17 -0
  317. sqlclean-0.1.1/src/sqlclean/rules/main/__init__.py +0 -0
  318. sqlclean-0.1.1/src/sqlclean/rules/main/define.py +30 -0
  319. sqlclean-0.1.1/src/sqlclean/rules/models.py +194 -0
  320. sqlclean-0.1.1/src/sqlclean/rules/types.py +33 -0
  321. sqlclean-0.1.1/src/sqlclean/testing.py +6 -0
  322. sqlclean-0.1.1/tests/__init__.py +0 -0
  323. sqlclean-0.1.1/tests/e2e/__init__.py +0 -0
  324. sqlclean-0.1.1/tests/e2e/src/__init__.py +0 -0
  325. sqlclean-0.1.1/tests/e2e/src/sqlclean/__init__.py +0 -0
  326. sqlclean-0.1.1/tests/e2e/src/sqlclean/hosting/__init__.py +0 -0
  327. sqlclean-0.1.1/tests/e2e/src/sqlclean/hosting/_test_types.py +93 -0
  328. sqlclean-0.1.1/tests/e2e/src/sqlclean/hosting/helpers.py +45 -0
  329. sqlclean-0.1.1/tests/e2e/src/sqlclean/hosting/test_sqlclean_command.py +451 -0
  330. sqlclean-0.1.1/tests/integration/__init__.py +0 -0
  331. sqlclean-0.1.1/tests/integration/src/__init__.py +0 -0
  332. sqlclean-0.1.1/tests/integration/src/sqlclean/__init__.py +0 -0
  333. sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/__init__.py +0 -0
  334. sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/_test_types.py +37 -0
  335. sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/helpers.py +124 -0
  336. sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/test_evaluate_rule.py +160 -0
  337. sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/test_readme_rule.py +87 -0
  338. sqlclean-0.1.1/tests/unit/__init__.py +0 -0
  339. sqlclean-0.1.1/tests/unit/src/__init__.py +0 -0
  340. sqlclean-0.1.1/tests/unit/src/sqlclean/__init__.py +0 -0
  341. sqlclean-0.1.1/tests/unit/src/sqlclean/rules/__init__.py +0 -0
  342. sqlclean-0.1.1/tests/unit/src/sqlclean/rules/_test_types.py +14 -0
  343. sqlclean-0.1.1/tests/unit/src/sqlclean/rules/helpers.py +12 -0
  344. sqlclean-0.1.1/tests/unit/src/sqlclean/rules/test_rule_definition.py +69 -0
  345. sqlclean-0.1.1/uv.lock +833 -0
  346. sqlclean-0.1.1/website/.gitignore +3 -0
  347. sqlclean-0.1.1/website/README.md +33 -0
  348. sqlclean-0.1.1/website/STYLE.md +54 -0
  349. sqlclean-0.1.1/website/astro.config.mjs +83 -0
  350. sqlclean-0.1.1/website/package-lock.json +6078 -0
  351. sqlclean-0.1.1/website/package.json +20 -0
  352. sqlclean-0.1.1/website/public/assets/icon.png +0 -0
  353. sqlclean-0.1.1/website/public/assets/logo-dark.png +0 -0
  354. sqlclean-0.1.1/website/public/assets/logo-light.png +0 -0
  355. sqlclean-0.1.1/website/public/assets/og-icon.png +0 -0
  356. sqlclean-0.1.1/website/public/assets/og-sqlclean-docs.png +0 -0
  357. sqlclean-0.1.1/website/public/assets/og-sqlclean.png +0 -0
  358. sqlclean-0.1.1/website/public/favicon.png +0 -0
  359. sqlclean-0.1.1/website/public/favicon.svg +5 -0
  360. sqlclean-0.1.1/website/public/fonts/LICENSE-DejaVu.txt +78 -0
  361. sqlclean-0.1.1/website/public/fonts/LICENSE-IBM-Plex-Mono.txt +93 -0
  362. sqlclean-0.1.1/website/public/fonts/LICENSE-Inter.txt +93 -0
  363. sqlclean-0.1.1/website/public/fonts/dejavu-sans-mono-diagram.woff2 +0 -0
  364. sqlclean-0.1.1/website/public/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  365. sqlclean-0.1.1/website/public/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  366. sqlclean-0.1.1/website/public/fonts/inter-latin-wght-normal.woff2 +0 -0
  367. sqlclean-0.1.1/website/public/index.html +494 -0
  368. sqlclean-0.1.1/website/public/styles.css +1011 -0
  369. sqlclean-0.1.1/website/recordings/.gitignore +2 -0
  370. sqlclean-0.1.1/website/recordings/README.md +33 -0
  371. sqlclean-0.1.1/website/recordings/prepare.sh +74 -0
  372. sqlclean-0.1.1/website/recordings/record.sh +14 -0
  373. sqlclean-0.1.1/website/recordings/tapes/check-fix.tape +45 -0
  374. sqlclean-0.1.1/website/recordings/tapes/common.tape +13 -0
  375. sqlclean-0.1.1/website/scripts/check_links.py +65 -0
  376. sqlclean-0.1.1/website/src/assets/logo-dark.png +0 -0
  377. sqlclean-0.1.1/website/src/assets/logo-light.png +0 -0
  378. sqlclean-0.1.1/website/src/code-themes.mjs +69 -0
  379. sqlclean-0.1.1/website/src/components/Header.astro +63 -0
  380. sqlclean-0.1.1/website/src/components/PageFrame.astro +135 -0
  381. sqlclean-0.1.1/website/src/components/PageTitle.astro +54 -0
  382. sqlclean-0.1.1/website/src/components/Pagination.astro +61 -0
  383. sqlclean-0.1.1/website/src/components/SiteTitle.astro +58 -0
  384. sqlclean-0.1.1/website/src/components/ThemeSelect.astro +53 -0
  385. sqlclean-0.1.1/website/src/components/TwoColumnContent.astro +43 -0
  386. sqlclean-0.1.1/website/src/components/docs/Card.astro +65 -0
  387. sqlclean-0.1.1/website/src/components/docs/CardGroup.astro +8 -0
  388. sqlclean-0.1.1/website/src/components/docs/Frame.astro +1 -0
  389. sqlclean-0.1.1/website/src/components/docs/Note.astro +8 -0
  390. sqlclean-0.1.1/website/src/components/docs/Warning.astro +8 -0
  391. sqlclean-0.1.1/website/src/content/docs/docs/benchmarks.mdx +77 -0
  392. sqlclean-0.1.1/website/src/content/docs/docs/cli/check.mdx +160 -0
  393. sqlclean-0.1.1/website/src/content/docs/docs/cli/facts.mdx +77 -0
  394. sqlclean-0.1.1/website/src/content/docs/docs/cli/fix.mdx +98 -0
  395. sqlclean-0.1.1/website/src/content/docs/docs/cli/format.mdx +76 -0
  396. sqlclean-0.1.1/website/src/content/docs/docs/cli/rules.mdx +112 -0
  397. sqlclean-0.1.1/website/src/content/docs/docs/cli/skills.mdx +60 -0
  398. sqlclean-0.1.1/website/src/content/docs/docs/configuration.mdx +177 -0
  399. sqlclean-0.1.1/website/src/content/docs/docs/custom-rules/fixes-and-options.mdx +154 -0
  400. sqlclean-0.1.1/website/src/content/docs/docs/custom-rules/overview.mdx +196 -0
  401. sqlclean-0.1.1/website/src/content/docs/docs/custom-rules/statement-facts.mdx +169 -0
  402. sqlclean-0.1.1/website/src/content/docs/docs/custom-rules/testing.mdx +177 -0
  403. sqlclean-0.1.1/website/src/content/docs/docs/embedding.mdx +147 -0
  404. sqlclean-0.1.1/website/src/content/docs/docs/formatting.mdx +186 -0
  405. sqlclean-0.1.1/website/src/content/docs/docs/index.mdx +93 -0
  406. sqlclean-0.1.1/website/src/content/docs/docs/integrations.mdx +107 -0
  407. sqlclean-0.1.1/website/src/content/docs/docs/quickstart.mdx +211 -0
  408. sqlclean-0.1.1/website/src/content/docs/docs/rules/overview.mdx +271 -0
  409. sqlclean-0.1.1/website/src/content/docs/docs/rules/reference.mdx +262 -0
  410. sqlclean-0.1.1/website/src/content.config.ts +7 -0
  411. sqlclean-0.1.1/website/src/navigation.mjs +5 -0
  412. sqlclean-0.1.1/website/src/sidebar.json +55 -0
  413. sqlclean-0.1.1/website/src/styles/fonts.css +42 -0
  414. sqlclean-0.1.1/website/src/styles/sqlclean.css +446 -0
  415. sqlclean-0.0.1/.gitignore +0 -3
  416. sqlclean-0.0.1/LICENSE +0 -13
  417. sqlclean-0.0.1/PKG-INFO +0 -21
  418. sqlclean-0.0.1/README.md +0 -5
  419. sqlclean-0.0.1/pyproject.toml +0 -25
  420. sqlclean-0.0.1/src/sqlclean/__init__.py +0 -3
@@ -0,0 +1,85 @@
1
+ # Releasing
2
+
3
+ The website and the packages ship separately.
4
+
5
+ ## Website
6
+
7
+ Cloudflare Pages builds `website/` (`npm ci && npm run build`, output `dist`) from its own Git
8
+ integration. A push to `main` deploys https://sqlclean.io; every other branch deploys a preview
9
+ at `<branch>.sqlclean.pages.dev`. No workflow here deploys the site, and publishing packages never
10
+ touches it.
11
+
12
+ ## Packages
13
+
14
+ Release Please keeps a release pull request open on `main`. Merging it tags `v0.MINOR.PATCH` and
15
+ dispatches `publish.yml`, which builds the `sqlclean-cli` wheels and both sdists and publishes
16
+ `sqlclean` and `sqlclean-cli` to PyPI.
17
+
18
+ Release Please counts every Conventional Commit on `main` since the last release, whatever its
19
+ type: `docs:` and `chore:` commits bump the patch version even though their changelog sections
20
+ are hidden or not. What keeps a change out of a release is `exclude-paths` in
21
+ `release-please-config.json`. A commit whose files all sit under `website/`, `benchmarks/` or
22
+ `.github/` is ignored, so site, benchmark and workflow edits never cut a release.
23
+
24
+ Documentation edits usually do release, on purpose: `make skills` copies the docs into
25
+ `crates/sqlclean-cli/skill/`, which the `sqlclean` executable embeds for `sqlclean skills`, so the
26
+ installed skill stays in step with the docs. A homepage or styling change touches only `website/`
27
+ and does not.
28
+
29
+ Merge pull requests one at a time, each with its branch up to date with `main`. The release
30
+ workflow only trusts a release base whose tree passed Verify on the pull request that produced
31
+ it. If two pull requests merge back to back, `main` holds a combination no pull request verified,
32
+ and the release workflow stops before it refreshes the release pull request's lockfiles. A later
33
+ run that leaves the release pull request unchanged does not repair it. To recover, close the
34
+ release pull request, then merge the next up-to-date pull request; Release Please opens a fresh
35
+ release pull request from the verified tree.
36
+
37
+ `main` was rewritten after 0.1.0 to drop tool attribution trailers, with every tree unchanged, so
38
+ the `v0.1.0` tag points at the pre-rewrite commit and is not an ancestor of `main`.
39
+ `last-release-sha` in `release-please-config.json` names the rewritten 0.1.0 release commit, so
40
+ Release Please collects only the commits after it.
41
+
42
+ ## One-time setup
43
+
44
+ A repository administrator does these once, in a browser:
45
+
46
+ 1. **Release GitHub App.** Install the organization's delivery GitHub App on this repository,
47
+ with contents, pull requests, issues and actions write access. In Settings > Secrets and
48
+ variables > Actions, add the variable `RELEASE_APP_ID` (the App ID) and the secret
49
+ `RELEASE_APP_PRIVATE_KEY` (the full contents of a private key generated under the app's
50
+ Credentials). Release Please opens release pull requests and dispatches `publish.yml` with
51
+ this app's token, because events from the default `GITHUB_TOKEN` do not start workflows.
52
+ Without either setting, the Release Please run fails at its first step and names the missing
53
+ one.
54
+ 2. **PyPI trusted publishers.** For both `sqlclean` and `sqlclean-cli`, add a GitHub publisher
55
+ with owner `sqlstack-io`, repository `sqlclean`, workflow `publish.yml` and environment
56
+ `pypi`. GitHub creates the `pypi` environment the first time `publish.yml` uses it.
57
+ 3. **Cloudflare Pages.** The `sqlclean` Pages project builds `website/` from this repository.
58
+ Add `sqlclean.io` and `www.sqlclean.io` under its Custom domains.
59
+
60
+ Automation tokens, such as a coding agent's GitHub App token or `GITHUB_TOKEN`, usually cannot
61
+ read or write Actions variables, secrets or app installations, and a private key can only be
62
+ downloaded when it is generated. These steps need a person.
63
+
64
+ ## Tooling versions
65
+
66
+ `uv.lock` is only reproducible with one uv release line: a newer uv rewrites the file's format,
67
+ and the release metadata check then rejects the release pull request. Workflows pin uv 0.12.9
68
+ and `[tool.uv] required-version` in `pyproject.toml` keeps local uv on 0.12, so update both
69
+ together. `make release-rehearsal` (the Release rehearsal job in Verify) applies Release Please's
70
+ version edits on a scratch worktree, refreshes the lockfiles the same way the release workflow
71
+ does, and runs the release metadata check, so a mismatch fails a pull request rather than a
72
+ release.
73
+
74
+ ## Verify
75
+
76
+ `verify.yml` classifies a pull request by the paths it changes and runs only the suites it needs.
77
+ The `Verify` job is the one check to require: it passes when every selected suite passed.
78
+
79
+ | Change | Suites |
80
+ |--------|--------|
81
+ | Only `website/`, `benchmarks/`, root `*.md`, `.github/demos/`, images, `website.yml` | docs skill check and ruff |
82
+ | Also the bundled skill under `crates/sqlclean-cli/skill/` | plus a Linux build and the `sqlclean skills` tests |
83
+ | Anything else, including workflows, Rust, Python, tests and lockfiles | everything: `make verify`, minimum Rust, Windows, three-platform package smoke tests, the source build and the release rehearsal |
84
+
85
+ A manual or release dispatch has no pull request diff, so it runs everything.
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+ # Guard the sqlclean-cli wheel tag. The package ships one standalone Rust
3
+ # executable that links no libpython, so an interpreter-specific tag makes the
4
+ # wheel uninstallable on every other Python and silently forces a source build.
5
+ set -euo pipefail
6
+ shopt -s nullglob
7
+
8
+ directory="${1:?usage: assert_cli_wheel_tags.sh <directory>}"
9
+ wheels=("${directory}"/sqlclean_cli-*.whl)
10
+
11
+ if [ ${#wheels[@]} -eq 0 ]; then
12
+ echo "No sqlclean-cli wheel found in ${directory}."
13
+ exit 1
14
+ fi
15
+
16
+ status=0
17
+ for wheel in "${wheels[@]}"; do
18
+ name="$(basename "${wheel}")"
19
+ case "${name}" in
20
+ sqlclean_cli-*-py3-none-*.whl)
21
+ echo "ok: ${name}"
22
+ ;;
23
+ *)
24
+ echo "error: sqlclean-cli wheel must be tagged py3-none-<platform>: ${name}"
25
+ status=1
26
+ ;;
27
+ esac
28
+ done
29
+
30
+ exit "${status}"
@@ -0,0 +1,55 @@
1
+ """Decide which Verify suites a change needs from the paths it touches, one per line on stdin.
2
+
3
+ Prints `full=` and `skills=` lines for GITHUB_OUTPUT:
4
+
5
+ - Documentation, website, benchmark and image changes need only the light checks.
6
+ - Changes confined to the bundled agent skill also rebuild the executable on Linux and run the
7
+ skills command tests, because the executable embeds the skill.
8
+ - Any other change, including workflows, Rust, Python, tests and lockfiles, runs every suite.
9
+
10
+ An empty path list means the diff could not be computed, so every suite runs.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import fnmatch
16
+ import sys
17
+
18
+ LIGHT: tuple[str, ...] = (
19
+ "website/*",
20
+ "benchmarks/*",
21
+ ".github/demos/*",
22
+ ".github/*.png",
23
+ ".github/*.md",
24
+ ".github/workflows/website.yml",
25
+ "*.md",
26
+ )
27
+ SKILL: tuple[str, ...] = ("crates/sqlclean-cli/skill/*",)
28
+
29
+
30
+ def classify(paths: list[str]) -> dict[str, bool]:
31
+ """Return which suites the changed paths need."""
32
+
33
+ def matches(path: str, patterns: tuple[str, ...]) -> bool:
34
+ # `*.md` must only match a root file, while `website/*` matches every depth.
35
+ return any(
36
+ fnmatch.fnmatch(path, pattern) and ("/" in pattern or "/" not in path)
37
+ for pattern in patterns
38
+ )
39
+
40
+ skills: bool = any(matches(path, SKILL) for path in paths)
41
+ full: bool = not paths or any(
42
+ not matches(path, LIGHT) and not matches(path, SKILL) for path in paths
43
+ )
44
+ return {"full": full, "skills": skills and not full}
45
+
46
+
47
+ def main() -> int:
48
+ paths: list[str] = [line.strip() for line in sys.stdin if line.strip()]
49
+ for name, value in classify(paths).items():
50
+ print(f"{name}={str(value).lower()}")
51
+ return 0
52
+
53
+
54
+ if __name__ == "__main__":
55
+ raise SystemExit(main())
@@ -0,0 +1,469 @@
1
+ """Reject release branches containing non-generated metadata changes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import copy
7
+ import json
8
+ import re
9
+ import subprocess
10
+ import tomllib
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ SEMVER_PART_COUNT: int = 3
16
+ NORMALIZED_VERSION: str = "0.0.0"
17
+ CHANGELOG_PATH: str = "CHANGELOG.md"
18
+ RELEASE_MANIFEST_PATH: str = ".release-please-manifest.json"
19
+ ROOT_PROJECT_PATH: str = "pyproject.toml"
20
+ CLI_PROJECT_PATH: str = "packages/sqlclean-cli/pyproject.toml"
21
+ CARGO_WORKSPACE_PATH: str = "Cargo.toml"
22
+ CARGO_LOCK_PATH: str = "Cargo.lock"
23
+ UV_LOCK_PATH: str = "uv.lock"
24
+ CLI_DISTRIBUTION: str = "sqlclean-cli"
25
+ VERSION_PATHS: tuple[str, ...] = (
26
+ RELEASE_MANIFEST_PATH,
27
+ ROOT_PROJECT_PATH,
28
+ CLI_PROJECT_PATH,
29
+ CARGO_WORKSPACE_PATH,
30
+ UV_LOCK_PATH,
31
+ CARGO_LOCK_PATH,
32
+ )
33
+ # Files that may not exist before the first release.
34
+ OPTIONAL_BASE_PATHS: frozenset[str] = frozenset({CHANGELOG_PATH})
35
+ OWNED_CARGO_PACKAGES: frozenset[str] = frozenset({"sqlclean", "sqlclean-cli"})
36
+ OWNED_UV_PACKAGES: frozenset[str] = frozenset({"sqlclean", "sqlclean-cli"})
37
+ ALLOWED_PATHS: frozenset[str] = frozenset((*VERSION_PATHS, CHANGELOG_PATH))
38
+ # The TOML table holding the owned version field in each manifest.
39
+ MANIFEST_SECTIONS: dict[str, tuple[str, ...]] = {
40
+ ROOT_PROJECT_PATH: ("project",),
41
+ CLI_PROJECT_PATH: ("project",),
42
+ CARGO_WORKSPACE_PATH: ("workspace", "package"),
43
+ }
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class ReleaseDelta:
48
+ """Base and release-head content for generated metadata files."""
49
+
50
+ old_version: str
51
+ new_version: str
52
+ changed_paths: frozenset[str]
53
+ base_files: dict[str, str]
54
+ head_files: dict[str, str]
55
+
56
+
57
+ def load_release_delta(*, repo_root: Path, base_ref: str, head_ref: str) -> ReleaseDelta:
58
+ """Read the changed metadata and package versions from two Git revisions."""
59
+
60
+ changed: list[str] = _git(
61
+ "diff", "--name-only", base_ref, head_ref, repo_root=repo_root
62
+ ).splitlines()
63
+ base_files: dict[str, str] = {
64
+ path: _show(ref=base_ref, path=path, repo_root=repo_root) for path in ALLOWED_PATHS
65
+ }
66
+ head_files: dict[str, str] = {
67
+ path: _git("show", f"{head_ref}:{path}", repo_root=repo_root) for path in ALLOWED_PATHS
68
+ }
69
+ old_version: str = tomllib.loads(base_files[ROOT_PROJECT_PATH])["project"]["version"]
70
+ new_version: str = tomllib.loads(head_files[ROOT_PROJECT_PATH])["project"]["version"]
71
+ return ReleaseDelta(
72
+ old_version=old_version,
73
+ new_version=new_version,
74
+ changed_paths=frozenset(changed),
75
+ base_files=base_files,
76
+ head_files=head_files,
77
+ )
78
+
79
+
80
+ def get_release_delta_errors(
81
+ *,
82
+ delta: ReleaseDelta,
83
+ allow_stale_lockfiles: bool = False,
84
+ ) -> list[str]:
85
+ """Return every unsafe or inconsistent release delta."""
86
+
87
+ errors: list[str] = []
88
+ unexpected: list[str] = sorted(delta.changed_paths - ALLOWED_PATHS)
89
+ if unexpected:
90
+ errors.append(f"unexpected release files: {', '.join(unexpected)}")
91
+ if not is_forward_semver(old_version=delta.old_version, new_version=delta.new_version):
92
+ errors.append("release version must be a forward MAJOR.MINOR.PATCH transition")
93
+ for path in VERSION_PATHS:
94
+ if not version_file_is_valid(
95
+ path=path,
96
+ delta=delta,
97
+ allow_stale_lockfiles=allow_stale_lockfiles,
98
+ ):
99
+ errors.append(f"{path} contains changes beyond the release version substitution")
100
+ changelog: str = delta.head_files[CHANGELOG_PATH]
101
+ base_history: str = delta.base_files[CHANGELOG_PATH].partition("\n\n")[2]
102
+ if not changelog.startswith("# Changelog\n\n") or base_history not in changelog:
103
+ errors.append("CHANGELOG.md rewrites existing release history")
104
+ if f"## [{delta.new_version}]" not in changelog:
105
+ errors.append(f"CHANGELOG.md has no {delta.new_version} release heading")
106
+ return errors
107
+
108
+
109
+ def is_forward_semver(*, old_version: str, new_version: str) -> bool:
110
+ """Return whether two plain semantic versions form a forward transition."""
111
+
112
+ old_semver: tuple[int, int, int] | None = _parse_semver(value=old_version)
113
+ new_semver: tuple[int, int, int] | None = _parse_semver(value=new_version)
114
+ return old_semver is not None and new_semver is not None and new_semver > old_semver
115
+
116
+
117
+ def version_file_is_valid(
118
+ *,
119
+ path: str,
120
+ delta: ReleaseDelta,
121
+ allow_stale_lockfiles: bool,
122
+ ) -> bool:
123
+ """Compare one parsed metadata file after normalizing owned version fields."""
124
+
125
+ try:
126
+ semantic_valid: bool
127
+ if path == RELEASE_MANIFEST_PATH:
128
+ semantic_valid = _release_manifest_is_valid(delta=delta, path=path)
129
+ else:
130
+ base: dict[str, Any] = tomllib.loads(delta.base_files[path])
131
+ head: dict[str, Any] = tomllib.loads(delta.head_files[path])
132
+ if path == ROOT_PROJECT_PATH:
133
+ semantic_valid = _root_project_is_valid(base=base, head=head, delta=delta)
134
+ elif path in MANIFEST_SECTIONS:
135
+ semantic_valid = _manifest_is_valid(
136
+ base=base,
137
+ head=head,
138
+ section=MANIFEST_SECTIONS[path],
139
+ delta=delta,
140
+ )
141
+ elif path == CARGO_LOCK_PATH:
142
+ semantic_valid = _lock_is_valid(
143
+ base=base,
144
+ head=head,
145
+ owned_packages=OWNED_CARGO_PACKAGES,
146
+ delta=delta,
147
+ allow_old=allow_stale_lockfiles,
148
+ )
149
+ elif path == UV_LOCK_PATH:
150
+ semantic_valid = _lock_is_valid(
151
+ base=base,
152
+ head=head,
153
+ owned_packages=OWNED_UV_PACKAGES,
154
+ delta=delta,
155
+ allow_old=allow_stale_lockfiles,
156
+ )
157
+ else:
158
+ return False
159
+ return semantic_valid and _version_text_is_valid(
160
+ path=path,
161
+ delta=delta,
162
+ allow_stale_lockfiles=allow_stale_lockfiles,
163
+ )
164
+ except (KeyError, TypeError, ValueError, tomllib.TOMLDecodeError):
165
+ return False
166
+
167
+
168
+ def _parse_semver(*, value: str) -> tuple[int, int, int] | None:
169
+ parts: list[str] = value.split(".")
170
+ if len(parts) != SEMVER_PART_COUNT or any(not part.isdigit() for part in parts):
171
+ return None
172
+ return (int(parts[0]), int(parts[1]), int(parts[2]))
173
+
174
+
175
+ def _release_manifest_is_valid(*, delta: ReleaseDelta, path: str) -> bool:
176
+ base: dict[str, Any] = json.loads(delta.base_files[path])
177
+ head: dict[str, Any] = json.loads(delta.head_files[path])
178
+ base_normalized: dict[str, Any] | None = _normalized_field(
179
+ document=base,
180
+ section=(),
181
+ field=".",
182
+ expected=delta.old_version,
183
+ )
184
+ head_normalized: dict[str, Any] | None = _normalized_field(
185
+ document=head,
186
+ section=(),
187
+ field=".",
188
+ expected=delta.new_version,
189
+ )
190
+ return base_normalized is not None and base_normalized == head_normalized
191
+
192
+
193
+ def _manifest_is_valid(
194
+ *,
195
+ base: dict[str, Any],
196
+ head: dict[str, Any],
197
+ section: tuple[str, ...],
198
+ delta: ReleaseDelta,
199
+ ) -> bool:
200
+ base_normalized: dict[str, Any] | None = _normalized_field(
201
+ document=base,
202
+ section=section,
203
+ field="version",
204
+ expected=delta.old_version,
205
+ )
206
+ head_normalized: dict[str, Any] | None = _normalized_field(
207
+ document=head,
208
+ section=section,
209
+ field="version",
210
+ expected=delta.new_version,
211
+ )
212
+ return base_normalized is not None and base_normalized == head_normalized
213
+
214
+
215
+ def _root_project_is_valid(
216
+ *,
217
+ base: dict[str, Any],
218
+ head: dict[str, Any],
219
+ delta: ReleaseDelta,
220
+ ) -> bool:
221
+ base_normalized: dict[str, Any] | None = _normalized_field(
222
+ document=base,
223
+ section=("project",),
224
+ field="version",
225
+ expected=delta.old_version,
226
+ )
227
+ head_normalized: dict[str, Any] | None = _normalized_field(
228
+ document=head,
229
+ section=("project",),
230
+ field="version",
231
+ expected=delta.new_version,
232
+ )
233
+ if base_normalized is None or head_normalized is None:
234
+ return False
235
+ base_normalized = _normalized_cli_dependency(
236
+ document=base_normalized,
237
+ version=delta.old_version,
238
+ )
239
+ head_normalized = _normalized_cli_dependency(
240
+ document=head_normalized,
241
+ version=delta.new_version,
242
+ )
243
+ return base_normalized is not None and base_normalized == head_normalized
244
+
245
+
246
+ def _normalized_field(
247
+ *,
248
+ document: dict[str, Any],
249
+ section: tuple[str, ...],
250
+ field: str,
251
+ expected: str,
252
+ ) -> dict[str, Any] | None:
253
+ normalized: dict[str, Any] = copy.deepcopy(document)
254
+ target: dict[str, Any] = normalized
255
+ for key in section:
256
+ target = target[key]
257
+ if target.get(field) != expected:
258
+ return None
259
+ target[field] = NORMALIZED_VERSION
260
+ return normalized
261
+
262
+
263
+ def _normalized_cli_dependency(
264
+ *,
265
+ document: dict[str, Any],
266
+ version: str,
267
+ ) -> dict[str, Any] | None:
268
+ requirement: str = f"{CLI_DISTRIBUTION}=={version}"
269
+ dependencies: list[Any] = document["project"].get("dependencies", [])
270
+ if dependencies.count(requirement) != 1:
271
+ return None
272
+ normalized: dict[str, Any] = copy.deepcopy(document)
273
+ normalized["project"]["dependencies"] = [
274
+ f"{CLI_DISTRIBUTION}=={NORMALIZED_VERSION}" if item == requirement else item
275
+ for item in dependencies
276
+ ]
277
+ return normalized
278
+
279
+
280
+ def _lock_is_valid(
281
+ *,
282
+ base: dict[str, Any],
283
+ head: dict[str, Any],
284
+ owned_packages: frozenset[str],
285
+ delta: ReleaseDelta,
286
+ allow_old: bool,
287
+ ) -> bool:
288
+ base_normalized: dict[str, Any] | None = _normalized_lock(
289
+ document=base,
290
+ owned_packages=owned_packages,
291
+ allowed_versions=frozenset({delta.old_version}),
292
+ )
293
+ allowed_head: frozenset[str] = frozenset(
294
+ {delta.old_version, delta.new_version} if allow_old else {delta.new_version}
295
+ )
296
+ head_normalized: dict[str, Any] | None = _normalized_lock(
297
+ document=head,
298
+ owned_packages=owned_packages,
299
+ allowed_versions=allowed_head,
300
+ )
301
+ return base_normalized is not None and base_normalized == head_normalized
302
+
303
+
304
+ def _normalized_lock(
305
+ *,
306
+ document: dict[str, Any],
307
+ owned_packages: frozenset[str],
308
+ allowed_versions: frozenset[str],
309
+ ) -> dict[str, Any] | None:
310
+ normalized: dict[str, Any] = copy.deepcopy(document)
311
+ found: set[str] = set()
312
+ for package in normalized.get("package", []):
313
+ if package.get("name") not in owned_packages:
314
+ continue
315
+ found.add(package["name"])
316
+ if package.get("version") not in allowed_versions:
317
+ return None
318
+ package["version"] = NORMALIZED_VERSION
319
+ return normalized if found == owned_packages else None
320
+
321
+
322
+ def _version_text_is_valid(
323
+ *,
324
+ path: str,
325
+ delta: ReleaseDelta,
326
+ allow_stale_lockfiles: bool,
327
+ ) -> bool:
328
+ base: str = delta.base_files[path]
329
+ expected: str | None
330
+ if path == RELEASE_MANIFEST_PATH:
331
+ expected = _replace_once(
332
+ value=base,
333
+ pattern=rf'("\."\s*:\s*"){re.escape(delta.old_version)}(")',
334
+ replacement=rf"\g<1>{delta.new_version}\g<2>",
335
+ )
336
+ elif path in MANIFEST_SECTIONS:
337
+ expected = _replace_manifest_version(
338
+ value=base,
339
+ section=".".join(MANIFEST_SECTIONS[path]),
340
+ old_version=delta.old_version,
341
+ new_version=delta.new_version,
342
+ )
343
+ if expected is not None and path == ROOT_PROJECT_PATH:
344
+ expected = _replace_once(
345
+ value=expected,
346
+ pattern=re.escape(f"{CLI_DISTRIBUTION}=={delta.old_version}"),
347
+ replacement=f"{CLI_DISTRIBUTION}=={delta.new_version}",
348
+ )
349
+ elif path == CARGO_LOCK_PATH:
350
+ expected = _replace_lock_versions(
351
+ value=base,
352
+ owned_packages=OWNED_CARGO_PACKAGES,
353
+ old_version=delta.old_version,
354
+ new_version=delta.new_version,
355
+ )
356
+ elif path == UV_LOCK_PATH:
357
+ expected = _replace_lock_versions(
358
+ value=base,
359
+ owned_packages=OWNED_UV_PACKAGES,
360
+ old_version=delta.old_version,
361
+ new_version=delta.new_version,
362
+ )
363
+ else:
364
+ return False
365
+ if expected is None:
366
+ return False
367
+ head: str = delta.head_files[path]
368
+ return head == expected or (
369
+ allow_stale_lockfiles and path in {CARGO_LOCK_PATH, UV_LOCK_PATH} and head == base
370
+ )
371
+
372
+
373
+ def _replace_manifest_version(
374
+ *,
375
+ value: str,
376
+ section: str,
377
+ old_version: str,
378
+ new_version: str,
379
+ ) -> str | None:
380
+ return _replace_once(
381
+ value=value,
382
+ pattern=(
383
+ rf'(?ms)(^\[{re.escape(section)}\]\s*$.*?^version\s*=\s*")'
384
+ rf"{re.escape(old_version)}"
385
+ r'("[^\n]*$)'
386
+ ),
387
+ replacement=rf"\g<1>{new_version}\g<2>",
388
+ )
389
+
390
+
391
+ def _replace_lock_versions(
392
+ *,
393
+ value: str,
394
+ owned_packages: frozenset[str],
395
+ old_version: str,
396
+ new_version: str,
397
+ ) -> str | None:
398
+ blocks: list[str] = re.split(r"(?=^\[\[package\]\]\s*$)", value, flags=re.MULTILINE)
399
+ found: set[str] = set()
400
+ for index, block in enumerate(blocks):
401
+ name_match: re.Match[str] | None = re.search(r'^name\s*=\s*"([^"]+)"', block, re.MULTILINE)
402
+ if name_match is None or name_match.group(1) not in owned_packages:
403
+ continue
404
+ found.add(name_match.group(1))
405
+ replaced: str | None = _replace_once(
406
+ value=block,
407
+ pattern=rf'(?m)(^version\s*=\s*"){re.escape(old_version)}("[^\n]*$)',
408
+ replacement=rf"\g<1>{new_version}\g<2>",
409
+ )
410
+ if replaced is None:
411
+ return None
412
+ blocks[index] = replaced
413
+ return "".join(blocks) if found == owned_packages else None
414
+
415
+
416
+ def _replace_once(*, value: str, pattern: str, replacement: str) -> str | None:
417
+ replaced, count = re.subn(pattern, replacement, value, count=1)
418
+ return replaced if count == 1 else None
419
+
420
+
421
+ def _show(*, ref: str, path: str, repo_root: Path) -> str:
422
+ try:
423
+ return _git("show", f"{ref}:{path}", repo_root=repo_root)
424
+ except subprocess.CalledProcessError:
425
+ if path in OPTIONAL_BASE_PATHS:
426
+ return ""
427
+ raise
428
+
429
+
430
+ def _git(*arguments: str, repo_root: Path) -> str:
431
+ result: subprocess.CompletedProcess[str] = subprocess.run(
432
+ ["git", *arguments],
433
+ cwd=repo_root,
434
+ check=True,
435
+ capture_output=True,
436
+ text=True,
437
+ )
438
+ return result.stdout
439
+
440
+
441
+ def _parse_args() -> argparse.Namespace:
442
+ parser: argparse.ArgumentParser = argparse.ArgumentParser(prog="release_metadata_check")
443
+ parser.add_argument("--base", required=True)
444
+ parser.add_argument("--head", default="HEAD")
445
+ parser.add_argument("--repo", type=Path, default=Path.cwd())
446
+ parser.add_argument("--allow-stale-lockfiles", action="store_true")
447
+ return parser.parse_args()
448
+
449
+
450
+ def main() -> int:
451
+ """Validate one generated release delta and render actionable failures."""
452
+
453
+ arguments: argparse.Namespace = _parse_args()
454
+ delta: ReleaseDelta = load_release_delta(
455
+ repo_root=arguments.repo,
456
+ base_ref=arguments.base,
457
+ head_ref=arguments.head,
458
+ )
459
+ errors: list[str] = get_release_delta_errors(
460
+ delta=delta,
461
+ allow_stale_lockfiles=arguments.allow_stale_lockfiles,
462
+ )
463
+ for error in errors:
464
+ print(f"release metadata error: {error}")
465
+ return int(bool(errors))
466
+
467
+
468
+ if __name__ == "__main__":
469
+ raise SystemExit(main())
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ # Rehearse a release on a scratch worktree: apply Release Please's version edits, refresh the
3
+ # lockfiles the way release-please.yml does, then run the strict release metadata check.
4
+ # This catches lockfile drift, such as a different uv rewriting uv.lock, before a real release.
5
+ set -euo pipefail
6
+
7
+ repo="$(git rev-parse --show-toplevel)"
8
+ current="$(jq -r '."."' "${repo}/.release-please-manifest.json")"
9
+ IFS=. read -r major minor _ <<< "${current}"
10
+ next="${major}.$((minor + 1)).0"
11
+ scratch="$(mktemp -d)"
12
+ trap 'git -C "${repo}" worktree remove --force "${scratch}" >/dev/null 2>&1 || true' EXIT
13
+
14
+ git -C "${repo}" worktree add --detach --quiet "${scratch}" HEAD
15
+ cd "${scratch}"
16
+ # Release Please rewrites the version on every line marked x-release-please-version.
17
+ for file in pyproject.toml Cargo.toml packages/sqlclean-cli/pyproject.toml; do
18
+ sed -i "/x-release-please-version/s/${current//./\\.}/${next}/" "${file}"
19
+ done
20
+ # The Python release strategy also rewrites [project] version, which carries no marker.
21
+ sed -i "0,/^version = \"${current//./\\.}\"/s//version = \"${next}\"/" pyproject.toml
22
+ jq --arg next "${next}" '."." = $next' .release-please-manifest.json > manifest.tmp
23
+ mv manifest.tmp .release-please-manifest.json
24
+ # Release Please adds the new section under the title and keeps the earlier history.
25
+ {
26
+ printf '# Changelog\n\n## [%s](https://example.com/compare/v%s...v%s) (2000-01-01)\n\n' \
27
+ "${next}" "${current}" "${next}"
28
+ tail -n +3 CHANGELOG.md
29
+ } > changelog.tmp
30
+ mv changelog.tmp CHANGELOG.md
31
+ # The same refresh as release-please.yml; cargo metadata rewrites Cargo.lock without compiling.
32
+ uv lock --quiet
33
+ cargo metadata --format-version 1 > /dev/null
34
+ git -c commit.gpgsign=false -c user.name=release-rehearsal -c user.email=release-rehearsal@example.com \
35
+ commit --quiet --all --message "chore(main): release ${next}"
36
+ python3 "${repo}/.github/scripts/release_metadata_check.py" --repo "${scratch}" --base HEAD~1 --head HEAD
37
+ echo "Release rehearsal ${current} -> ${next} passed with $(uv --version)"
Binary file