Skip to content

Versioning and Dependency Resolution

df-cli uses Semantic Versioning (semver) for all package versions and supports a rich constraint expression syntax for declaring compatible version ranges.

Version Format

A version follows the semver MAJOR.MINOR.PATCH format, optionally followed by a pre-release tag and build metadata:

1.2.3
1.2.3-beta.1
1.2.3+build.456
1.2.3-rc.1+build.456
Component Description
MAJOR Incremented for incompatible API changes.
MINOR Incremented for backwards-compatible new functionality.
PATCH Incremented for backwards-compatible bug fixes.
Pre-release Optional. Appended after - (e.g. -beta.1, -rc.2). Pre-release versions have lower precedence than the associated normal version.
Build metadata Optional. Appended after + (e.g. +build.456). Informational only and ignored during version matching.

Versions can optionally include a leading v prefix (v1.2.3), which is stripped during parsing. Incomplete versions are normalized with zeros: 1 becomes 1.0.0, 1.2 becomes 1.2.0.


Version Constraint Expressions

When declaring a dependency, you can specify a version constraint that defines which versions of that package are acceptable. Constraints are specified in the dependencies section of the workspace .sws file or as part of a package string on the command line.

Exact Version

Pins to a single version. No other version will match.

1.2.3
=1.2.3

Caret Ranges (^)

Allow changes that do not modify the left-most non-zero digit. This is the most common constraint type — it allows updates that are expected to be backwards-compatible.

Expression Expands to Allows
^1.2.3 >=1.2.3 <2.0.0 Minor and patch updates
^0.2.3 >=0.2.3 <0.3.0 Patch updates only (major is 0, so minor is significant)
^0.0.3 >=0.0.3 <0.0.4 No updates (major and minor are 0)

When to use: You trust the publisher to follow semver — breaking changes only happen in new major versions.

Tilde Ranges (~)

Allow patch-level changes only. More restrictive than caret ranges.

Expression Expands to Allows
~1.2.3 >=1.2.3 <1.3.0 Patch updates only
~1.2 >=1.2.0 <1.3.0 Same as ~1.2.0
~1 >=1.0.0 <2.0.0 Same as ~1.0.0

When to use: You want bug fixes but not new features that could introduce subtle behavior changes.

Comparison Operators

Explicit bounds for fine-grained control.

Operator Example Meaning
>= >=1.0.0 Version 1.0.0 or higher
> >1.0.0 Strictly above 1.0.0
<= <=2.0.0 Version 2.0.0 or lower
< <2.0.0 Strictly below 2.0.0

Combine operators to define a range with both lower and upper bounds:

>=1.2.0 <2.0.0

OR Expressions (||)

Match if any of the listed constraints is satisfied. Useful when a package works with multiple major versions.

^1.0.0 || ^2.0.0
1.0.0 || 2.0.0 || 3.0.0

Pre-release Versions

Pre-release tags (e.g. -beta.1, -rc.1) are included in constraint matching only when the constraint itself references a pre-release:

>=1.0.0-rc.1      # matches 1.0.0-rc.1, 1.0.0-rc.2, 1.0.0, 1.1.0, ...
>=1.0.0            # matches 1.0.0, 1.1.0, ... but NOT 1.0.0-rc.1

Dependency Resolution

When the workspace is loaded, df-cli builds a complete dependency tree by resolving every declared dependency to a concrete version. Dependencies can come from the package repository at packages.dataflex.dev, from a Git URL, or from a local path.

Resolution Order

For each dependency, df-cli tries the following sources in order:

  1. Already loaded — If the dependency was already resolved earlier in the tree, reuse that version (if compatible).
  2. Lock file — If a .sws.lock file exists and contains a pinned version for this dependency, use it (if it still satisfies the constraint).
  3. Fixed version — If the constraint is an exact version rather than a range, use it directly.
  4. Fetch latest — Query the package repository for the latest version that satisfies the constraint, filtered by the workspace's DataFlex version.

Shared Dependencies and Constraint Intersection

Packages in the DataFlex ecosystem can depend on each other and form a dependency tree. When multiple packages in the tree depend on the same shared package, their version constraints must be compatible.

Consider this scenario:

MyApp (workspace)
 +-- Charts ^1.0.0
 |    +-- Utils ~1.2.0
 +-- Utils ^1.0.0

MyApp depends on both Charts and Utils. Charts also depends on Utils, but with a different constraint. df-cli must find a single version of Utils that satisfies both constraints simultaneously.

How constraint intersection works:

  1. Collect all constraints on Utils:
  2. MyApp requires ^1.0.0 (meaning >=1.0.0 <2.0.0)
  3. Charts requires ~1.2.0 (meaning >=1.2.0 <1.3.0)

  4. Extract the bounds from each constraint and intersect them:

  5. Lower bound: the highest minimum → max(1.0.0, 1.2.0) = 1.2.0
  6. Upper bound: the lowest maximum → min(2.0.0, 1.3.0) = 1.3.0

  7. The effective range for Utils is >=1.2.0 <1.3.0. df-cli fetches the latest version in this window (e.g. 1.2.5).

