datasecops-cli 0.5.8__tar.gz → 0.5.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/CHANGELOG.md +15 -0
  2. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/PKG-INFO +2 -2
  3. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/README.md +1 -1
  4. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/pyproject.toml +1 -1
  5. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/menus/development.py +9 -5
  6. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/menus/git_operations.py +40 -1
  7. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/dbt_runner.py +2 -0
  8. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/git_service.py +26 -0
  9. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/linting_service.py +15 -0
  10. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/.github/workflows/auto-tag.yml +0 -0
  11. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/.github/workflows/publish-cli.yml +0 -0
  12. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/.github/workflows/test.yml +0 -0
  13. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/.gitignore +0 -0
  14. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/DEVELOPMENT.md +0 -0
  15. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/LICENSE +0 -0
  16. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/docs/getting-started.md +0 -0
  17. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/docs/git-operations.md +0 -0
  18. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/docs/legacy.md +0 -0
  19. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/docs/legacy_plan_of_action.md +0 -0
  20. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/docs/mcp-server.md +0 -0
  21. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/mcp-servers.json +0 -0
  22. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/setup.ps1 +0 -0
  23. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/setup.sh +0 -0
  24. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/__init__.py +0 -0
  25. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/config.py +0 -0
  26. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/main.py +0 -0
  27. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/menus/__init__.py +0 -0
  28. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/menus/configuration.py +0 -0
  29. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/menus/downloads.py +0 -0
  30. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/models/__init__.py +0 -0
  31. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/models/git_helpers.py +0 -0
  32. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/models/project_config.py +0 -0
  33. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/__init__.py +0 -0
  34. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  35. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
  36. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
  37. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/download_service.py +0 -0
  38. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/skill_service.py +0 -0
  39. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/snowflake_service.py +0 -0
  40. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/services/upstream_service.py +0 -0
  41. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/utilities/__init__.py +0 -0
  42. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/utilities/display.py +0 -0
  43. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/utilities/file_utils.py +0 -0
  44. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/utilities/mcp.py +0 -0
  45. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  46. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_mcp/__init__.py +0 -0
  47. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_mcp/__main__.py +0 -0
  48. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_mcp/connection.py +0 -0
  49. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/src/datasecops_mcp/server.py +0 -0
  50. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/tests/__init__.py +0 -0
  51. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/tests/test_config.py +0 -0
  52. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/tests/test_file_utils.py +0 -0
  53. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/tests/test_main.py +0 -0
  54. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/tests/test_models.py +0 -0
  55. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/tests/test_version.py +0 -0
  56. {datasecops_cli-0.5.8 → datasecops_cli-0.5.10}/tests/test_yaml_utils.py +0 -0
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to the DataSecOps CLI are documented in this file.
4
4
 
5
+ ## [0.5.10] - 2026-06-26
6
+
7
+ ### Changed
8
+
9
+ - **Lint modified includes all uncommitted files** — "lint modified" and "fix modified" now detect staged changes, unstaged changes, and untracked files (previously only caught unstaged modifications).
10
+ - **Lint specific file accepts model names** — the "specific" lint option now accepts a model name (e.g. `stg_orders`) and resolves it to the SQL file path by searching the models directory recursively.
11
+ - **Reset branch from main** — new git menu option (10) to force-reset an environment branch (e.g. `test`) to match `origin/main`, removing all existing commits on that branch.
12
+ - **Deploy excludes main branch** — the deploy-to-environment menu no longer lists `main` as a target, preventing accidental force-pushes to the primary branch.
13
+
14
+ ## [0.5.9] - 2026-06-22
15
+
16
+ ### Fixed
17
+
18
+ - **`dbt seed` scoped to current project** — `seed` now passes `--select <project_name>` by default (matching `run`, `compile`, `build`, and `test`), preventing package seeds from being loaded.
19
+
5
20
  ## [0.5.8] - 2026-06-22
6
21
 
7
22
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasecops-cli
3
- Version: 0.5.8
3
+ Version: 0.5.10
4
4
  Summary: DataSecOps Framework CLI for Snowflake Native App
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -81,7 +81,7 @@ datasecops
81
81
  | Menu | Capabilities |
82
82
  |------|-------------|
83
83
  | **Development** | dbt run, build, test, lint (SQLFluff), deps, seed, compile, snapshot, freshness, docs |
84
- | **Git** | Branch create/checkout/delete, commit & push, rebase, squash, deploy to environment branches, cherry-pick |
84
+ | **Git** | Branch create/checkout/delete, commit & push, rebase, squash, deploy to environment branches, cherry-pick, reset branch from main |
85
85
  | **Downloads** | SQLFluff config, CI/CD pipelines (GitHub Actions / Azure DevOps), dbt packages, Cortex Code skills |
86
86
 
87
87
  ## Non-Interactive Mode (CI/CD)
@@ -62,7 +62,7 @@ datasecops
62
62
  | Menu | Capabilities |
63
63
  |------|-------------|
64
64
  | **Development** | dbt run, build, test, lint (SQLFluff), deps, seed, compile, snapshot, freshness, docs |
65
- | **Git** | Branch create/checkout/delete, commit & push, rebase, squash, deploy to environment branches, cherry-pick |
65
+ | **Git** | Branch create/checkout/delete, commit & push, rebase, squash, deploy to environment branches, cherry-pick, reset branch from main |
66
66
  | **Downloads** | SQLFluff config, CI/CD pipelines (GitHub Actions / Azure DevOps), dbt packages, Cortex Code skills |
67
67
 
68
68
  ## Non-Interactive Mode (CI/CD)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datasecops-cli"
7
- version = "0.5.8"
7
+ version = "0.5.10"
8
8
  description = "DataSecOps Framework CLI for Snowflake Native App"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -204,19 +204,23 @@ class DevelopmentMenu:
204
204
  complete_action()
205
205
  return
206
206
  if option == 1:
207
- changed = [f.file for f in self.git.get_changed_files()] if self.git else []
207
+ changed = self.git.get_all_modified_files() if self.git else []
208
208
  self.linting.lint_modified(fix=False, changed_files=changed)
209
209
  elif option == 2:
210
- changed = [f.file for f in self.git.get_changed_files()] if self.git else []
210
+ changed = self.git.get_all_modified_files() if self.git else []
211
211
  self.linting.lint_modified(fix=True, changed_files=changed)
212
212
  elif option == 3:
213
213
  self.linting.lint_file(fix=False)
214
214
  elif option == 4:
215
215
  self.linting.lint_file(fix=True)
216
216
  elif option == 5:
217
- path = get_input_string("Enter SQL file path (e.g. models/staging/stg_orders.sql): ")
218
- if path != "0":
219
- self.linting.lint_file(file_path=path, fix=False)
217
+ name = get_input_string("Enter model name or file path (e.g. stg_orders): ")
218
+ if name != "0":
219
+ path = name if name.endswith(".sql") else self.linting.resolve_model_path(name)
220
+ if path:
221
+ self.linting.lint_file(file_path=path, fix=False)
222
+ else:
223
+ error_line(f"Could not find model '{name}' in models/ directory")
220
224
  elif option == 6:
221
225
  if not self.downloads:
222
226
  error_line("Download service not available")
@@ -42,6 +42,8 @@ class GitOperationsMenu:
42
42
  self._cherry_pick_test()
43
43
  elif option == 9:
44
44
  self._deploy_prod()
45
+ elif option == 10:
46
+ self._reset_branch_from_main()
45
47
  self._menu()
46
48
  option = get_input_number("Choose an option: ")
47
49
 
@@ -57,6 +59,7 @@ class GitOperationsMenu:
57
59
  menu_option(7, "squash to test - Squash merge into test")
58
60
  menu_option(8, "remove from test - Remove a branch from test")
59
61
  menu_option(9, "deploy to prod - Deploy main to production")
62
+ menu_option(10, "reset branch - Force reset a branch from main")
60
63
  menu_option(0, "back - Return to main menu")
61
64
 
62
65
  def _branch_menu(self) -> None:
@@ -190,7 +193,7 @@ class GitOperationsMenu:
190
193
  display_action_header("Deploy to Environment")
191
194
 
192
195
  # Build environment list from framework config, filtering non-deployable environments
193
- excluded = {"unit-test", "ci", "local-dev", "prod", "test"}
196
+ excluded = {"unit-test", "ci", "local-dev", "prod", "test", "main"}
194
197
  environments = {}
195
198
  if self.source_control.environments:
196
199
  for env in self.source_control.environments:
@@ -289,3 +292,39 @@ class GitOperationsMenu:
289
292
  selected = commits[choice-1]
290
293
  self.git.revert_from_test(selected["sha"], selected["branch"])
291
294
  complete_action()
