Skip to content

Semver-aware dependency resolution for plugins #1982

Closed

Description

@bpamiri

Summary

The current dependency system ($determineDependency()) only checks whether a named plugin is loaded — it cannot express version constraints. A plugin declaring dependency="authenticateThis" succeeds even if the loaded version is incompatible.

Current Behavior

// Plugins.cfc ~line 198-213
// Checks: does a plugin with this name exist in the loaded plugins struct?
// No version comparison whatsoever

Proposed Behavior

Declaration (in plugin.json)

{
  "dependencies": {
    "authenticateThis": ">=1.0.0 <2.0.0",
    "wheels-utils": "^3.0.0"
  }
}

Resolution

  • Parse semver constraints from plugin.json dependencies
  • Compare against the version reported by each loaded plugin
  • On constraint mismatch:
    • Development: throw an error with clear message ("Plugin X requires authenticateThis >=1.0.0 but 0.9.2 is loaded")
    • Production: log a warning
  • Plugins without plugin.json fall back to existing presence-only check

Semver Support

Standard semver operators: >=, <=, >, <, =, ^ (compatible), ~ (patch-level). Implement a lightweight semver comparison utility (or use existing CFML library if available).

Files

Phase

Phase 2 — DI Integration & Lifecycle (3.x, backward compatible)

Activity

  1. bpamiri commented on Mar 16, 2026

    @bpamiri
    CollaboratorAuthor

    Analysis

    Synopsis

    Replace the current presence-only dependency check ($determineDependency() in Plugins.cfc:198-213) with semver-aware version constraint resolution, so plugins can declare version requirements like ">=1.0.0 <2.0.0" against their dependencies.

    Merit Assessment

    Recommendation: Proceed with modifications

    • Real problem: The current $determineDependency() only checks StructKeyExists(variables.$class.plugins, name) — it has no concept of versions. A plugin requiring authenticateThis 2.x will silently load with 0.9.x, leading to runtime errors.
    • Semver is the right answer: The ecosystem (box.json, npm, Cargo) has standardized on semver constraints. Plugin authors already declare versions in box.json — they just can't constrain against them.
    • Scope concern: Implementing a full semver parser (^, ~, range intersections) in CFML is non-trivial. Recommend starting with a subset: >=, >, <=, <, =, and exact match. Defer ^ and ~ shorthand to a follow-up or implement them as syntactic sugar that expands to range expressions.

    Implementation Plan

    1. Create vendor/wheels/utils/Semver.cfc — A lightweight utility with:

      • parse(versionString) — returns struct {major, minor, patch} (strip leading v, ignore pre-release labels initially)
      • satisfies(version, constraint) — evaluates a single constraint (e.g., >=1.0.0)
      • satisfiesAll(version, constraintString) — splits space-separated constraints and ANDs them (e.g., ">=1.0.0 <2.0.0")
      • compare(v1, v2) — returns -1, 0, 1
    2. Replace $determineDependency() (Plugins.cfc:198-213):

      • If the plugin has plugin.json with dependencies (struct of {name: constraintString}), use semver resolution.
      • If the plugin uses legacy CFC metadata dependency attribute (current behavior), fall back to presence-only check.
      • Version source: variables.$class.pluginMeta[name].version (already populated from box.json by $pluginMetaData() at line 184-196, and from plugin.json once Add plugin.json manifest support alongside box.json #1976 lands).
    3. Error behavior:

      • Development/testing/maintenance environments: Throw a clear error: "Plugin 'X' requires 'Y' >=1.0.0 <2.0.0 but version 0.9.2 is loaded"
      • Production: Log warning, add to variables.$class.dependantPlugins (existing pattern), do not throw.
    4. Handle missing version: If a dependency plugin has no version metadata (no box.json, no plugin.json), treat as "unknown" and:

      • Dev: warn that version constraint cannot be verified
      • Production: allow (assume compatible)
    5. Tests — Unit test Semver.cfc exhaustively (this is pure logic, easy to test): comparisons, range satisfaction, edge cases (0.x, pre-release, missing patch). Integration test with mock plugins declaring version constraints.

    Estimated complexity: Medium (3-4 days — mostly the semver parser + tests).

    Dependencies & Risks

    • Depends on Add plugin.json manifest support alongside box.json #1976 (plugin.json manifest) for the dependencies declaration format. Without plugin.json, constraints can only come from CFC metadata attributes (ugly but possible as a stopgap).
    • Version source reliability: $pluginMetaData() currently reads version from box.json only. Many existing plugins may not have box.json or may have stale versions. This feature is most useful once Add plugin.json manifest support alongside box.json #1976 (plugin.json) provides a canonical version source.
    • Semver edge cases: CFML doesn't have a semver library. The parser must handle: missing patch (1.0 → 1.0.0), leading v prefix, pre-release labels (ignore initially). Keep the initial implementation strict and well-tested.
    • No circular dependency detection: The current system (and this proposal) checks dependencies after all plugins load. Circular version constraints (A requires B >=2, B requires A >=2) won't cause infinite loops but should produce a clear error. Consider adding cycle detection as a follow-up.
    • Breaking change risk: None for existing plugins (legacy dependency attribute falls back to presence check). New behavior only activates when plugin.json declares versioned dependencies.

    Automated analysis by @claude — review and adjust as needed.

  2. added this to the v3.2 milestone on Mar 17, 2026
  3. bpamiri commented on Mar 25, 2026

    @bpamiri
    CollaboratorAuthor

    Implemented in develop.

    • 89dd99f feat: semver-aware dependency resolution for plugins (wh-0cf)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions