dbdocs 1.0.0__tar.gz → 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {dbdocs-1.0.0 → dbdocs-1.0.1}/PKG-INFO +1 -1
  2. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/config.py +21 -2
  3. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/builder.py +14 -7
  4. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/deploy.py +17 -0
  5. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/inject.py +3 -3
  6. {dbdocs-1.0.0 → dbdocs-1.0.1}/.gitignore +0 -0
  7. {dbdocs-1.0.0 → dbdocs-1.0.1}/LICENSE +0 -0
  8. {dbdocs-1.0.0 → dbdocs-1.0.1}/README.md +0 -0
  9. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/__init__.py +0 -0
  10. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/__main__.py +0 -0
  11. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/cli/__init__.py +0 -0
  12. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/cli/main.py +0 -0
  13. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/__init__.py +0 -0
  14. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/artifacts.py +0 -0
  15. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/exceptions.py +0 -0
  16. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/core/log.py +0 -0
  17. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/__init__.py +0 -0
  18. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/_sqlglot_lineage.py +0 -0
  19. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/column_lineage.py +0 -0
  20. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/erd.py +0 -0
  21. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/erd_json.py +0 -0
  22. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/graph.py +0 -0
  23. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/extract/nodes.py +0 -0
  24. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/main.py +0 -0
  25. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/__init__.py +0 -0
  26. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/app.js +0 -0
  27. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/favicon.svg +0 -0
  28. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/graph/index.css +0 -0
  29. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/graph/index.js +0 -0
  30. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/style.css +0 -0
  31. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/vendor/marked.min.js +0 -0
  32. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/assets/vendor/minisearch.min.js +0 -0
  33. {dbdocs-1.0.0 → dbdocs-1.0.1}/dbdocs/site/bundle/index.html +0 -0
  34. {dbdocs-1.0.0 → dbdocs-1.0.1}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbdocs
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Alternative dbt docs site: Catalog + ERD + column-level lineage
5
5
  Project-URL: Homepage, https://github.com/datnguye/dbt-docs
6
6
  Project-URL: Repository, https://github.com/datnguye/dbt-docs
@@ -8,6 +8,23 @@ from dbdocs.core.exceptions import DbDocsConfigError
8
8
  DEFAULT_CONFIG_FILENAME = "dbdocs.yml"
9
9
 
10
10
 
11
+ def _resolve_within_cwd(value: str, field_name: str) -> Path:
12
+ """Resolve *value* against cwd; reject a relative path that escapes the cwd.
13
+
14
+ An absolute *value* is resolved as-is (the caller's deliberate choice — this
15
+ is documented behaviour and must not be rejected). A relative *value* is
16
+ resolved against ``Path.cwd()`` and then checked: if the result is not
17
+ inside (or equal to) the cwd, ``DbDocsConfigError`` is raised.
18
+ """
19
+ base = Path.cwd().resolve()
20
+ candidate = Path(value)
21
+ resolved = candidate if candidate.is_absolute() else (base / candidate)
22
+ resolved = resolved.resolve()
23
+ if not candidate.is_absolute() and resolved != base and base not in resolved.parents:
24
+ raise DbDocsConfigError(f"{field_name} {value!r} escapes the project directory ({base}).")
25
+ return resolved
26
+
27
+
11
28
  @dataclass
12
29
  class DbDocsConfig:
13
30
  """Site configuration for a dbdocs build.
@@ -103,8 +120,9 @@ class DbDocsConfig:
103
120
  directory **at access time** — this is intentional and must stay aligned
104
121
  with dbterd's ``DbtErd``, which also reads artifacts from ``./target``
105
122
  relative to the cwd. An absolute ``target_dir`` is returned unchanged.
123
+ A relative path that would escape the cwd raises ``DbDocsConfigError``.
106
124
  """
107
- return str(Path.cwd() / self.target_dir)
125
+ return str(_resolve_within_cwd(self.target_dir, "target_dir"))
108
126
 
109
127
  @property
110
128
  def output_path(self) -> str:
@@ -113,5 +131,6 @@ class DbDocsConfig:
113
131
  Resolved against the cwd at access time, mirroring ``target_path`` — a
114
132
  relative ``output_dir`` follows the working directory, an absolute one is
115
133
  returned unchanged.
134
+ A relative path that would escape the cwd raises ``DbDocsConfigError``.
116
135
  """
117
- return str(Path.cwd() / self.output_dir)
136
+ return str(_resolve_within_cwd(self.output_dir, "output_dir"))
@@ -9,11 +9,12 @@ data → write ``index.html`` + a debug ``dbdocs-data.json``.
9
9
  import json
10
10
  from datetime import datetime
11
11
  from pathlib import Path
12
- from shutil import copytree
12
+ from shutil import copytree, rmtree
13
13
  from typing import Any
14
14
 
15
15
  from dbdocs.core.artifacts import adapter_type, load_artifacts
16
- from dbdocs.core.config import DbDocsConfig
16
+ from dbdocs.core.config import DbDocsConfig, _resolve_within_cwd
17
+ from dbdocs.core.exceptions import DbDocsConfigError
17
18
  from dbdocs.core.log import logger
18
19
  from dbdocs.extract.column_lineage import ColumnLineageExtractor
19
20
  from dbdocs.extract.erd import build_erd, build_erd_data
@@ -66,12 +67,16 @@ class ReportBuilder:
66
67
  def _read_readme(self) -> str:
67
68
  """The project README markdown (rendered on the overview), or ``""``.
68
69
 
69
- ``config.readme`` is a path relative to the cwd; a missing file or an
70
- empty config value just yields no README section.
70
+ ``config.readme`` is a path relative to the cwd; a missing file, an
71
+ empty config value, or a relative path that escapes the cwd all yield
72
+ no README section (fail-soft — a bad README must never sink generate).
71
73
  """
72
74
  if not self.config.readme:
73
75
  return ""
74
- path = Path(self.config.readme)
76
+ try:
77
+ path = _resolve_within_cwd(self.config.readme, "readme")
78
+ except DbDocsConfigError:
79
+ return ""
75
80
  try:
76
81
  return path.read_text(encoding="utf-8")
77
82
  except OSError:
@@ -109,7 +114,9 @@ class ReportBuilder:
109
114
  def generate(self, output_dir: "str | None" = None) -> str:
110
115
  """Render the site into ``output_dir`` (or config's). Returns its path."""
111
116
  out = Path(output_dir) if output_dir else Path(self.config.output_path)
112
- out.mkdir(parents=True, exist_ok=True)
117
+ if out.exists():
118
+ rmtree(out)
119
+ out.mkdir(parents=True)
113
120
  copytree(src=BUNDLE_DIR, dst=out, dirs_exist_ok=True)
114
121
 
115
122
  data = self.build_data()
@@ -117,7 +124,7 @@ class ReportBuilder:
117
124
  index.write_text(inject(index.read_text(encoding="utf-8"), data), encoding="utf-8")
118
125
  # Compact, not indented — keeps the debug dump cheap on large projects.
119
126
  (out / "dbdocs-data.json").write_text(
120
- json.dumps(data, separators=(",", ":"), default=self._json_default),
127
+ json.dumps(data, separators=(",", ":"), sort_keys=True, default=self._json_default),
121
128
  encoding="utf-8",
122
129
  )
123
130
 
@@ -14,6 +14,7 @@ the output dir onto a ``gh-pages`` branch and pushes it.
14
14
  """
15
15
 
16
16
  import json
17
+ import re
17
18
  import subprocess
18
19
  from pathlib import Path
19
20
  from shutil import copytree, rmtree
@@ -25,6 +26,17 @@ from dbdocs.site.builder import ReportBuilder
25
26
 
26
27
  VERSIONS_FILE = "versions.json"
27
28
 
29
+ #: Allows only alphanumerics, dots, underscores, and hyphens — a safe single path segment.
30
+ _SAFE_SEGMENT = re.compile(r"^[A-Za-z0-9._-]+$")
31
+
32
+
33
+ def _validate_segment(value: str, kind: str) -> None:
34
+ """Raise :class:`DeployError` if *value* is not a safe single path segment."""
35
+ if not _SAFE_SEGMENT.match(value) or value in {".", ".."}:
36
+ raise DeployError(
37
+ f"Invalid {kind} {value!r}: must match ^[A-Za-z0-9._-]+$ and not be '.' or '..'."
38
+ )
39
+
28
40
 
29
41
  def _read_versions(output_root: Path) -> list:
30
42
  path = output_root / VERSIONS_FILE
@@ -57,6 +69,9 @@ def deploy(
57
69
  title: "str | None" = None,
58
70
  ) -> str:
59
71
  """Generate ``version`` into the output root and update the version index."""
72
+ _validate_segment(version, "version")
73
+ if alias is not None:
74
+ _validate_segment(alias, "alias")
60
75
  output_root = Path(config.output_path)
61
76
  version_dir = output_root / version
62
77
  if version_dir.exists():
@@ -85,6 +100,7 @@ def delete(config: DbDocsConfig, version: str, push: bool = False) -> None:
85
100
 
86
101
  Raises :class:`DeployError` if the version isn't deployed.
87
102
  """
103
+ _validate_segment(version, "version")
88
104
  output_root = Path(config.output_path)
89
105
  versions = _read_versions(output_root)
90
106
  entry = next((v for v in versions if v.get("version") == version), None)
@@ -95,6 +111,7 @@ def delete(config: DbDocsConfig, version: str, push: bool = False) -> None:
95
111
  if version_dir.exists():
96
112
  rmtree(version_dir)
97
113
  for alias in entry.get("aliases", []):
114
+ _validate_segment(alias, "alias")
98
115
  alias_dir = output_root / alias
99
116
  if alias_dir.exists():
100
117
  rmtree(alias_dir)
@@ -16,9 +16,9 @@ INJECT_MARKER = "<!-- DBDOCS_DATA -->"
16
16
 
17
17
  def data_script(data: dict) -> str:
18
18
  """The ``<script>`` tag that sets ``window.dbdocsData`` from ``data``."""
19
- payload = base64.b64encode(json.dumps(data, separators=(",", ":")).encode("utf-8")).decode(
20
- "ascii"
21
- )
19
+ payload = base64.b64encode(
20
+ json.dumps(data, separators=(",", ":"), sort_keys=True).encode("utf-8")
21
+ ).decode("ascii")
22
22
  return f'<script>window.dbdocsData = JSON.parse(atob("{payload}"));</script>'
23
23
 
24
24
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes