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
31 changes: 31 additions & 0 deletions tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,37 @@ def test_row_default_falls_back_to_install_command(repo):
assert row.install == "tool add widget@1.0.0"


def test_plugin_may_own_the_install_command(repo):
"""A per-package install command survives the index-wide template.

The template runs after every plugin hook, so without an explicit
precedence rule it silently overwrote whatever the plugin set -- and a
plugin that can shape every other part of the page but not this line has
no way to show a spec that differs per package.
"""
plugin = write_plugin(repo, '''
from xpkgindex.models import Identity
from xpkgindex.plugins import Plugin

class P(Plugin):
name = "installs"
def identity(self, raw, path):
return Identity.joined(raw.get("namespace", ""), raw.get("name", ""))
def on_package(self, pkg, raw):
if raw.get("namespace") == "alpha":
pkg.extensions.setdefault("_core", {})["install_command"] = \\
"native add widget#deadbeef"
''')
write_config(repo, plugins=[plugin])

site, _ = build(repo, offline=True)
by_ref = {p.identity.install_ref: p for p in site.packages}
assert by_ref["alpha.widget"].install_command == "native add widget#deadbeef"
# The package the plugin did not claim still gets the template, so opting
# one package in does not opt the whole index out.
assert by_ref["beta.widget"].install_command == "tool add beta.widget@1.0.0"


def test_blocks_and_facets_reach_the_model(repo):
plugin = write_plugin(repo, '''
from xpkgindex.models import Block, Facet, FacetValue, Identity
Expand Down
17 changes: 17 additions & 0 deletions xpkgindex/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,25 @@ def _check_slugs(packages: List[Package]) -> None:


def _install_commands(packages: List[Package], config: SiteConfig) -> None:
"""Fill in each package's install command, unless its plugin set one.

The template is an index-wide string, so it can only say the one thing
that is true of every package. That is the right default, but it cannot
express a command that varies per package -- and in an ecosystem whose
own tool installs a package directly, the per-package spec is exactly
what the reader needs first (`dsh plugin add <name>@<v>` for one package,
`... add github:owner/repo#<sha>` for its unpublished neighbour). Only
the plugin knows which.

So a plugin may claim this line in `on_package`; a package it does not
claim still gets the template. Without the check this ran last and
silently overwrote the plugin, which reads as the plugin API being
ignored rather than as a documented precedence rule.
"""
tmpl = config.install_command_template
for pkg in packages:
if pkg.extensions.get("_core", {}).get("install_command"):
continue
version = pkg.latest or "latest"
try:
cmd = tmpl.format(ref=pkg.identity.install_ref,
Expand Down
Loading