295
+
296
+ def _reset_branch_from_main(self) -> None:
297
+ display_action_header("Reset Branch from Main")
298
+
299
+ # Build list of resettable branches from environments + deployment_branches
300
+ excluded = {"main", "prod"}
301
+ branches = {}
302
+ if self.source_control.environments:
303
+ for env in self.source_control.environments:
304
+ if env.branch_name and env.branch_name.lower() not in excluded:
305
+ branches[env.branch_name] = env.branch_name
306
+
307
+ if not branches:
308
+ branches = {
309
+ k: v for k, v in self.deployment_branches.items()
310
+ if k.lower() not in excluded
311
+ }
312
+
313
+ # Always include test if not already present
314
+ if "test" not in branches:
315
+ branches["test"] = "test"
316
+
317
+ if not branches:
318
+ error_line("No branches available to reset")
319
+ complete_action()
320
+ return
321
+
322
+ selected = select_from_dict(branches, "branch to reset")
323
+ if selected == "back":
324
+ return
325
+
326
+ warning_line(f"This will FORCE RESET '{selected}' to match origin/main")
327
+ warning_line("All existing commits on that branch will be lost")
328
+ if get_input_true_false("Are you sure?", "n"):
329
+ self.git.reset_branch_from_main(selected)
330
+ complete_action()
@@ -104,6 +104,8 @@ class DbtRunner:
104
104
  args = [f"--target={self.target}"]
105
105
  if select:
106
106
  args += ["--select", select]
107
+ elif self.project_name:
108
+ args += ["--select", self.project_name]
107
109
  if full_refresh:
108
110
  args.append("--full-refresh")
109
111
  return self._run_command("seed", args)
@@ -30,6 +30,22 @@ class GitService:
30
30
  def get_new_files(self) -> list[GitCommitHelper]:
31
31
  return [GitCommitHelper(file=f) for f in self.repo.untracked_files]
32
32
 
33
+ def get_all_modified_files(self) -> list[str]:
34
+ """Get all files that differ from the last commit: unstaged, staged, and untracked."""
35
+ paths = set()
36
+ # Staged changes (index vs HEAD)
37
+ for d in self.repo.index.diff("HEAD"):
38
+ if d.change_type != "D" and d.a_path:
39
+ paths.add(d.a_path)
40
+ # Unstaged changes (working tree vs index)
41
+ for d in self.repo.index.diff(None):
42
+ if d.change_type != "D" and d.a_path:
43
+ paths.add(d.a_path)
44
+ # Untracked files
45
+ for f in self.repo.untracked_files:
46
+ paths.add(f)
47
+ return sorted(paths)
48
+
33
49
  def get_local_branches(self) -> dict[str, str]:
34
50
  return {b.name: b.name for b in self.repo.branches}
35
51
 
@@ -397,3 +413,13 @@ class GitService:
397
413
  if branch.name not in ("main", "test"):
398
414
  self.repo.delete_head(branch, force=True)
399
415
  success_line("Reset to main completed")
416
+
417
+ def reset_branch_from_main(self, branch_name: str) -> None:
418
+ """Force reset a remote branch to match origin/main."""
419
+ try:
420
+ self.repo.remotes.origin.fetch()
421
+ main_sha = self.repo.commit("origin/main").hexsha
422
+ self.repo.remotes.origin.push(refspec=f"{main_sha}:refs/heads/{branch_name}", force=True)
423
+ success_line(f"Reset '{branch_name}' to match main ({main_sha[:8]})")
424
+ except GitCommandError as e:
425
+ error_line(f"Failed to reset '{branch_name}': {e}")
@@ -88,6 +88,21 @@ class LintingService:
88
88
  warning_line(f"Missing packages: {', '.join(missing)}")
89
89
  return self.install_requirements(missing)
90
90
 
91
+ def resolve_model_path(self, name: str) -> str | None:
92
+ """Resolve a model name (e.g. 'stg_orders') to its SQL file path under models/."""
93
+ models_dir = self.project_dir / "models"
94
+ if not models_dir.exists():
95
+ return None
96
+ matches = list(models_dir.rglob(f"{name}.sql"))
97
+ if len(matches) == 1:
98
+ return str(matches[0].relative_to(self.project_dir))
99
+ if len(matches) > 1:
100
+ warning_line(f"Multiple matches for '{name}':")
101
+ for m in matches:
102
+ info_line(f" {m.relative_to(self.project_dir)}")
103
+ return None
104
+ return None
105
+
91
106
  def lint_file(self, file_path: str = None, fix: bool = False) -> subprocess.CompletedProcess:
92
107
  action = "fix" if fix else "lint"
93
108
  cmd = ["sqlfluff", action]
File without changes
File without changes