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.
- sqlclean-0.1.1/.github/RELEASING.md +85 -0
- sqlclean-0.1.1/.github/demos/check-fix.gif +0 -0
- sqlclean-0.1.1/.github/scripts/assert_cli_wheel_tags.sh +30 -0
- sqlclean-0.1.1/.github/scripts/classify_changes.py +55 -0
- sqlclean-0.1.1/.github/scripts/release_metadata_check.py +469 -0
- sqlclean-0.1.1/.github/scripts/simulate_release.sh +37 -0
- sqlclean-0.1.1/.github/sqlclean-logo-dark.png +0 -0
- sqlclean-0.1.1/.github/sqlclean-logo-light.png +0 -0
- sqlclean-0.1.1/.github/sqlclean-logo.png +0 -0
- sqlclean-0.1.1/.github/workflows/publish.yml +182 -0
- sqlclean-0.1.1/.github/workflows/pull-request-metadata.yml +98 -0
- sqlclean-0.1.1/.github/workflows/release-please.yml +278 -0
- sqlclean-0.1.1/.github/workflows/verify.yml +216 -0
- sqlclean-0.1.1/.github/workflows/version-guard.yml +67 -0
- sqlclean-0.1.1/.github/workflows/website.yml +45 -0
- sqlclean-0.1.1/.gitignore +25 -0
- sqlclean-0.1.1/.pre-commit-config.yaml +55 -0
- sqlclean-0.1.1/.pre-commit-hooks.yaml +23 -0
- sqlclean-0.1.1/.python-version +1 -0
- sqlclean-0.1.1/.release-please-manifest.json +3 -0
- sqlclean-0.1.1/CHANGELOG.md +38 -0
- sqlclean-0.1.1/Cargo.lock +844 -0
- sqlclean-0.1.1/Cargo.toml +60 -0
- sqlclean-0.1.1/LICENSE +201 -0
- sqlclean-0.1.1/Makefile +51 -0
- sqlclean-0.1.1/PKG-INFO +200 -0
- sqlclean-0.1.1/README.md +176 -0
- sqlclean-0.1.1/benchmarks/linters/README.md +49 -0
- sqlclean-0.1.1/benchmarks/linters/bench.sh +98 -0
- sqlclean-0.1.1/benchmarks/linters/convert_duckshop.py +73 -0
- sqlclean-0.1.1/benchmarks/linters/rules/layers.py +61 -0
- sqlclean-0.1.1/benchmarks/linters/summarize.py +95 -0
- sqlclean-0.1.1/crates/sqlclean/Cargo.toml +26 -0
- sqlclean-0.1.1/crates/sqlclean/src/constants.rs +15 -0
- sqlclean-0.1.1/crates/sqlclean/src/lib.rs +9 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/_helpers/collection.rs +236 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/_helpers/mod.rs +3 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/_helpers/statements.rs +38 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/_helpers/tokens.rs +234 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/main/file_facts.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/main/mod.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/main/statement_ends.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/mod.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/models.rs +81 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/tests/helpers.rs +4 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/tests/test_file_facts.rs +281 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/tests/test_types.rs +27 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_facts/tests.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/additional.rs +1873 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/engine.rs +1978 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/formatter.rs +352 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/formatter_syntax.rs +459 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/inline_relations.rs +223 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/join_predicates.rs +694 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/mod.rs +10 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/parsing.rs +44 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/printed_layout.rs +1079 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/terminal_shape.rs +303 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/_helpers/token_layout.rs +815 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/constants.rs +61 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/batch_engine.rs +19 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/catalogue.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/cross_join_subject.rs +15 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/default_rules.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/engine.rs +5 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/fill_finding_path.rs +9 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/format_requests.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/formatter.rs +5 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/lint.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/mod.rs +11 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/parse_sql.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/main/validate_dialect.rs +13 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/mod.rs +9 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/models.rs +191 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/fixtures/FORMAT_CORPUS_NOTICE.md +32 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/fixtures/format_corpus.json +5354 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/helpers.rs +304 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_authored_tokens.rs +416 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_batch_lint.rs +43 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_comment_formatter.rs +101 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_cross_joins.rs +286 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_empty_fixture_lint.rs +61 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_format_corpus.rs +61 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_function_call_spacing.rs +117 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_idempotency_reprint.rs +48 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_layout_rules.rs +294 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_line_wrap.rs +121 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_lint_exemptions.rs +130 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_set_operation_formatter.rs +59 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_sql_lint.rs +1969 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_types.rs +182 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_unused_table_alias.rs +317 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests/test_values_formatter.rs +53 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_lint/tests.rs +34 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/cte_columns.rs +457 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/declared_keys.rs +50 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/json_parses.rs +201 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/keys.rs +437 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/literals.rs +71 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/ranking.rs +218 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/removal_safety.rs +102 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/setting_help.rs +26 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/_helpers/syntax.rs +124 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/constants.rs +202 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/main/declared_relation_keys.rs +38 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/main/diagnostics.rs +32 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/main/mod.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/mod.rs +27 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/models.rs +47 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/helpers.rs +116 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/test_json_parses.rs +282 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/test_quality_rules.rs +403 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/test_quality_scale.rs +43 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests/test_types.rs +38 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/tests.rs +10 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_quality/types.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/_helpers/builtin_functions.rs +151 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/_helpers/mod.rs +3 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/_helpers/reserved_keywords.rs +43 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/_helpers/token_context.rs +30 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/constants.rs +35 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/builtin_function_names.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/canonical_tokens.rs +25 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/case_folding.rs +23 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/is_unquoted_word.rs +11 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/mod.rs +10 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/name_positions.rs +16 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/projection_spans.rs +41 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/query_fingerprint.rs +25 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/main/token_texts.rs +16 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/mod.rs +9 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/models.rs +7 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/bigquery.txt +99 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/databricks.txt +4 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/duckdb.txt +77 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/generic.txt +28 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/postgres.txt +80 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/snowflake.txt +65 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/reserved_keywords/tsql.txt +186 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/tests/test_canonical_tokens.rs +218 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/tests/test_types.rs +7 -0
- sqlclean-0.1.1/crates/sqlclean/src/sql_tokens/tests.rs +4 -0
- sqlclean-0.1.1/crates/sqlclean-cli/Cargo.toml +32 -0
- sqlclean-0.1.1/crates/sqlclean-cli/build.rs +5 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/SKILL.md +61 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/CONTENTS.md +39 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/benchmarks.md +80 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/check.md +170 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/facts.md +78 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/fix.md +99 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/format.md +77 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/rules.md +122 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/cli/skills.md +61 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/configuration.md +188 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/custom-rules/fixes-and-options.md +160 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/custom-rules/overview.md +194 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/custom-rules/statement-facts.md +186 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/custom-rules/testing.md +183 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/embedding.md +160 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/formatting.md +200 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/index.md +83 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/integrations.md +116 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/quickstart.md +223 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/rules/overview.md +273 -0
- sqlclean-0.1.1/crates/sqlclean-cli/skill/references/docs/rules/reference.md +275 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/_helpers/entries.rs +13 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/_helpers/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/constants.rs +4 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/main/cache_key.rs +14 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/main/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/mod.rs +9 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/models.rs +105 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/tests/test_result_cache.rs +128 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/tests/test_types.rs +17 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/caching/tests.rs +4 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/builtin.rs +88 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/catalogue.rs +109 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/custom.rs +239 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/exemptions.rs +97 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/mod.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/orchestration.rs +179 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/_helpers/selection.rs +39 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/constants.rs +15 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/main/check_sources.rs +14 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/main/mod.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/main/rule_catalogue.rs +18 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/mod.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/checking/models.rs +77 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/check_command.rs +33 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/diffs.rs +25 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/dispatch.rs +58 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/facts_command.rs +19 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/fix_command.rs +70 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/format_command.rs +55 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/mod.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/rules_command.rs +77 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/_helpers/skills_command.rs +140 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/constants.rs +20 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/main/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/main/run.rs +10 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/mod.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/command/models.rs +110 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/_helpers/file_schema.rs +47 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/_helpers/loading.rs +65 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/_helpers/mod.rs +3 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/_helpers/validation.rs +62 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/constants.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/main/load_config.rs +10 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/main/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/mod.rs +9 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/models.rs +82 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/tests/test_load_config.rs +123 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/tests/test_types.rs +16 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/config/tests.rs +4 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/_helpers/matching.rs +60 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/_helpers/mod.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/_helpers/walking.rs +77 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/main/discover_files.rs +11 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/main/mod.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/main/path_patterns.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/mod.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/models.rs +11 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/tests/test_discover_files.rs +67 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/tests/test_types.rs +5 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/discovery/tests.rs +4 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/_helpers/edit_selection.rs +56 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/_helpers/mod.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/_helpers/passes.rs +74 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/constants.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/main/fix_sources.rs +16 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/main/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/mod.rs +9 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/models.rs +14 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/tests/helpers.rs +27 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/tests/test_edit_selection.rs +37 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/tests/test_types.rs +5 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/fixing/tests.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/formatting/constants.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/formatting/main/format_source.rs +65 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/formatting/main/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/formatting/mod.rs +4 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/_helpers/interpreter.rs +26 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/_helpers/mod.rs +2 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/_helpers/process.rs +57 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/constants.rs +7 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/main/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/main/run_host.rs +8 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/mod.rs +6 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/hosting/models.rs +59 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/lib.rs +14 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/main.rs +9 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/positions/main/apply_edits.rs +18 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/positions/main/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/positions/mod.rs +7 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/positions/models.rs +37 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/positions/tests/test_positions.rs +78 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/positions/tests/test_types.rs +12 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/positions/tests.rs +4 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/_helpers/location.rs +32 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/_helpers/machine.rs +68 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/_helpers/mod.rs +3 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/_helpers/text.rs +110 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/main/mod.rs +1 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/main/render_findings.rs +20 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/mod.rs +5 -0
- sqlclean-0.1.1/crates/sqlclean-cli/src/reporting/models.rs +24 -0
- sqlclean-0.1.1/fensu.toml +53 -0
- sqlclean-0.1.1/packages/sqlclean-cli/README.md +3 -0
- sqlclean-0.1.1/packages/sqlclean-cli/pyproject.toml +37 -0
- sqlclean-0.1.1/pyproject.toml +89 -0
- sqlclean-0.1.1/release-please-config.json +76 -0
- sqlclean-0.1.1/scripts/__init__.py +0 -0
- sqlclean-0.1.1/scripts/check_docs_skill.py +15 -0
- sqlclean-0.1.1/scripts/docs_skill/__init__.py +0 -0
- sqlclean-0.1.1/scripts/docs_skill/_helpers/__init__.py +0 -0
- sqlclean-0.1.1/scripts/docs_skill/_helpers/mdx.py +85 -0
- sqlclean-0.1.1/scripts/docs_skill/_helpers/navigation.py +65 -0
- sqlclean-0.1.1/scripts/docs_skill/_helpers/output.py +47 -0
- sqlclean-0.1.1/scripts/docs_skill/_helpers/render.py +110 -0
- sqlclean-0.1.1/scripts/docs_skill/constants.py +18 -0
- sqlclean-0.1.1/scripts/docs_skill/main/__init__.py +0 -0
- sqlclean-0.1.1/scripts/docs_skill/main/check.py +41 -0
- sqlclean-0.1.1/scripts/docs_skill/main/generate.py +30 -0
- sqlclean-0.1.1/scripts/docs_skill/models.py +21 -0
- sqlclean-0.1.1/scripts/generate_docs_skill.py +15 -0
- sqlclean-0.1.1/src/sqlclean/__init__.py +39 -0
- sqlclean-0.1.1/src/sqlclean/hosting/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/hosting/_helpers/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/hosting/_helpers/evaluation.py +109 -0
- sqlclean-0.1.1/src/sqlclean/hosting/_helpers/loading.py +64 -0
- sqlclean-0.1.1/src/sqlclean/hosting/_helpers/requests.py +116 -0
- sqlclean-0.1.1/src/sqlclean/hosting/_helpers/sql_files.py +126 -0
- sqlclean-0.1.1/src/sqlclean/hosting/constants.py +9 -0
- sqlclean-0.1.1/src/sqlclean/hosting/exceptions.py +11 -0
- sqlclean-0.1.1/src/sqlclean/hosting/main/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/hosting/main/build_sql_file.py +12 -0
- sqlclean-0.1.1/src/sqlclean/hosting/main/evaluate_definition.py +17 -0
- sqlclean-0.1.1/src/sqlclean/hosting/main/serve.py +15 -0
- sqlclean-0.1.1/src/sqlclean/py.typed +0 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/_helpers/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/_helpers/harness.py +66 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/_helpers/native_facts.py +38 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/constants.py +6 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/exceptions.py +7 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/main/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/main/evaluate_rule.py +11 -0
- sqlclean-0.1.1/src/sqlclean/rule_testing/models.py +42 -0
- sqlclean-0.1.1/src/sqlclean/rules/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/rules/_helpers/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/rules/_helpers/definition.py +107 -0
- sqlclean-0.1.1/src/sqlclean/rules/classes/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/rules/classes/rule_context.py +99 -0
- sqlclean-0.1.1/src/sqlclean/rules/classes/rule_option.py +128 -0
- sqlclean-0.1.1/src/sqlclean/rules/constants.py +12 -0
- sqlclean-0.1.1/src/sqlclean/rules/exceptions.py +17 -0
- sqlclean-0.1.1/src/sqlclean/rules/main/__init__.py +0 -0
- sqlclean-0.1.1/src/sqlclean/rules/main/define.py +30 -0
- sqlclean-0.1.1/src/sqlclean/rules/models.py +194 -0
- sqlclean-0.1.1/src/sqlclean/rules/types.py +33 -0
- sqlclean-0.1.1/src/sqlclean/testing.py +6 -0
- sqlclean-0.1.1/tests/__init__.py +0 -0
- sqlclean-0.1.1/tests/e2e/__init__.py +0 -0
- sqlclean-0.1.1/tests/e2e/src/__init__.py +0 -0
- sqlclean-0.1.1/tests/e2e/src/sqlclean/__init__.py +0 -0
- sqlclean-0.1.1/tests/e2e/src/sqlclean/hosting/__init__.py +0 -0
- sqlclean-0.1.1/tests/e2e/src/sqlclean/hosting/_test_types.py +93 -0
- sqlclean-0.1.1/tests/e2e/src/sqlclean/hosting/helpers.py +45 -0
- sqlclean-0.1.1/tests/e2e/src/sqlclean/hosting/test_sqlclean_command.py +451 -0
- sqlclean-0.1.1/tests/integration/__init__.py +0 -0
- sqlclean-0.1.1/tests/integration/src/__init__.py +0 -0
- sqlclean-0.1.1/tests/integration/src/sqlclean/__init__.py +0 -0
- sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/__init__.py +0 -0
- sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/_test_types.py +37 -0
- sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/helpers.py +124 -0
- sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/test_evaluate_rule.py +160 -0
- sqlclean-0.1.1/tests/integration/src/sqlclean/rule_testing/test_readme_rule.py +87 -0
- sqlclean-0.1.1/tests/unit/__init__.py +0 -0
- sqlclean-0.1.1/tests/unit/src/__init__.py +0 -0
- sqlclean-0.1.1/tests/unit/src/sqlclean/__init__.py +0 -0
- sqlclean-0.1.1/tests/unit/src/sqlclean/rules/__init__.py +0 -0
- sqlclean-0.1.1/tests/unit/src/sqlclean/rules/_test_types.py +14 -0
- sqlclean-0.1.1/tests/unit/src/sqlclean/rules/helpers.py +12 -0
- sqlclean-0.1.1/tests/unit/src/sqlclean/rules/test_rule_definition.py +69 -0
- sqlclean-0.1.1/uv.lock +833 -0
- sqlclean-0.1.1/website/.gitignore +3 -0
- sqlclean-0.1.1/website/README.md +33 -0
- sqlclean-0.1.1/website/STYLE.md +54 -0
- sqlclean-0.1.1/website/astro.config.mjs +83 -0
- sqlclean-0.1.1/website/package-lock.json +6078 -0
- sqlclean-0.1.1/website/package.json +20 -0
- sqlclean-0.1.1/website/public/assets/icon.png +0 -0
- sqlclean-0.1.1/website/public/assets/logo-dark.png +0 -0
- sqlclean-0.1.1/website/public/assets/logo-light.png +0 -0
- sqlclean-0.1.1/website/public/assets/og-icon.png +0 -0
- sqlclean-0.1.1/website/public/assets/og-sqlclean-docs.png +0 -0
- sqlclean-0.1.1/website/public/assets/og-sqlclean.png +0 -0
- sqlclean-0.1.1/website/public/favicon.png +0 -0
- sqlclean-0.1.1/website/public/favicon.svg +5 -0
- sqlclean-0.1.1/website/public/fonts/LICENSE-DejaVu.txt +78 -0
- sqlclean-0.1.1/website/public/fonts/LICENSE-IBM-Plex-Mono.txt +93 -0
- sqlclean-0.1.1/website/public/fonts/LICENSE-Inter.txt +93 -0
- sqlclean-0.1.1/website/public/fonts/dejavu-sans-mono-diagram.woff2 +0 -0
- sqlclean-0.1.1/website/public/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- sqlclean-0.1.1/website/public/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- sqlclean-0.1.1/website/public/fonts/inter-latin-wght-normal.woff2 +0 -0
- sqlclean-0.1.1/website/public/index.html +494 -0
- sqlclean-0.1.1/website/public/styles.css +1011 -0
- sqlclean-0.1.1/website/recordings/.gitignore +2 -0
- sqlclean-0.1.1/website/recordings/README.md +33 -0
- sqlclean-0.1.1/website/recordings/prepare.sh +74 -0
- sqlclean-0.1.1/website/recordings/record.sh +14 -0
- sqlclean-0.1.1/website/recordings/tapes/check-fix.tape +45 -0
- sqlclean-0.1.1/website/recordings/tapes/common.tape +13 -0
- sqlclean-0.1.1/website/scripts/check_links.py +65 -0
- sqlclean-0.1.1/website/src/assets/logo-dark.png +0 -0
- sqlclean-0.1.1/website/src/assets/logo-light.png +0 -0
- sqlclean-0.1.1/website/src/code-themes.mjs +69 -0
- sqlclean-0.1.1/website/src/components/Header.astro +63 -0
- sqlclean-0.1.1/website/src/components/PageFrame.astro +135 -0
- sqlclean-0.1.1/website/src/components/PageTitle.astro +54 -0
- sqlclean-0.1.1/website/src/components/Pagination.astro +61 -0
- sqlclean-0.1.1/website/src/components/SiteTitle.astro +58 -0
- sqlclean-0.1.1/website/src/components/ThemeSelect.astro +53 -0
- sqlclean-0.1.1/website/src/components/TwoColumnContent.astro +43 -0
- sqlclean-0.1.1/website/src/components/docs/Card.astro +65 -0
- sqlclean-0.1.1/website/src/components/docs/CardGroup.astro +8 -0
- sqlclean-0.1.1/website/src/components/docs/Frame.astro +1 -0
- sqlclean-0.1.1/website/src/components/docs/Note.astro +8 -0
- sqlclean-0.1.1/website/src/components/docs/Warning.astro +8 -0
- sqlclean-0.1.1/website/src/content/docs/docs/benchmarks.mdx +77 -0
- sqlclean-0.1.1/website/src/content/docs/docs/cli/check.mdx +160 -0
- sqlclean-0.1.1/website/src/content/docs/docs/cli/facts.mdx +77 -0
- sqlclean-0.1.1/website/src/content/docs/docs/cli/fix.mdx +98 -0
- sqlclean-0.1.1/website/src/content/docs/docs/cli/format.mdx +76 -0
- sqlclean-0.1.1/website/src/content/docs/docs/cli/rules.mdx +112 -0
- sqlclean-0.1.1/website/src/content/docs/docs/cli/skills.mdx +60 -0
- sqlclean-0.1.1/website/src/content/docs/docs/configuration.mdx +177 -0
- sqlclean-0.1.1/website/src/content/docs/docs/custom-rules/fixes-and-options.mdx +154 -0
- sqlclean-0.1.1/website/src/content/docs/docs/custom-rules/overview.mdx +196 -0
- sqlclean-0.1.1/website/src/content/docs/docs/custom-rules/statement-facts.mdx +169 -0
- sqlclean-0.1.1/website/src/content/docs/docs/custom-rules/testing.mdx +177 -0
- sqlclean-0.1.1/website/src/content/docs/docs/embedding.mdx +147 -0
- sqlclean-0.1.1/website/src/content/docs/docs/formatting.mdx +186 -0
- sqlclean-0.1.1/website/src/content/docs/docs/index.mdx +93 -0
- sqlclean-0.1.1/website/src/content/docs/docs/integrations.mdx +107 -0
- sqlclean-0.1.1/website/src/content/docs/docs/quickstart.mdx +211 -0
- sqlclean-0.1.1/website/src/content/docs/docs/rules/overview.mdx +271 -0
- sqlclean-0.1.1/website/src/content/docs/docs/rules/reference.mdx +262 -0
- sqlclean-0.1.1/website/src/content.config.ts +7 -0
- sqlclean-0.1.1/website/src/navigation.mjs +5 -0
- sqlclean-0.1.1/website/src/sidebar.json +55 -0
- sqlclean-0.1.1/website/src/styles/fonts.css +42 -0
- sqlclean-0.1.1/website/src/styles/sqlclean.css +446 -0
- sqlclean-0.0.1/.gitignore +0 -3
- sqlclean-0.0.1/LICENSE +0 -13
- sqlclean-0.0.1/PKG-INFO +0 -21
- sqlclean-0.0.1/README.md +0 -5
- sqlclean-0.0.1/pyproject.toml +0 -25
- 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.
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
Binary file
|