If no version exists that satisfies all constraints, df-cli reports an incompatibility error and aborts the operation.

Deeper Diamond Dependencies

The same logic applies to arbitrarily deep dependency trees. Consider:

MyApp (workspace)
 +-- UI ^2.0.0
 |    +-- Core ~2.1.0
 +-- API ^2.0.0
 |    +-- Core ^2.0.0
 +-- Core ^2.2.0

Three different packages all depend on Core with different constraints: - UI requires ~2.1.0>=2.1.0 <2.2.0 - API requires ^2.0.0>=2.0.0 <3.0.0 - MyApp requires ^2.2.0>=2.2.0 <3.0.0

Intersection: - Lower bound: max(2.1.0, 2.0.0, 2.2.0) = 2.2.0 - Upper bound: min(2.2.0, 3.0.0, 3.0.0) = 2.2.0

The only version that satisfies all three constraints is exactly >=2.2.0 <2.2.0 — which is impossible. This is a conflict, and df-cli will report an incompatibility error. The fix would be for UI to relax its constraint on Core (e.g. change ~2.1.0 to ^2.1.0).


Lock File

The lock file ensures that every developer and automated environment uses the exact same dependency versions, even when version ranges would allow newer releases.

Format

The lock file is named <workspace>.sws.lock (e.g. MyProject.sws.lock) and sits next to the workspace file. It contains the resolved version for every dependency:

{
  "lockVersion": "1.0.0",
  "dependencies": {
    "DAE/Charts": {
      "ref": "1.2.5"
    },
    "DAE/Utils": {
      "ref": "1.2.3"
    }
  }
}

Behavior

  • On install / load: If a lock file exists, its pinned versions are used as long as they still satisfy the constraints in the .sws file. This prevents unexpected version drift.
  • On upgrade: The lock file is updated to reflect the newly resolved versions after a successful upgrade.
  • On conflict: If a locked version no longer satisfies a changed constraint (e.g. you updated the constraint in your .sws file), the lock is ignored for that dependency and a new version is fetched.
  • Missing lock file: Not an error. Dependencies are resolved normally and a lock file is written after the first successful resolution.

When to Commit the Lock File

The lock file should be committed to version control. This ensures that: - All team members work against the same dependency versions. - Automated builds are reproducible. - Upgrading is an explicit action (df-cli package upgrade), not something that happens silently.


Version Constraints by Source Type

Source Type Constraint Support Example
Repository (SRV) Full semver range expressions ^1.0.0, ~2.1, >=1.0.0 <3.0.0, ^0.x\|\|^1.x
Git Semver range expressions, branch names, commit hashes ^1.0.0, >=1.0.0, main, abc123…
Local No versioning Always uses the workspace as-is

For Git dependencies, range expressions are matched against the semver-compatible tags fetched from the remote repository. The latest tag that satisfies the expression is selected and checked out. Branch names and commit hashes are also accepted and are treated as exact refs.


Constraint Behaviour Reference

Resolution — what add() / install() stores

Constraint Example GIT SRV
Exact semver tag 1.0.0 Checks out that tag; sws stores 1.0.0 Downloads exact version
Tilde ~ ~1.0.0 Tag search >=1.0.0 <1.1.0; sws stores resolved tag webapi receives min/max window
Caret ^ ^1.0.0 Tag search >=1.0.0 <2.0.0; sws stores resolved tag webapi receives min/max window
Lower bound >= >=1.0.0 Tag search >=1.0.0; sws stores resolved tag webapi receives only min-version
Upper bound <= <=1.0.1 Tag search <=1.0.1; sws stores resolved tag webapi receives only max-version
Strict lower > >1.0.0 Tag search >=1.0.1 (incremented); sws stores resolved tag webapi receives min-version=1.0.1
Strict upper < <2.0.0 Tag search, upper bound decremented; sws stores resolved tag webapi receives decremented max-version
OR (overlapping) ^1.0.0\|\|^2.0.0 Union window >=1.0.0 <3.0.0; tag search picks latest in window webapi receives union window
OR (disjoint exact) 1.0.0\|\|2.0.0 Not supported — min/max window inverts Not supported
Branch name main Resolves to HEAD commit hash; sws stores branch name N/A
Commit hash abc123… Checks out that commit; sws stores hash N/A

update() — detecting newer versions

sws constraint GIT SRV
Exact semver tag Finds latest semver tag satisfying the tightest window across all dependents Queries webapi with tightest window
Expression (~, ^, >=, etc.) Tag search within tightest window across all dependents webapi call with tightest window
OR (overlapping) Same as expression — union window used Same
Branch name Compares HEAD commit hash of branch against lock file N/A
Commit hash Always UP_TO_DATE — pinned forever N/A

upgrade() — how the sws constraint is updated

Condition sws after upgrade
Expression still covers new version (satisfies_range) Expression preserved; only lock file advances
GIT branch dep (sws = branch name, old_dep->version = commit hash) Branch name preserved; new commit hash written to lock
GIT tag dep (sws = old semver tag, neither side is a commit hash) sws updated to new semver tag
SRV exact version sws updated to new version string