1panel-toolkit 0.3.0__py3-none-any.whl

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 (47) hide show
  1. 1panel_toolkit-0.3.0.dist-info/METADATA +407 -0
  2. 1panel_toolkit-0.3.0.dist-info/RECORD +47 -0
  3. 1panel_toolkit-0.3.0.dist-info/WHEEL +4 -0
  4. 1panel_toolkit-0.3.0.dist-info/entry_points.txt +2 -0
  5. 1panel_toolkit-0.3.0.dist-info/licenses/LICENSE +21 -0
  6. p1toolkit/__init__.py +3 -0
  7. p1toolkit/checks/__init__.py +21 -0
  8. p1toolkit/checks/compose.py +188 -0
  9. p1toolkit/checks/forms.py +129 -0
  10. p1toolkit/checks/hygiene.py +73 -0
  11. p1toolkit/checks/i18n.py +140 -0
  12. p1toolkit/checks/lessons.py +111 -0
  13. p1toolkit/checks/structure.py +140 -0
  14. p1toolkit/checks/substance.py +55 -0
  15. p1toolkit/cli.py +890 -0
  16. p1toolkit/config.py +114 -0
  17. p1toolkit/dependencies.py +218 -0
  18. p1toolkit/deploy.py +302 -0
  19. p1toolkit/depth.py +414 -0
  20. p1toolkit/errors.py +18 -0
  21. p1toolkit/evidence.py +523 -0
  22. p1toolkit/fix.py +394 -0
  23. p1toolkit/gen.py +728 -0
  24. p1toolkit/lifecycle.py +143 -0
  25. p1toolkit/loader.py +110 -0
  26. p1toolkit/logo.py +174 -0
  27. p1toolkit/model.py +71 -0
  28. p1toolkit/net.py +81 -0
  29. p1toolkit/panel.py +154 -0
  30. p1toolkit/patterns/__init__.py +250 -0
  31. p1toolkit/patterns/catalog.py +271 -0
  32. p1toolkit/patterns/data/patterns.json +724 -0
  33. p1toolkit/patterns/extract.py +402 -0
  34. p1toolkit/remote.py +554 -0
  35. p1toolkit/report.py +96 -0
  36. p1toolkit/resolve/__init__.py +554 -0
  37. p1toolkit/resolve/analyze.py +329 -0
  38. p1toolkit/resolve/sources.py +306 -0
  39. p1toolkit/rules/__init__.py +99 -0
  40. p1toolkit/rules/data/decisions.yaml +132 -0
  41. p1toolkit/rules/data/dependencies.yaml +113 -0
  42. p1toolkit/rules/data/envkeys.yaml +19 -0
  43. p1toolkit/rules/data/form-design.yaml +59 -0
  44. p1toolkit/rules/data/i18n.yaml +354 -0
  45. p1toolkit/rules/data/lessons.yaml +84 -0
  46. p1toolkit/rules/data/store.yaml +171 -0
  47. p1toolkit/selftest.py +211 -0
@@ -0,0 +1,140 @@
1
+ """Package structure: data.yml layout, required metadata, version dirs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from .. import rules
8
+ from ..model import Package
9
+ from ..report import Report
10
+
11
+
12
+ def run(package: Package, report: Report, strict: bool = False) -> None:
13
+ assets(package, report)
14
+ store = rules.store()
15
+ allowed = set(store.get("root_allowed_keys") or [])
16
+ warn_keys = set(store.get("root_warn_keys") or [])
17
+ required = list(store.get("root_required_additional") or [])
18
+ recommended = list(store.get("root_recommended_additional") or [])
19
+
20
+ root_keys = set(package.app)
21
+ for key in sorted(root_keys - allowed):
22
+ if key in warn_keys:
23
+ report.warn(
24
+ "P001",
25
+ f"root data.yml has non-standard top-level key {key!r}",
26
+ "data.yml",
27
+ )
28
+ else:
29
+ report.fail(
30
+ "P001",
31
+ f"root data.yml has unsupported top-level key {key!r}",
32
+ "data.yml",
33
+ )
34
+ for key in sorted(allowed - root_keys):
35
+ if key != "tags":
36
+ report.warn("P002", f"root data.yml is missing top-level {key!r}", "data.yml")
37
+
38
+ additional = package.additional
39
+ for key in required:
40
+ if key not in additional:
41
+ report.fail("P003", f"root additionalProperties.{key} is required", "data.yml")
42
+ for key in recommended:
43
+ if key not in additional:
44
+ report.warn(
45
+ "P004", f"root additionalProperties.{key} is recommended", "data.yml"
46
+ )
47
+
48
+ architectures = additional.get("architectures")
49
+ if architectures is not None and not (
50
+ isinstance(architectures, list) and architectures
51
+ ):
52
+ report.fail(
53
+ "P005",
54
+ "additionalProperties.architectures must be a non-empty list",
55
+ "data.yml",
56
+ )
57
+
58
+ tags = additional.get("tags")
59
+ if tags:
60
+ allowed_tags = rules.allowed_tags()
61
+ unknown = [t for t in tags if t not in allowed_tags]
62
+ if unknown:
63
+ report.warn(
64
+ "P006",
65
+ f"additionalProperties.tags not seen in the store survey: {unknown} "
66
+ "(allowed if intentional, but check for a typo or a localized value)",
67
+ "data.yml",
68
+ )
69
+
70
+ app_type = additional.get("type")
71
+ if app_type and str(app_type) not in set(store.get("app_types") or []):
72
+ report.warn(
73
+ "P007",
74
+ f"additionalProperties.type {app_type!r} is outside the documented set "
75
+ f"{store.get('app_types')}",
76
+ "data.yml",
77
+ )
78
+
79
+ if not package.versions:
80
+ report.fail(
81
+ "P010",
82
+ "no version directory found (expected e.g. 'latest/' with docker-compose.yml)",
83
+ )
84
+ return
85
+
86
+ version_allowed = set(store.get("version_allowed_keys") or [])
87
+ for version in package.versions:
88
+ label = f"{version.name}/data.yml"
89
+ if not version.dir.joinpath("data.yml").exists():
90
+ report.fail("P011", f"{version.name}/ is missing data.yml", label)
91
+ if not version.compose_text:
92
+ report.fail(
93
+ "P012", f"{version.name}/ is missing docker-compose.yml", label
94
+ )
95
+ extra = set(version.data) - version_allowed
96
+ if extra:
97
+ report.fail(
98
+ "P013",
99
+ f"{label} has unsupported top-level keys: {sorted(extra)}",
100
+ label,
101
+ )
102
+ if isinstance(version.data.get("formFields"), list):
103
+ report.fail(
104
+ "P014",
105
+ f"{label} declares formFields at the top level; it belongs under additionalProperties",
106
+ label,
107
+ )
108
+ if "architectures" in version.data or "architectures" in version.additional:
109
+ report.fail(
110
+ "P015",
111
+ f"{label} must not declare architectures (root data.yml owns it)",
112
+ label,
113
+ )
114
+ if not version.form_fields:
115
+ report.warn("P016", f"{label} declares no formFields", label)
116
+
117
+
118
+ def assets(package: Package, report: Report) -> None:
119
+ """Logo check lives separately so tests can call it in isolation."""
120
+ logo = package.root / "logo.png"
121
+ if not logo.exists():
122
+ report.fail("P020", "logo.png is missing", "logo.png")
123
+ return
124
+ size = _png_size(logo)
125
+ if size is None:
126
+ report.warn("P021", "logo.png is not a readable PNG", "logo.png")
127
+ return
128
+ if size != (180, 180):
129
+ report.warn(
130
+ "P022",
131
+ f"logo.png is {size[0]}x{size[1]}; the store default is 180x180",
132
+ "logo.png",
133
+ )
134
+
135
+
136
+ def _png_size(path: Path) -> tuple[int, int] | None:
137
+ raw = path.read_bytes()[:24]
138
+ if len(raw) < 24 or raw[:8] != b"\x89PNG\r\n\x1a\n":
139
+ return None
140
+ return int.from_bytes(raw[16:20], "big"), int.from_bytes(raw[20:24], "big")
@@ -0,0 +1,55 @@
1
+ """Substance checks: does this package look adapted, or just reskinned?
2
+
3
+ These are the cheap, corpus-free half of ``p1 depth``. They catch the package
4
+ that was generated by filling a template and never thinking about the app.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from ..depth import profile_app
10
+ from ..model import Package
11
+ from ..report import Report
12
+
13
+
14
+ def run(package: Package, report: Report, strict: bool = False) -> None:
15
+ profile = profile_app(package.root)
16
+ if profile is None:
17
+ return
18
+
19
+ if not profile.app_env_keys:
20
+ report.warn(
21
+ "S001",
22
+ "compose 里没有任何应用自有环境变量;官方语料里平均每个应用有 6 个,"
23
+ "确认这个应用是真的不需要配置,还是照模板填的",
24
+ "docker-compose.yml",
25
+ )
26
+ if not profile.mount_targets:
27
+ report.warn(
28
+ "S002",
29
+ "compose 里没有任何持久化挂载;除非应用确实无状态,否则卸载/升级会丢数据",
30
+ "docker-compose.yml",
31
+ )
32
+ # A package that ships source evidence had its ports traced back to an
33
+ # upstream file, so "looks like a template default" would be noise.
34
+ traced = (package.root / "source-evidence.json").exists()
35
+ if (
36
+ profile.port_pairs
37
+ and not traced
38
+ and not any(
39
+ port not in {"80", "8080", "3000", "8000", "5000"}
40
+ for port in profile.port_pairs
41
+ )
42
+ ):
43
+ report.warn(
44
+ "S003",
45
+ f"容器内端口是常见模板默认值 {profile.port_pairs};"
46
+ "官方语料里这个维度有 359 种取值,请回到上游 EXPOSE / 官方 compose 核对",
47
+ "docker-compose.yml",
48
+ )
49
+ if profile.services == 1 and not profile.high_signal_keys:
50
+ report.info(
51
+ "S004",
52
+ "没有使用任何上游特有的运行时字段(security_opt / user / cap_add 等);"
53
+ "87% 的单服务应用也确实如此,仅供参考",
54
+ "docker-compose.yml",
55
+ )