portolan-cli 0.4.2__tar.gz → 0.4.4__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.
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.pre-commit-config.yaml +10 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/CHANGELOG.md +41 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/CLAUDE.md +2 -1
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/PKG-INFO +1 -1
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/SKILL.md +13 -3
- portolan_cli-0.4.4/context/shared/adr/0029-unified-catalog-root-detection.md +125 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/catalog.py +81 -0
- portolan_cli-0.4.4/portolan_cli/clean.py +210 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/cli.py +736 -302
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/constants.py +6 -1
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/dataset.py +429 -138
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/errors.py +19 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/formats.py +4 -1
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/pull.py +18 -1
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/push.py +67 -1
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/scan.py +15 -4
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/scan_classify.py +4 -2
- portolan_cli-0.4.4/portolan_cli/status.py +528 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/validation/rules.py +1 -1
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/pyproject.toml +2 -2
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/conftest.py +104 -0
- portolan_cli-0.4.4/tests/integration/test_add_catalog_root_integration.py +416 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_add_rm_integration.py +223 -3
- portolan_cli-0.4.4/tests/integration/test_catalog_root_consistency.py +269 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_check_command.py +124 -0
- portolan_cli-0.4.4/tests/integration/test_clean_integration.py +594 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_dataset_integration.py +94 -30
- portolan_cli-0.4.4/tests/integration/test_list_cli.py +304 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_push_integration.py +21 -11
- portolan_cli-0.4.4/tests/integration/test_scan_batching.py +141 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_scan_cli.py +172 -5
- portolan_cli-0.4.4/tests/integration/test_scan_unrecognized_files.py +457 -0
- portolan_cli-0.4.4/tests/integration/test_status_integration.py +499 -0
- portolan_cli-0.4.4/tests/unit/test_add_catalog_root.py +362 -0
- portolan_cli-0.4.4/tests/unit/test_add_continue_on_errors.py +570 -0
- portolan_cli-0.4.4/tests/unit/test_add_failures_batching.py +288 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_add_rm_hypothesis.py +645 -24
- portolan_cli-0.4.4/tests/unit/test_clean.py +551 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_cli_add_rm.py +416 -20
- portolan_cli-0.4.4/tests/unit/test_cli_check.py +804 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_cli_dataset.py +108 -6
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_csv_skip.py +68 -65
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_dataset.py +308 -55
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_dataset_directory.py +107 -46
- portolan_cli-0.4.4/tests/unit/test_find_catalog_root.py +400 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_formats.py +49 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_formats_property.py +121 -1
- portolan_cli-0.4.4/tests/unit/test_list_output.py +523 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_pull.py +145 -20
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_push.py +172 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan.py +85 -17
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_classify.py +42 -2
- portolan_cli-0.4.4/tests/unit/test_scan_output_batching.py +385 -0
- portolan_cli-0.4.4/tests/unit/test_scan_unrecognized_files_property.py +192 -0
- portolan_cli-0.4.4/tests/unit/test_status.py +1807 -0
- portolan_cli-0.4.4/tests/unit/test_status_filegdb.py +634 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_sync.py +134 -0
- portolan_cli-0.4.4/tests/unit/test_tabular_parquet_skip.py +646 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_validation_rules.py +1 -1
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/uv.lock +1 -1
- portolan_cli-0.4.2/portolan_cli/status.py +0 -234
- portolan_cli-0.4.2/tests/unit/test_cli_check.py +0 -1112
- portolan_cli-0.4.2/tests/unit/test_status.py +0 -700
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.analyze.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.checklist.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.clarify.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.constitution.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.implement.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.plan.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.specify.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.tasks.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/commands/speckit.taskstoissues.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/hooks/post-bash-remind.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/hooks/post-read-inject-docs.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/hooks/pre-read-check.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/hooks/prompt-inject.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.claude/settings.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.coderabbit.yaml +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.github/CODEOWNERS +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.github/SOCIAL_PREVIEW.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.github/dependabot.yml +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.github/pull_request_template.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.github/workflows/ci.yml +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.github/workflows/docs.yml +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.github/workflows/nightly.yml +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.github/workflows/release.yml +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.gitignore +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.grepaiignore +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.mcp.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.python-version +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/memory/constitution.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/scripts/bash/common.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/scripts/bash/setup-plan.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/templates/agent-file-template.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/templates/checklist-template.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/templates/plan-template.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/templates/spec-template.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/.specify/templates/tasks-template.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/LICENSE +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/README.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/SECURITY.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/codecov.yml +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0000-template.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0001-agentic-first-development.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0002-click-for-cli.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0003-plugin-architecture.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0004-iceberg-as-plugin.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0005-versions-json-source-of-truth.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0006-remote-ownership-model.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0007-cli-wraps-api.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0008-pipx-for-installation.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0009-output-dry-run-and-verbose-modes.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0010-delegate-conversion-validation.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0011-mvp-validation-framework.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0012-flat-catalog-hierarchy.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0013-gitingest-auto-fetch.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0014-accept-non-cloud-native-formats.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0015-two-tier-versioning-architecture.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0016-scan-before-import.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0017-mtime-heuristics-change-detection.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0018-metadata-generation-tiers.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0019-cog-optimization-defaults.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0020-conversion-output-location.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0021-catalog-json-root-level.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0022-git-style-implicit-tracking.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0023-stac-structure-separation.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0024-hierarchical-config-system.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0025-architecture-as-code.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0026-conversion-config-design.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0027-unified-config-yaml-sentinel.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/adr/0028-all-files-as-assets.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/documentation/ci.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/documentation/context-guide.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/documentation/distill-mcp.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/documentation/gitingest-setup.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/documentation/test-fixtures.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/known-issues/example.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/known-issues/geoparquet-io-windows-segfault.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/known-issues/pyarrow-abseil-abi.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/known-issues/pystac-absolute-paths.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/context/shared/plans/2026-03-05-fix-versions-snapshot-model.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/BRANDING.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/assets/images/cover.png +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/assets/images/icon-white.svg +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/assets/images/icon.svg +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/assets/images/logo.png +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/assets/images/logo.svg +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/assets/images/social-card.png +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/assets/stylesheets/extra.css +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/changelog.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/contributing.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/index.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/reference/cli.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/docs/reference/configuration.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/mkdocs.yml +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/backends/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/backends/json_file.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/backends/protocol.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/catalog_versions.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/check.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/collection.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/collection_id.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/config.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/conversion_config.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/convert.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/download.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/inspect.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/item.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/json_output.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/metadata/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/metadata/cog.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/metadata/detection.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/metadata/fix.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/metadata/geoparquet.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/metadata/models.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/metadata/update.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/metadata/validation.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/models/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/models/catalog.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/models/collection.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/models/item.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/models/schema.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/models/version.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/output.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/scan_detect.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/scan_fix.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/scan_infer.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/scan_output.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/schema/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/schema/breaking.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/schema/export.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/schema/import_.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/stac.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/sync.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/upload.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/validation/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/validation/results.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/validation/runner.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/portolan_cli/versions.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/scripts/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/scripts/fetch_lib_docs.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/scripts/generate_claude_md_sections.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/scripts/generate_skill_md.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/scripts/orchestrate-issue.sh +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/scripts/update_freshness.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/scripts/validate_claude_md.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/scripts/validate_skill_md.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/benchmark/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/benchmark/test_catalog_benchmark.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/benchmark/test_conversion_benchmark.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/benchmark/test_format_detection_benchmark.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/benchmark/test_metadata_benchmark.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/benchmark/test_scan_performance.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/benchmark/test_validation_benchmark.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/docs/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/docs/conftest.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/docs/test_readme_examples.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/README.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/edge/antimeridian.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/edge/special_filename spaces.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/edge/unicode_properties.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/generate.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/invalid/catalog_missing_id.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/invalid/collection_bad_extent.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/invalid/item_no_geometry.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/invalid/item_wrong_type.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/valid/catalog_full.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/valid/catalog_minimal.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/valid/collection_raster.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/valid/collection_vector.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/valid/item_cog.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/stac/valid/item_geoparquet.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/versions/invalid/versions_bad_checksum.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/versions/invalid/versions_duplicate.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/versions/invalid/versions_missing_current.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/versions/valid/versions_breaking.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/versions/valid/versions_partial_sync.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/versions/valid/versions_v1.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/metadata/versions/valid/versions_v3.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/raster/invalid/not_georeferenced.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/raster/invalid/truncated.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/raster/natural-earth-non-cog.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/raster/valid/float32.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/raster/valid/nodata.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/raster/valid/rgb.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/raster/valid/singleband.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/README.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/fieldmaps-boundaries.parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000001.TablesByName.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000001.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000001.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000001.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000002.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000002.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000003.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000003.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000003.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000004.CatItemsByPhysicalName.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000004.CatItemsByType.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000004.FDO_UUID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000004.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000004.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000004.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000004.spx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000005.CatItemTypesByName.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000005.CatItemTypesByParentTypeID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000005.CatItemTypesByUUID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000005.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000005.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000005.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000006.CatRelsByDestinationID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000006.CatRelsByOriginID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000006.CatRelsByType.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000006.FDO_UUID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000006.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000006.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000006.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.CatRelTypesByBackwardLabel.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.CatRelTypesByDestItemTypeID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.CatRelTypesByForwardLabel.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.CatRelTypesByName.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.CatRelTypesByOriginItemTypeID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.CatRelTypesByUUID.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000007.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000009.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000009.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000009.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a00000009.spx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000a.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000a.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000a.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000b.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000b.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000b.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000c.band_index.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000c.blk_key_index.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000c.col_index.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000c.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000c.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000c.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000c.row_index.atx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000d.gdbindexes +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000d.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/a0000000d.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/gdb +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/filegdb/field_alias.gdb/timestamps +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/nwi-wetlands.parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/open-buildings.parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/rapidai4eo-sample.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/realdata/road-detections.parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/README.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/clean_flat/argentina.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/clean_flat/example.parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/clean_flat/seaLevelDistanceGeoJSON.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/complete_shapefile/radios_sample.dbf +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/complete_shapefile/radios_sample.prj +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/complete_shapefile/radios_sample.shp +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/complete_shapefile/radios_sample.shx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/duplicate_basenames/dir_a/argentina.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/duplicate_basenames/dir_b/argentina.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/filegdb/sample.gdb/a00000001.gdbtable +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/filegdb/sample.gdb/a00000001.gdbtablx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/filegdb/sample.gdb/gdb +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/filegdb/sample.gdb.zip +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/incomplete_shapefile/radios_sample.prj +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/incomplete_shapefile/radios_sample.shp +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/incomplete_shapefile/radios_sample.shx +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/invalid_chars/data (copy).parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/invalid_chars/donn/303/251es.geojson" +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/mixed_formats/ID1_N80_W170_RP10_depth_reclass.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/mixed_formats/example.parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/multiple_primaries/la_plata_L1.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/multiple_primaries/la_plata_L2.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/multiple_primaries/la_plata_L3.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/nested/census/2020/boundaries.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/nested/census/2022/boundaries.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/nested/imagery/2024/flood_depth.tif +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/unsupported/argentina.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/unsupported/metadata.csv +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/scan/unsupported/project.mxd +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/validation/pmtiles/README.md +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/validation/pmtiles/sample.parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/validation/pmtiles/sample.pmtiles +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/invalid/empty.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/invalid/malformed.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/invalid/no_geometry.json +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/invalid/null_geometries.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/open-buildings-utm31n.parquet +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/valid/large_properties.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/valid/lines.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/valid/multigeom.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/valid/points.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/fixtures/vector/valid/polygons.geojson +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_check_conversion_config.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_cli_json.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_clone_ergonomics_integration.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_config_command.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_convert_workflow.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_dataset_warnings.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_download_integration.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_fetch_lib_docs_integration.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_gpio_integration.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_init_command.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_init_v2.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_pull_integration.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_realdata_metadata.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_riocogeo_integration.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_schema_roundtrip.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_sync_integration.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/integration/test_upload_integration.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/network/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/network/test_network_placeholder.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/test_placeholder.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/backends/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/backends/test_backends.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/metadata/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/models/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/models/test_catalog.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/models/test_collection.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/models/test_item.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/models/test_schema.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/models/test_version.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/schema/__init__.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/schema/test_breaking.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/schema/test_export.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/schema/test_import.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_catalog_init.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_catalog_model_init.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_catalog_versions_collections.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_cli_config.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_cli_info.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_cli_init.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_clone_ergonomics.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_collection_creation.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_collection_id.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_config.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_conversion_config.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_convert.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_download.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_errors.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_fetch_lib_docs.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_filegdb_add_support.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_fixtures.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_formats_cloud_native.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_formats_convertible.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_formats_edge_cases.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_formats_effective_status.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_formats_status.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_formats_unsupported.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_inspect.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_item_creation.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_json_output.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_metadata_cog.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_metadata_detection.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_metadata_fix.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_metadata_geoparquet.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_metadata_models.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_metadata_update.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_metadata_validation.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_output.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_push_default_remote.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_coverage.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_detect.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_filegdb_discovery.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_fix.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_fix_coverage.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_infer.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_infer_coverage.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scan_output.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_schema_extraction.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_scripts.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_stac.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_stac_fixtures.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_state_detection.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_upload.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_validation_results.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_validation_runner.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_versions.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/tests/unit/test_versions_fixtures.py +0 -0
- {portolan_cli-0.4.2 → portolan_cli-0.4.4}/vulture_whitelist.py +0 -0
|
@@ -43,6 +43,16 @@ repos:
|
|
|
43
43
|
|
|
44
44
|
- repo: local
|
|
45
45
|
hooks:
|
|
46
|
+
# === SAFETY HOOKS ===
|
|
47
|
+
# Prevent accidental commits of temporary/generated content
|
|
48
|
+
|
|
49
|
+
- id: forbid-worktrees
|
|
50
|
+
name: forbid worktree files
|
|
51
|
+
entry: "bash -c 'echo ERROR - Worktree files should not be committed. Remove .claude/worktrees/ from staging. && exit 1'"
|
|
52
|
+
language: system
|
|
53
|
+
files: ^\.claude/worktrees/
|
|
54
|
+
pass_filenames: false
|
|
55
|
+
|
|
46
56
|
# === AUTO-UPDATE HOOKS ===
|
|
47
57
|
# These regenerate content from code - keeps docs in sync automatically
|
|
48
58
|
|
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
## v0.4.4 (2026-03-07)
|
|
2
|
+
|
|
3
|
+
### Feat
|
|
4
|
+
|
|
5
|
+
- **scan**: batch and summarize repeated warnings (#194)
|
|
6
|
+
- **add**: accept multiple paths like git add (#189)
|
|
7
|
+
- **add**: support tabular parquet as auxiliary assets (#190)
|
|
8
|
+
|
|
9
|
+
### Fix
|
|
10
|
+
|
|
11
|
+
- **list**: show all assets grouped by item (#196) (#204)
|
|
12
|
+
- **add**: batch repetitive failures by error message (#199) (#205)
|
|
13
|
+
- **add**: accept PMTiles as cloud-native primary asset (#201)
|
|
14
|
+
- **status**: treat FileGDB directories as single assets (#187)
|
|
15
|
+
- **status**: detect and skip symlink cycles (#188)
|
|
16
|
+
- **add**: continue on errors and report all failures at end (#191)
|
|
17
|
+
- **scan**: list specific unrecognized files instead of just count (#185)
|
|
18
|
+
- **scan**: make PATH argument optional, default to current directory (#184)
|
|
19
|
+
- **scan**: remove non-existent --bundle flag reference from suggestion (#183)
|
|
20
|
+
|
|
21
|
+
## v0.4.3 (2026-03-07)
|
|
22
|
+
|
|
23
|
+
### BREAKING CHANGE
|
|
24
|
+
|
|
25
|
+
- Removes --fix-metadata flag; use --metadata --fix instead.
|
|
26
|
+
|
|
27
|
+
### Feat
|
|
28
|
+
|
|
29
|
+
- **cli**: add portolan clean command to remove metadata (#172)
|
|
30
|
+
- **cli**: add --item-id flag to portolan add command (#171)
|
|
31
|
+
- **check**: redesign --fix to work orthogonally with --metadata/--geo-assets (#164)
|
|
32
|
+
|
|
33
|
+
### Fix
|
|
34
|
+
|
|
35
|
+
- **dataset**: make add_dataset atomic and track files in-place (#170)
|
|
36
|
+
- **catalog**: unify catalog root detection (#162) (#169)
|
|
37
|
+
- **status**: detect untracked files in uninitialized collections (#167)
|
|
38
|
+
- **dry-run**: prevent network calls in dry-run mode (#168)
|
|
39
|
+
- **add**: support recursive add at catalog root (#166)
|
|
40
|
+
- **list**: add guidance when no items found (#165)
|
|
41
|
+
|
|
1
42
|
## v0.4.2 (2026-03-06)
|
|
2
43
|
|
|
3
44
|
### Feat
|
|
@@ -100,6 +100,7 @@ AI agents will write most of the code. Human review does not scale to match AI o
|
|
|
100
100
|
| [0026](context/shared/adr/0026-conversion-config-design.md) | Conversion config: extension/path overrides, precedence rules |
|
|
101
101
|
| [0027](context/shared/adr/0027-unified-config-yaml-sentinel.md) | Unified config.yaml as sentinel and user config (eliminates config.json) |
|
|
102
102
|
| [0028](context/shared/adr/0028-all-files-as-assets.md) | Track ALL files in item directories as assets |
|
|
103
|
+
| [0029](context/shared/adr/0029-unified-catalog-root-detection.md) | Unified catalog root detection via .portolan/config.yaml |
|
|
103
104
|
|
|
104
105
|
## Common Commands
|
|
105
106
|
|
|
@@ -375,7 +376,7 @@ error("No geometry column (required)") # ✗ Red X
|
|
|
375
376
|
detail("Processing chunk 3/10...") # Dimmed text
|
|
376
377
|
```
|
|
377
378
|
|
|
378
|
-
<!-- freshness: last-verified: 2026-03-
|
|
379
|
+
<!-- freshness: last-verified: 2026-03-07 -->
|
|
379
380
|
## Design Principles
|
|
380
381
|
|
|
381
382
|
| Principle | Meaning | ADR |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: portolan-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: A CLI tool for managing cloud-native geospatial data
|
|
5
5
|
Project-URL: Homepage, https://github.com/portolan-sdi/portolan-cli
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/portolan-sdi/portolan-cli/issues
|
|
@@ -23,6 +23,8 @@ Portolan is a CLI for publishing and managing **cloud-native geospatial data cat
|
|
|
23
23
|
Track files in the catalog.
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
+
portolan add demographics/census.parquet
|
|
27
|
+
portolan add file1.geojson file2.geojson # Add multiple files
|
|
26
28
|
portolan add imagery/ # Add all files in directory
|
|
27
29
|
portolan add . # Add all files in catalog
|
|
28
30
|
```
|
|
@@ -34,7 +36,15 @@ Validate a Portolan catalog or check files for cloud-native status.
|
|
|
34
36
|
portolan check # Validate all (metadata + geo-assets)
|
|
35
37
|
portolan check --metadata # Validate metadata only
|
|
36
38
|
portolan check --geo-assets # Check geo-assets only
|
|
37
|
-
portolan check
|
|
39
|
+
portolan check --fix # Fix both metadata and geo-assets
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `portolan clean`
|
|
43
|
+
Remove all Portolan metadata while preserving data files.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
portolan clean # Remove all metadata
|
|
47
|
+
portolan clean --dry-run # Preview what would be removed
|
|
38
48
|
```
|
|
39
49
|
|
|
40
50
|
### `portolan clone`
|
|
@@ -123,10 +133,10 @@ portolan rm -f vectors/ # Force remove entire directory
|
|
|
123
133
|
Scan a directory for geospatial files and potential issues.
|
|
124
134
|
|
|
125
135
|
```bash
|
|
136
|
+
portolan scan # Scan current directory
|
|
137
|
+
portolan scan --json # JSON output in current directory
|
|
126
138
|
portolan scan /data/geospatial
|
|
127
|
-
portolan scan . --json
|
|
128
139
|
portolan scan /large/tree --max-depth=2
|
|
129
|
-
portolan scan /data --no-recursive
|
|
130
140
|
```
|
|
131
141
|
|
|
132
142
|
### `portolan status`
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# ADR-0029: Unified Catalog Root Detection
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Portolan CLI has inconsistent catalog root detection across commands, causing confusing behavior where users can add files successfully but then can't see them with `list`.
|
|
10
|
+
|
|
11
|
+
### Current State (Pre-fix)
|
|
12
|
+
|
|
13
|
+
Two competing functions detect catalog roots using different criteria:
|
|
14
|
+
|
|
15
|
+
| Function | Location | Sentinel | Used By |
|
|
16
|
+
|----------|----------|----------|---------|
|
|
17
|
+
| `find_catalog_root()` | `dataset.py` | `catalog.json` | `status`, `add`, `rm` |
|
|
18
|
+
| `_find_catalog_root()` | `cli.py` | `.portolan/` directory | `config get/set/list/unset` |
|
|
19
|
+
|
|
20
|
+
Additionally, `check` only looks at the current directory without walking up.
|
|
21
|
+
|
|
22
|
+
### Problems
|
|
23
|
+
|
|
24
|
+
1. **Shadow catalogs:** `add` auto-creates structure, but `list` looks for parent catalog, so added items are invisible
|
|
25
|
+
2. **Inconsistent behavior:** `status` shows sibling directories from parent catalog when user is in subdirectory
|
|
26
|
+
3. **Trust erosion:** Commands report contradictory states
|
|
27
|
+
|
|
28
|
+
### Related Issues
|
|
29
|
+
|
|
30
|
+
- GitHub Issue #162: Inconsistent catalog root detection across CLI commands
|
|
31
|
+
- GitHub Issue #137: Multiple related bugs that may be symptoms of this root cause
|
|
32
|
+
|
|
33
|
+
## Decision
|
|
34
|
+
|
|
35
|
+
**Use `.portolan/config.yaml` as the single sentinel for catalog root detection.**
|
|
36
|
+
|
|
37
|
+
All commands will use a unified `find_catalog_root()` function in `catalog.py` that:
|
|
38
|
+
|
|
39
|
+
1. Walks up from the current directory (or specified path)
|
|
40
|
+
2. Looks for `.portolan/config.yaml` (not `catalog.json`, not bare `.portolan/`)
|
|
41
|
+
3. Returns the catalog root path, or `None` if not found
|
|
42
|
+
4. Enforces a depth limit (`MAX_CATALOG_SEARCH_DEPTH = 20`) for security
|
|
43
|
+
|
|
44
|
+
This aligns with ADR-0027, which established `config.yaml` as the unified sentinel file for managed catalogs.
|
|
45
|
+
|
|
46
|
+
### Behavior Changes
|
|
47
|
+
|
|
48
|
+
| Command | Old Behavior | New Behavior |
|
|
49
|
+
|---------|--------------|--------------|
|
|
50
|
+
| `add` | Auto-creates catalog structure if missing | Fails with "run `portolan init`" message |
|
|
51
|
+
| `list` | Uses explicit `--catalog` argument (default `.`) | *Unchanged* - uses `--catalog` arg, not `find_catalog_root()` |
|
|
52
|
+
| `status` | Finds parent catalog via `catalog.json` | Finds parent catalog via `.portolan/config.yaml` |
|
|
53
|
+
| `rm` | Finds parent catalog via `catalog.json` | Finds parent catalog via `.portolan/config.yaml` |
|
|
54
|
+
| `config *` | Finds catalog via `.portolan/` directory | Finds catalog via `.portolan/config.yaml` |
|
|
55
|
+
|
|
56
|
+
> **Note:** `list` is excluded from the unified `find_catalog_root()` treatment because it takes `--catalog` as an explicit argument. This is a separate issue to address in a future PR.
|
|
57
|
+
|
|
58
|
+
### UNMANAGED_STAC Impact
|
|
59
|
+
|
|
60
|
+
Directories with only `catalog.json` (no `.portolan/`) will no longer be detected as catalogs. Users must run `portolan init` or `portolan adopt` to manage existing STAC catalogs.
|
|
61
|
+
|
|
62
|
+
This is acceptable because:
|
|
63
|
+
- UNMANAGED_STAC support was always incomplete (some commands worked, others didn't)
|
|
64
|
+
- Clear error messages guide users to the fix
|
|
65
|
+
- Pre-v1.0, so breaking changes are acceptable
|
|
66
|
+
|
|
67
|
+
## Implementation
|
|
68
|
+
|
|
69
|
+
### Single Function Location
|
|
70
|
+
|
|
71
|
+
The unified `find_catalog_root()` lives in `portolan_cli/catalog.py` alongside `detect_state()`, since both deal with catalog detection logic.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
def find_catalog_root(start_path: Path | None = None) -> Path | None:
|
|
75
|
+
"""Find catalog root by walking up to find .portolan/config.yaml.
|
|
76
|
+
|
|
77
|
+
Per ADR-0029, uses .portolan/config.yaml as the single sentinel,
|
|
78
|
+
unifying detection across all CLI commands.
|
|
79
|
+
|
|
80
|
+
Security: Limited to MAX_CATALOG_SEARCH_DEPTH (20) levels.
|
|
81
|
+
"""
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Deleted Code
|
|
85
|
+
|
|
86
|
+
- `find_catalog_root()` from `dataset.py` (looked for `catalog.json`)
|
|
87
|
+
- `_find_catalog_root()` from `cli.py` (looked for `.portolan/` directory)
|
|
88
|
+
|
|
89
|
+
## Consequences
|
|
90
|
+
|
|
91
|
+
### Benefits
|
|
92
|
+
|
|
93
|
+
- **Consistent behavior:** All commands agree on catalog root
|
|
94
|
+
- **No shadow catalogs:** `add` requires `init` first, eliminating invisible items
|
|
95
|
+
- **Simpler mental model:** One sentinel, one function, one behavior
|
|
96
|
+
- **Aligns with ADR-0027:** Uses the established `config.yaml` sentinel
|
|
97
|
+
|
|
98
|
+
### Trade-offs
|
|
99
|
+
|
|
100
|
+
- **UNMANAGED_STAC no longer auto-detected:** Users must explicitly init/adopt
|
|
101
|
+
- **`add` no longer auto-creates:** Requires explicit `init` first (git-style)
|
|
102
|
+
|
|
103
|
+
### Migration
|
|
104
|
+
|
|
105
|
+
None required. Pre-v1.0 with minimal users. Existing managed catalogs already have `.portolan/config.yaml`.
|
|
106
|
+
|
|
107
|
+
## Alternatives Considered
|
|
108
|
+
|
|
109
|
+
### Option A: Use `catalog.json` as sentinel everywhere
|
|
110
|
+
|
|
111
|
+
**Rejected:** Would still allow "unmanaged" catalogs that only partially work. The root cause is supporting two modes (managed/unmanaged) inconsistently.
|
|
112
|
+
|
|
113
|
+
### Option B: Current-directory-only (no walking up)
|
|
114
|
+
|
|
115
|
+
**Rejected:** Breaks git-style UX that users expect. Commands should work from subdirectories.
|
|
116
|
+
|
|
117
|
+
### Option C: Distinguish managed/unmanaged with two-tier behavior
|
|
118
|
+
|
|
119
|
+
**Rejected:** More complex, doesn't eliminate the inconsistency problem, and UNMANAGED_STAC was never fully supported anyway.
|
|
120
|
+
|
|
121
|
+
## References
|
|
122
|
+
|
|
123
|
+
- [ADR-0027: Unified config.yaml as Sentinel](0027-unified-config-yaml-sentinel.md)
|
|
124
|
+
- [ADR-0023: STAC Structure Separation](0023-stac-structure-separation.md)
|
|
125
|
+
- [GitHub Issue #162](https://github.com/portolan-sdi/portolan-cli/issues/162)
|
|
@@ -95,6 +95,87 @@ def detect_state(path: Path) -> CatalogState:
|
|
|
95
95
|
return CatalogState.FRESH
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
def find_catalog_root(
|
|
99
|
+
start_path: Path | None = None,
|
|
100
|
+
*,
|
|
101
|
+
require_operational: bool = True,
|
|
102
|
+
) -> Path | None:
|
|
103
|
+
"""Find the catalog root by walking up from the given path.
|
|
104
|
+
|
|
105
|
+
Searches for a managed Portolan catalog starting from start_path (or cwd if None)
|
|
106
|
+
and walking up parent directories. This provides git-style behavior
|
|
107
|
+
where commands work from any subdirectory within a catalog.
|
|
108
|
+
|
|
109
|
+
Per ADR-0029, this uses .portolan/config.yaml as the primary sentinel,
|
|
110
|
+
unifying detection across all CLI commands. By default (require_operational=True),
|
|
111
|
+
it also requires at least one operational file (catalog.json or state.json)
|
|
112
|
+
to avoid detecting half-initialized repos.
|
|
113
|
+
|
|
114
|
+
Security: Limited to MAX_CATALOG_SEARCH_DEPTH levels to prevent
|
|
115
|
+
traversing to filesystem root where a malicious .portolan might exist.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
start_path: Starting directory for search (defaults to cwd).
|
|
119
|
+
require_operational: If True (default), require .portolan/config.yaml
|
|
120
|
+
AND at least one of (catalog.json, .portolan/state.json) to exist.
|
|
121
|
+
Set to False during init_catalog() when creating a new catalog
|
|
122
|
+
where config.yaml is written before catalog.json.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Path to catalog root if found, None otherwise.
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
>>> find_catalog_root(Path("/my-catalog/collection/item"))
|
|
129
|
+
PosixPath('/my-catalog')
|
|
130
|
+
|
|
131
|
+
>>> find_catalog_root(Path("/no-catalog-here"))
|
|
132
|
+
None
|
|
133
|
+
|
|
134
|
+
>>> find_catalog_root() # Uses current working directory
|
|
135
|
+
PosixPath('/my-catalog')
|
|
136
|
+
|
|
137
|
+
>>> # During init, check for config.yaml only (catalog.json not yet written)
|
|
138
|
+
>>> find_catalog_root(start_path, require_operational=False)
|
|
139
|
+
"""
|
|
140
|
+
from portolan_cli.constants import MAX_CATALOG_SEARCH_DEPTH
|
|
141
|
+
|
|
142
|
+
def _is_catalog_root(path: Path) -> bool:
|
|
143
|
+
"""Check if path is a valid catalog root."""
|
|
144
|
+
config_yaml = path / ".portolan" / "config.yaml"
|
|
145
|
+
if not config_yaml.exists():
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
if not require_operational:
|
|
149
|
+
# During init, config.yaml alone is sufficient
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
# Require operational file: catalog.json at root OR state.json in .portolan
|
|
153
|
+
catalog_json = path / "catalog.json"
|
|
154
|
+
state_json = path / ".portolan" / "state.json"
|
|
155
|
+
return catalog_json.exists() or state_json.exists()
|
|
156
|
+
|
|
157
|
+
# Handle non-existent paths gracefully
|
|
158
|
+
if start_path is not None and not start_path.exists():
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
current = (start_path or Path.cwd()).resolve()
|
|
162
|
+
depth = 0
|
|
163
|
+
|
|
164
|
+
# Walk up until we find a valid catalog root, hit filesystem root, or exceed depth
|
|
165
|
+
while current != current.parent and depth < MAX_CATALOG_SEARCH_DEPTH:
|
|
166
|
+
if _is_catalog_root(current):
|
|
167
|
+
return current
|
|
168
|
+
current = current.parent
|
|
169
|
+
depth += 1
|
|
170
|
+
|
|
171
|
+
# Check the root directory itself (only if within depth limit)
|
|
172
|
+
if depth < MAX_CATALOG_SEARCH_DEPTH:
|
|
173
|
+
if _is_catalog_root(current):
|
|
174
|
+
return current
|
|
175
|
+
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
|
|
98
179
|
# Keep legacy exception for backward compatibility
|
|
99
180
|
class CatalogExistsError(Exception):
|
|
100
181
|
"""Raised when attempting to initialize a catalog that already exists.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Clean command for removing Portolan metadata while preserving data files.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to remove all Portolan-generated metadata
|
|
4
|
+
from a catalog while preserving user data files. Essential for testing
|
|
5
|
+
workflows where you want to reset a catalog to a "clean slate" state.
|
|
6
|
+
|
|
7
|
+
Files removed (metadata only):
|
|
8
|
+
- .portolan/ directory (entire thing)
|
|
9
|
+
- catalog.json (with "type": "Catalog")
|
|
10
|
+
- */collection.json (with "type": "Collection")
|
|
11
|
+
- */*/item.json or similar with "type": "Feature" (STAC items)
|
|
12
|
+
- */versions.json (Portolan-specific schema)
|
|
13
|
+
|
|
14
|
+
Files preserved (all data):
|
|
15
|
+
- .parquet, .tif, .gpkg, .geojson, .shp, etc.
|
|
16
|
+
- ANY file that isn't STAC metadata
|
|
17
|
+
- Non-STAC JSON files (checked by "type" field)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import shutil
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from portolan_cli.constants import PORTOLAN_DIR
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_stac_metadata(path: Path) -> bool:
|
|
30
|
+
"""Check if a JSON file is STAC metadata (safe to remove).
|
|
31
|
+
|
|
32
|
+
STAC metadata files have a "type" field with one of:
|
|
33
|
+
- "Catalog" (root catalog.json)
|
|
34
|
+
- "Collection" (collection.json)
|
|
35
|
+
- "Feature" (item.json - STAC items are GeoJSON Features)
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
path: Path to the file to check.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if the file is STAC metadata, False otherwise.
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
>>> is_stac_metadata(Path("catalog.json")) # has type=Catalog
|
|
45
|
+
True
|
|
46
|
+
>>> is_stac_metadata(Path("style.json")) # non-STAC JSON
|
|
47
|
+
False
|
|
48
|
+
>>> is_stac_metadata(Path("data.parquet"))
|
|
49
|
+
False
|
|
50
|
+
"""
|
|
51
|
+
# Only check JSON files
|
|
52
|
+
if path.suffix.lower() != ".json":
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
data = json.loads(path.read_text())
|
|
57
|
+
if not isinstance(data, dict):
|
|
58
|
+
return False
|
|
59
|
+
stac_type = data.get("type")
|
|
60
|
+
return stac_type in ("Catalog", "Collection", "Feature")
|
|
61
|
+
except (json.JSONDecodeError, OSError):
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def is_versions_json(path: Path) -> bool:
|
|
66
|
+
"""Check if a file is versions.json (Portolan-specific, safe to remove).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
path: Path to the file to check.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
True if the file is named versions.json, False otherwise.
|
|
73
|
+
"""
|
|
74
|
+
return path.name == "versions.json"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def collect_files_to_remove(catalog_root: Path) -> tuple[list[Path], list[Path]]:
|
|
78
|
+
"""Collect all metadata files and directories to remove from a catalog.
|
|
79
|
+
|
|
80
|
+
Walks the catalog structure and identifies:
|
|
81
|
+
1. .portolan/ directory (always removed entirely)
|
|
82
|
+
2. STAC metadata files (catalog.json, collection.json, item.json with proper type)
|
|
83
|
+
3. versions.json files (Portolan-specific)
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
catalog_root: Path to the catalog root directory.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Tuple of (files_to_remove, directories_to_remove).
|
|
90
|
+
Files are sorted for deterministic output.
|
|
91
|
+
"""
|
|
92
|
+
files_to_remove: list[Path] = []
|
|
93
|
+
directories_to_remove: list[Path] = []
|
|
94
|
+
|
|
95
|
+
# Always remove .portolan directory if it exists
|
|
96
|
+
portolan_dir = catalog_root / PORTOLAN_DIR
|
|
97
|
+
if portolan_dir.exists() and portolan_dir.is_dir():
|
|
98
|
+
directories_to_remove.append(portolan_dir)
|
|
99
|
+
|
|
100
|
+
# Walk the catalog and collect files
|
|
101
|
+
for path in catalog_root.rglob("*"):
|
|
102
|
+
# Skip .portolan directory contents (we're removing the whole dir)
|
|
103
|
+
if PORTOLAN_DIR in path.parts:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if path.is_file():
|
|
107
|
+
# Check for STAC metadata
|
|
108
|
+
if is_stac_metadata(path):
|
|
109
|
+
files_to_remove.append(path)
|
|
110
|
+
# Check for versions.json
|
|
111
|
+
elif is_versions_json(path):
|
|
112
|
+
files_to_remove.append(path)
|
|
113
|
+
|
|
114
|
+
# Sort files for deterministic output
|
|
115
|
+
files_to_remove.sort()
|
|
116
|
+
|
|
117
|
+
return files_to_remove, directories_to_remove
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def remove_empty_directories(catalog_root: Path, removed_files: list[Path]) -> list[Path]:
|
|
121
|
+
"""Remove directories that became empty after file removal.
|
|
122
|
+
|
|
123
|
+
Walks bottom-up from the deepest directories and removes any that are empty.
|
|
124
|
+
Stops at the catalog root.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
catalog_root: Path to the catalog root directory.
|
|
128
|
+
removed_files: List of files that were removed.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of directories that were removed.
|
|
132
|
+
"""
|
|
133
|
+
directories_removed: list[Path] = []
|
|
134
|
+
|
|
135
|
+
# Get unique parent directories of removed files, sorted deepest first
|
|
136
|
+
parent_dirs = set()
|
|
137
|
+
for file_path in removed_files:
|
|
138
|
+
parent = file_path.parent
|
|
139
|
+
while parent != catalog_root and parent != catalog_root.parent:
|
|
140
|
+
parent_dirs.add(parent)
|
|
141
|
+
parent = parent.parent
|
|
142
|
+
|
|
143
|
+
# Sort by depth (deepest first)
|
|
144
|
+
sorted_dirs = sorted(parent_dirs, key=lambda p: len(p.parts), reverse=True)
|
|
145
|
+
|
|
146
|
+
for dir_path in sorted_dirs:
|
|
147
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
148
|
+
# Check if directory is empty using lazy iteration (no list allocation)
|
|
149
|
+
if next(dir_path.iterdir(), None) is None:
|
|
150
|
+
try:
|
|
151
|
+
dir_path.rmdir()
|
|
152
|
+
except OSError:
|
|
153
|
+
# On Windows, rmdir fails if the directory is the process CWD.
|
|
154
|
+
# This is a best-effort cleanup, so skip and continue.
|
|
155
|
+
continue
|
|
156
|
+
directories_removed.append(dir_path)
|
|
157
|
+
|
|
158
|
+
return directories_removed
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def clean_catalog(
|
|
162
|
+
catalog_root: Path,
|
|
163
|
+
*,
|
|
164
|
+
dry_run: bool = False,
|
|
165
|
+
) -> tuple[list[Path], list[Path], int]:
|
|
166
|
+
"""Remove all Portolan metadata from a catalog while preserving data.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
catalog_root: Path to the catalog root directory.
|
|
170
|
+
dry_run: If True, only report what would be removed without deleting.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Tuple of (files_removed, directories_removed, data_files_preserved).
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
OSError: If file deletion fails (permission errors, etc.).
|
|
177
|
+
"""
|
|
178
|
+
files_to_remove, directories_to_remove = collect_files_to_remove(catalog_root)
|
|
179
|
+
|
|
180
|
+
# Count data files (files that are NOT being removed)
|
|
181
|
+
# Use a set for O(1) membership checks instead of O(n) list scan
|
|
182
|
+
files_to_remove_set = set(files_to_remove)
|
|
183
|
+
data_files = 0
|
|
184
|
+
for path in catalog_root.rglob("*"):
|
|
185
|
+
if path.is_file() and PORTOLAN_DIR not in path.parts:
|
|
186
|
+
if path not in files_to_remove_set:
|
|
187
|
+
data_files += 1
|
|
188
|
+
|
|
189
|
+
if dry_run:
|
|
190
|
+
return files_to_remove, directories_to_remove, data_files
|
|
191
|
+
|
|
192
|
+
# Actually remove files
|
|
193
|
+
files_removed: list[Path] = []
|
|
194
|
+
for file_path in files_to_remove:
|
|
195
|
+
if file_path.exists():
|
|
196
|
+
file_path.unlink()
|
|
197
|
+
files_removed.append(file_path)
|
|
198
|
+
|
|
199
|
+
# Remove .portolan directory
|
|
200
|
+
directories_removed: list[Path] = []
|
|
201
|
+
for dir_path in directories_to_remove:
|
|
202
|
+
if dir_path.exists():
|
|
203
|
+
shutil.rmtree(dir_path)
|
|
204
|
+
directories_removed.append(dir_path)
|
|
205
|
+
|
|
206
|
+
# Clean up empty directories
|
|
207
|
+
empty_dirs_removed = remove_empty_directories(catalog_root, files_removed)
|
|
208
|
+
directories_removed.extend(empty_dirs_removed)
|
|
209
|
+
|
|
210
|
+
return files_removed, directories_removed, data_files
|