Skip to content

Internal Documentation

Documentation for EpiAwarePackageTools's internal interface.

Contents

Index

Internal API

EpiAwarePackageTools._apply Function
julia
_apply(
    target_dir::AbstractString;
    managed_only,
    force,
    ad,
    benchmarks,
    downgrade_compat,
    inputs
)
julia
_apply(target_dir; managed_only, force, ad, benchmarks,
    downgrade_compat, inputs)

Shared worker for scaffold/update.

managed_only restricts to managed templates (the update path). force overwrites package-owned files too (only meaningful for scaffold). ad selects the AD-enabled or AD-disabled standard; benchmarks gates the opt-in benchmark CI/suite/docs page; downgrade_compat gates the opt-in downgrade-compat CI job. Returns a (created, updated, preserved, removed, warnings) manifest of destination paths (removed being the retired managed paths cleaned up; see RETIRED_PATHS; warnings a Vector{String} of non-fatal issues raised while applying, e.g. a diverged-but-unmarked test/ad/setup.jl about to be overwritten).

source
EpiAwarePackageTools._apply_gitignore Function
julia
_apply_gitignore(
    target_dir::AbstractString,
    inputs::NamedTuple
) -> Tuple{Symbol, Bool}
julia
_apply_gitignore(target_dir, inputs)

Apply the managed .gitignore block to target_dir.

Returns (action, changed) where action is :created, :injected (markers added to an existing file, e.g. on first run of a kit version with this fix), or :refreshed (markers already present; only the marked region is touched). Mirrors _apply_badges.

source
EpiAwarePackageTools._apply_org_branding Function
julia
_apply_org_branding(
    target_dir::AbstractString,
    org_branding::Bool
) -> Symbol
julia
_apply_org_branding(target_dir, org_branding)

Write (or remove) the bundled EpiAware org logo asset, following the package's ORG_BRANDING opt-in (#242).

Bundled but deliberately not a SCAFFOLD_TEMPLATES entry, like the LICENSE variants: the template table is emitted wholesale, and a third-party adopter must not be handed an EpiAware logo.

Returns :created (written fresh), :refreshed (drifted, rewritten), :unchanged (already byte-correct, nothing written), :removed (branding off, the kit's asset withdrawn), or :skipped (branding off, nothing of ours there).

Managed, so the asset is re-applied when it drifts and removed when branding is turned back off — leaving a repo that opts out with no EpiAware asset, and update a fixed point in both states.

Turning branding off deletes the asset only when it is byte-identical to the one the kit shipped. A file the package put at that path is not the kit's to remove, so it is left alone with a warning, rather than silently destroying package content — the same care _detect_managed_override takes elsewhere.

source
EpiAwarePackageTools._apply_standard_sections Function
julia
_apply_standard_sections(
    target_dir::AbstractString,
    inputs::NamedTuple;
    org_branding
) -> Tuple{Symbol, Bool}
julia
_apply_standard_sections(target_dir, inputs)

Inject or refresh the managed README standard-sections block.

Returns (action, changed) where action is :refreshed (markers present), :injected (appended to a README that carries none of these sections yet), or :skipped (no README, missing inputs, or a marker-less README that already has a bespoke Contributing/Code-of-conduct/citation section — migrating that to the managed block is a deliberate, maintainer-signed per-repo wording change, #67). Mirrors _apply_badges/_apply_gitignore: only the marked region is rewritten.

source
EpiAwarePackageTools._detect_ad_setup_owned Function
julia
_detect_ad_setup_owned(target_dir::AbstractString) -> Bool
julia
_detect_ad_setup_owned(target_dir)

Whether a package has opted its AD-harness driver (test/ad/setup.jl) out of kit management by marking it package-owned (#162).

test/ad/setup.jl is force-managed: update() overwrites it with the generic driver that assumes the package's ADFixtures registry satisfies the current ADRegistry contract (its scenarios accepts a category keyword). A package whose ADFixtures predates that contract cannot run the generic driver (it would MethodError on category=), so it must keep a package-owned driver while it migrates. Adding the marker EPIAWARE_AD_SETUP_OWNED to the committed test/ad/setup.jl (in a comment) tells update() to preserve the file instead of clobbering it — the same detect-from-the-destination idempotency as _detect_downgrade_compat (#121). A never-scaffolded or unmarked file is managed as before, so the opt-out is explicit and self-documenting.

source
EpiAwarePackageTools._detect_benchmark_history_parked Function
julia
_detect_benchmark_history_parked(
    target_dir::AbstractString
) -> Bool
julia
_detect_benchmark_history_parked(target_dir)

Whether a package has parked benchmark-history.yaml's push/tag triggers, so a resync (update) preserves that state instead of re-enabling a permanently failing history run (#153).

AirspeedVelocity/benchpkg installs the package into a temp environment where a [sources] pin does not apply, so an unregistered [sources]-pinned dependency (currently every adopter, via the unregistered kit itself) can never resolve there and every push/tag-triggered history run fails. The fix is to park the workflow — drop the push/tags triggers, keeping only workflow_dispatch — until the package is registered. The committed on: block is the marker: parked iff it carries no push: trigger. A fresh (never-scaffolded) target has no file, so it defaults to the full triggers — the standard once a package is registered, mirroring _detect_downgrade_compat.

source
EpiAwarePackageTools._detect_benchmarks Function
julia
_detect_benchmarks(target_dir::AbstractString) -> Bool
julia
_detect_benchmarks(target_dir)

Whether a repo already has benchmarks enabled, so a resync (update with no benchmarks kwarg) preserves an adopter's opt-in instead of reverting to the opt-out default and stripping their benchmark CI/suite/page (the #72 trap).

The scheduled template-sync bakes benchmarks = into its update call, but a repo scaffolded before this flag has a template-sync that re-passes nothing, so the state must also be recoverable from the destination. The managed benchmark CI workflows are the marker: present iff benchmarks were enabled. A fresh (never-scaffolded) target has neither, so it defaults to opt-out — exactly the intended behaviour for a new package.

source
EpiAwarePackageTools._detect_docs_subdomain Function
julia
_detect_docs_subdomain(
    target_dir::AbstractString
) -> Union{Nothing, String, Symbol}
julia
_detect_docs_subdomain(target_dir)

Recover the docs-hosting choice from an already-scaffolded repo so a resync (update with no docs_subdomain kwarg) keeps it instead of silently reverting a subdomain-hosted package to project-pages (#123).

The managed docs/make.jl carries the resolved deploy_url literal, which is the source of truth: a quoted host means the custom-subdomain path, a bare nothing means project-pages. Returns the host string, nothing (explicit project-pages), or :missing when docs/make.jl is absent or carries no deploy_url (a never-scaffolded target, so the caller falls back to the scaffold default). Mirrors _detect_reviewer/_detect_benchmarks: the destination is read back so a scheduled sync — which never re-passes docs_subdomain — stays idempotent, and a package that has drifted to the wrong base self-heals on the next update.

When docs/make.jl says deploy_url = nothing but the repo has a gh-pages CNAME (a Pages custom domain set out of band), the two disagree: the site is served from the CNAME root while the build uses the /<Repo>.jl/ project-pages base, so every asset 404s and the docs render unstyled. That mismatch is invisible to CI (the deploy passes), so the deploy_url alone cannot detect it. The CNAME is consulted in that one case and, when present, its host is recovered as the subdomain so the drift heals — the concrete case #123's self-heal promise was missing.

source
EpiAwarePackageTools._detect_doi Function
julia
_detect_doi(
    target_dir::AbstractString
) -> Union{Tuple{Nothing, Nothing}, Tuple{String, String}}
julia
_detect_doi(target_dir)

Recover a persisted Zenodo DOI and badge id from an already-scaffolded repo so a resync (update with no doi/zenodo_badge kwargs) keeps an adopter's DOI badge instead of stripping it (#161).

The README "License & DOI" badge cell is fully managed and re-rendered on every sync, but doi/zenodo_badge default to nothing and the scheduled template-sync never re-passes them, so the values must be read back from the destination — exactly as _detect_reviewer recovers the code-owner handle. Reads the managed DOI badge the kit renders ([![DOI](https://zenodo.org/badge/<id>.svg)](https://doi.org/<doi>)) back from the existing README and returns the (doi, zenodo_badge) pair, or (nothing, nothing) when the README is absent or carries no DOI badge (so a never-configured repo stays unconfigured).

source
EpiAwarePackageTools._detect_downgrade_compat Function
julia
_detect_downgrade_compat(target_dir::AbstractString) -> Bool
julia
_detect_downgrade_compat(target_dir)

Whether a repo keeps the opt-in downgrade-compat CI job, so a resync (update with no downgrade_compat kwarg) preserves a package's decision to drop it instead of unconditionally reintroducing a job the package deliberately removed (#121).

A package pinned to a Julia floor (or one adopting an unregistered, [sources]-pinned dependency) can never resolve the julia-downgrade-compat job, so it disables that job in its managed .github/workflows/test.yaml. The current template would regenerate it on every sync, silently reintroducing a permanently-red job. The committed test.yaml's downgrade.yml caller is the marker: present iff the job is kept. A fresh (never-scaffolded) target has no test.yaml, so it defaults to keeping the job — the standard for a new package.

source
EpiAwarePackageTools._detect_license Function
julia
_detect_license(
    target_dir::AbstractString
) -> Union{Nothing, SubString{String}, String}
julia
_detect_license(target_dir)

Recover an already-scaffolded repo's licence so a resync (update with no license kwarg) keeps it instead of resetting the README badge to "MIT" (#235).

The README "License & DOI" badge cell is managed and re-rendered on every sync, but license defaults to "MIT" and the scheduled template-sync never re-passes it, so a non-MIT adopter's badge was flipped to MIT on every automated sync — a licensing-correctness regression, and the same failure mode _detect_doi fixed for the DOI badge (#161). The value is therefore read back from the destination: first the managed License badge the kit renders, then the Project.toml license field for a repo whose README has no badge block yet. Returns the SPDX id, or nothing when neither carries one (so a never-configured repo takes the scaffold default). Only a SUPPORTED_LICENSES id is recovered, since that is what scaffold_inputs accepts; an explicit license keyword still wins, so a deliberate change is never blocked.

source
EpiAwarePackageTools._detect_managed_override Function
julia
_detect_managed_override(
    target_dir::AbstractString,
    dest::AbstractString,
    rendered::AbstractString
) -> Bool
julia
_detect_managed_override(target_dir, dest, rendered)

Whether the template-emitted managed file at dest has been marked package-owned, so update() preserves it rather than resyncing it (#224).

Managed files always resync — that is what keeps an adopter on the current standard. A package that must keep its own version of one (a hand-kept AD driver mid-migration, a workflow the package genuinely owns) says so in the file, by putting the marker EPIAWARE_MANAGED_OVERRIDE in a comment. The committed file is then the marker, the same detect-from-the-destination idempotency as _detect_downgrade_compat (#121), so the opt-out is explicit, self-documenting, and survives every sync. The match is a plain case-sensitive occursin, so a mis-cased marker does nothing.

This governs whole files emitted from a template (SCAFFOLD_TEMPLATES) only. The marker-delimited regions the kit injects into otherwise package-owned files (the .gitignore managed block, the README badge and standard-sections blocks, Project.toml's [workspace] stanza) are refreshed by their own appliers (_apply_gitignore and friends), which never consult this, so they cannot be opted out this way.

rendered is the freshly rendered template for dest, and is required rather than defaulted: a managed template that itself contained the marker literal (a workflow comment documenting this feature, say) would otherwise hand every adopter a self-preserving copy of that file on its next sync, and the kit would silently stop managing its own file, everywhere, forever. So when the fresh render carries the marker, the marker means nothing and the file stays managed. A caller cannot omit the guard by accident. The test suite additionally asserts that no bundled template renders the marker, so a template that ever adds it fails the kit's own CI loudly rather than being tacitly absorbed here.

test/ad/setup.jl additionally still honours its original marker EPIAWARE_AD_SETUP_OWNED (#162) via _detect_ad_setup_owned, which adopters carry today; either marker opts that file out.

scaffold/scaffold_generate (force = true) ignore the marker and lay the managed file down fresh, so a new package always starts managed. The marker opts a file out of resyncing, not out of retirement: a path the kit retires (RETIRED_PATHS) is still deleted, marker or not.

source
EpiAwarePackageTools._detect_org_branding Function
julia
_detect_org_branding(target_dir::AbstractString) -> Bool
julia
_detect_org_branding(target_dir)

Whether the package opted in to EpiAware org branding, via const ORG_BRANDING = true in the package-owned docs/docs_config.jl (#242).

Read from the destination rather than passed as a kwarg, the same detect-from-the-file idempotency as _detect_benchmarks/_detect_downgrade_compat: an update (or the scheduled template sync, which passes no kwargs) then preserves the package's choice instead of reverting it. Defaults to false — off — for a package with no config, or one predating the key.

source
EpiAwarePackageTools._detect_reviewer Function
julia
_detect_reviewer(
    target_dir::AbstractString
) -> Union{Nothing, String}
julia
_detect_reviewer(target_dir)

Recover a persisted reviewer handle from an already-scaffolded repo so a resync (update with no reviewer kwarg) keeps it instead of reverting to the org placeholder (#72).

CODEOWNERS and the Dependabot reviewers block are managed (re-emitted on every sync), and the scheduled template-sync never re-passes reviewer, so the handle must be read back from the destination — exactly as _preserve_reusable_refs reads existing reusable-workflow refs to stay idempotent against Dependabot SHA bumps. Reads the active (uncommented) CODEOWNERS owner line the kit renders from the handle and returns its first @handle (the leading @ stripped, an org/team slug kept whole), or nothing when CODEOWNERS is absent or carries only the commented placeholder (so a never-configured repo stays unconfigured).

source
EpiAwarePackageTools._docstr_text Function
julia
_docstr_text(docstr) -> Any
julia
_docstr_text(docstr)

Render one DocStr's text vector to a string, keeping only the authored prose.

A DocStr.text is a vector of pieces: plain interpolation splits it into several AbstractString fragments, and a registered @template wraps each docstring as [Template{:before}, "<prose>", Template{:after}], so the prose is an interior element, not the last one. Joining only the AbstractString/Markdown.MD pieces and dropping the Template markers means a templated package reads the same as a plain one.

source
EpiAwarePackageTools._logo_initial Function
julia
_logo_initial(pkg::Union{Nothing, AbstractString}) -> Any
julia
_logo_initial(pkg)

The single glyph shown on the placeholder logo (templates/docs/src/assets/logo.svg): the package's first letter, uppercased, or "?" when the package name is unknown (a scaffold_generated/scaffolded target always has one, but this keeps scaffold_inputs total). Purely cosmetic — replacing the placeholder SVG with a real logo makes this irrelevant.

source
EpiAwarePackageTools._preserve_action_pins Function
julia
_preserve_action_pins(
    content::AbstractString,
    dest::AbstractString
) -> Any
julia
_preserve_action_pins(content, dest)

Keep the destination's existing third-party action pins when re-emitting a managed workflow.

Dependabot owns the github-actions pins in every adopting repo (the managed dependabot.yml enables the github-actions ecosystem), so a template that hard-pins actions/checkout@v7 would revert a Dependabot bump on every resync. When template-sync re-applies on a branch it did not open (a Dependabot PR), that revert rides along silently into the merge (#215). When the destination already pins a version for an action, that pin wins and only the rest of the workflow is re-applied from the template; on first adoption (no destination yet) the template's seed pin is used. Mirrors _preserve_reusable_refs, which does the same for the org reusable-workflow callers (those lines are left to it).

source
EpiAwarePackageTools._preserve_downstreams Function
julia
_preserve_downstreams(
    content::AbstractString,
    dest::AbstractString
) -> Any
julia
_preserve_downstreams(content, dest)

Keep the destination's reverse-dependency list when re-emitting the managed downstream.yaml.

Which packages depend on this one is a fact about the adopting package, not a standard the kit sets: the template seeds downstreams: '[]' with a worked example, and re-applying that seed on every sync silently reset an adopter's list (#234). So the committed value wins and only the rest of the workflow is re-applied from the template — the same read-back-the-destination shape as _preserve_reusable_refs/_preserve_action_pins, and preferable to marking the whole file package-owned (EPIAWARE_MANAGED_OVERRIDE, see _detect_managed_override), which would stop the workflow tracking the standard for the sake of one line.

A bespoke pass rather than a _preserve_caller_with_inputs case. That machinery keys off the uses:with:secrets: shape, which this caller does not have (it puts secrets: first), and the key is one the template itself renders, so _merge_with_blocks would let the template's seed win on it (#183). On first adoption, or when the committed workflow sets no list, the template's seed stands.

source
EpiAwarePackageTools._preserve_reusable_refs Function
julia
_preserve_reusable_refs(
    content::AbstractString,
    dest::AbstractString
) -> Any
julia
_preserve_reusable_refs(content, dest)

Keep the destination's existing reusable-workflow refs when re-emitting a managed CI caller.

Dependabot owns the EpiAware/.github reusable SHAs in every adopting repo, so a template that hard-pinned one SHA would report drift (and fail self-drift / churn the scheduled sync) every time Dependabot moved the live pin. When the destination already pins a ref for the same reusable workflow, that ref wins and only the rest of the caller body is re-applied from the template; on first adoption (no destination yet) the template's seed ref is used. This makes update idempotent against Dependabot's bumps.

source
EpiAwarePackageTools._render_badges Function
julia
_render_badges(
    repo::AbstractString,
    pkg::AbstractString;
    ad,
    license,
    docs_url,
    doi,
    zenodo_badge
)
julia
_render_badges(repo, pkg; ad, license = DEFAULT_LICENSE,
    docs_url = nothing, doi = nothing, zenodo_badge = nothing)

Render the standard badge block (without the markers) from resolved inputs.

repo is the owner/name.jl slug; pkg the package name; ad adds the per-backend AD CI + coverage badge table; license is the SPDX id whose badge is shown. doi/zenodo_badge add a Zenodo DOI badge when both are given. The layout matches CensoredDistributions.jl: a five-column header table (Documentation, Build Status, Code Quality, License & DOI, Downloads) plus the per-backend AD table. No owner/repo is hardcoded — every URL is built from repo/pkg.

source
EpiAwarePackageTools._resolve_docs_subdomain Function
julia
_resolve_docs_subdomain(_::Nothing, pkg)
julia
_resolve_docs_subdomain(spec, pkg)

Resolve the docs_subdomain input to either nothing (project-pages, the default) or a concrete host string.

true selects the conventional <pkg>.epiaware.org; a string is taken verbatim; nothing/false opt out. The Bool and Nothing cases dispatch to their own methods so the String conversion only ever runs on a genuine string input (keeps JET type-stable — String(::Bool) has no method and would otherwise show as a possible error).

source
EpiAwarePackageTools._tutorials_subdir Function
julia
_tutorials_subdir(target_dir::AbstractString) -> String
julia
_tutorials_subdir(target_dir)

Read TUTORIALS_SUBDIR from the package-owned docs/docs_config.jl: the subdir (relative to docs/src) holding the Literate tutorial sources and their rendered .md pages.

The managed .gitignore ignores those rendered pages, so the ignore must track whatever path the package configures rather than hardcode one. The const is written as a quoted string or a joinpath("a", "b") of quoted segments; every quoted segment is joined with / (the gitignore separator). Falls back to the template default when the config is absent (e.g. at first scaffold) or omits the const.

source
EpiAwarePackageTools.run_package_tests Function
julia
run_package_tests(; ...)
run_package_tests(testdir::AbstractString; filter, verbose)
julia
run_package_tests(testdir = pwd(); filter = nothing, verbose = false)

Discover and run the @testitems under testdir (a package's test/ tree), scoped so a nested worktree checked out under the package root cannot inject or shadow items or @testsnippets (kit #191).

Drop-in replacement for TestItemRunner.@run_package_tests in a scaffolded test/runtests.jl: call it with @__DIR__ and the same filter predicate. filter receives a (; filename, name, tags) named tuple per item and keeps those for which it returns true; verbose forwards to the test sets. The default using <Package> import in each item still works — the package name is taken from the package-root Project.toml, the parent of testdir.

source