Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/reference/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,18 @@ clean source checkout and a local LoopX release activate bundled providers
without separately installing a console script; catalog discovery remains
declarative and does not import the module.

Because discovery is declarative, a declared launch target is only shape-checked
until activation. The public smoke
`examples/extension-entrypoint-surface-smoke.py` is a repository-scoped coverage
guard for that gap rather than an activation check: without importing provider
code it resolves the declared `python_module`, the `entrypoint` console script,
each hook adapter `factory`, and each presentation `view_validator` for every
bundled and co-located manifest in this repository against the source tree, so a
renamed or removed entrypoint fails in the same change that removes it instead
of at the user's first activation. It does not cover an edited or third-party
manifest, and detection is structural: an attribute the scanner cannot see, for
example one installed through `getattr`, is reported as unresolved.

### Local executable locations

Successful executable install/upgrade and doctor operations save the selected
Expand Down
54 changes: 54 additions & 0 deletions examples/extension-entrypoint-surface-smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Guard the entrypoints every extension manifest declares.

Manifest loading is deliberately import-free, so ``python_module``, hook
``factory`` and presentation ``view_validator`` references are only checked for
shape. A renamed or removed object therefore survives manifest validation and
only fails when a user activates the extension. This smoke resolves every
declared reference in the bundled and co-located manifests against the
repository source tree, without importing provider code.
"""

from __future__ import annotations

from pathlib import Path

from loopx.extensions.entrypoint_surface import (
EntrypointKind,
render_report,
resolve_declared_entrypoints,
)


ROOT = Path(__file__).resolve().parents[1]

EXPECTED_KINDS = frozenset(kind.value for kind in EntrypointKind)

REQUIRED_KINDS = (
EntrypointKind.PYTHON_MODULE.value,
EntrypointKind.CONSOLE_SCRIPT.value,
EntrypointKind.HOOK_FACTORY.value,
EntrypointKind.VIEW_VALIDATOR.value,
)


def main() -> int:
report = resolve_declared_entrypoints(ROOT)
print(render_report(report))
if not report.ok:
return 1

assert report.manifest_count > 0, "expected bundled extension manifests"
assert report.entrypoints, "expected declared extension entrypoints"

kinds = {item.kind.value for item in report.entrypoints}
assert kinds <= EXPECTED_KINDS, kinds
for required in REQUIRED_KINDS:
assert required in kinds, f"no manifest declares a {required} entrypoint: {sorted(kinds)}"

print("extension-entrypoint-surface-smoke: ok")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading