/*
* This file is part of the xPack project (http://xpack.github.io).
* Copyright (c) 2017-2026 Liviu Ionescu. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software
* for any purpose is hereby granted, under the terms of the MIT license.
*
* If a copy of the license was not distributed with this file, it can
* be obtained from https://opensource.org/license/mit.
*/
// ----------------------------------------------------------------------------
import assert from 'node:assert'
// https://nodejs.org/docs/latest/api/
import { Logger } from '@xpack/logger'
// https://www.npmjs.com/package/semver
import semver from 'semver'
// ============================================================================
/**
* Configuration parameters for constructing a policies instance.
*
* @remarks
* This interface defines the required configuration for creating an
* instance of {@link Policies}. Both properties are mandatory.
*
* The parameters provide the minimum xpm version for policy
* evaluation and the logger for diagnostic output.
*/
export interface PoliciesConstructorParameters {
/**
* The minimum xpm version to evaluate.
*/
minVersion?: string
/**
* The logger instance for output and diagnostics.
*/
log: Logger
}
/**
* Computes feature policy flags based on a minimum xpm version.
*
* @remarks
* This class derives compatibility flags from a specified minimum
* version to preserve legacy behaviour when required.
*
* Policy versioning allows xpm to evolve its behavior while maintaining
* backward compatibility for packages that specify older minimum versions.
* Each policy flag represents a breaking change introduced at a specific
* xpm version:
*
*
* - Packages with minimumXpmRequired \< version threshold get
* legacy behavior.
* - Packages with minimumXpmRequired \>= version threshold get
* new behavior.
*
*
* This approach ensures that:
*
*
* - Existing packages continue to work with newer xpm versions without
* modification.
* - New packages can opt into modern behavior by specifying a recent
* minimumXpmRequired.
* - Breaking changes are tied to explicit version declarations rather than
* xpm installation version.
*
*
* Policy flags are evaluated once during initialization and cached for the
* duration of the operation.
*/
export class Policies {
// --------------------------------------------------------------------------
// Public Members.
/**
* The minimum xpm version used to derive policy flags.
*/
minVersion = '0.0.0'
/**
* Indicates whether npm dependencies are shared across installations.
*
* @remarks
* Legacy behavior (before 0.14.0): npm dependencies were shared in a global
* location, similar to how npm itself works with global `node_modules`.
*
* Modern behavior (0.14.0+): npm dependencies are installed locally per
* xpm package, providing better isolation and avoiding version
* conflicts.
*
* Set to `true` for packages with minimumXpmRequired \< 0.14.0.
*/
shareNpmDependencies = false
/**
* Indicates whether local `xpacks` folders are non-hierarchical.
*
* @remarks
* Legacy behavior (before 0.16.0): xpacks were stored in a flat structure
* within the local `xpacks` folder.
*
* Modern behavior (0.16.0+): xpacks are organized hierarchically within
* the `xpacks` folder, mirroring the scoped package structure (e.g.,
* `xpacks/@scope/package`).
*
* Set to `true` for packages with minimumXpmRequired \< 0.16.0.
*/
nonHierarchicalLocalXpacksFolder = false
/**
* Indicates whether dependencies are restricted to string-only notation.
*
* @remarks
* Legacy behavior (before 0.16.0): dependencies could only be specified as
* strings (e.g., `"@scope/package": "1.0.0"`).
*
* Modern behavior (0.16.0+): dependencies can be specified as objects with
* additional metadata (e.g., platforms, repositories), providing richer
* dependency configuration.
*
* Set to `true` for packages with minimumXpmRequired \< 0.16.0.
*/
onlyStringDependencies = false
/**
* Indicates whether `xpm init` templates accept a single parameter.
*
* @remarks
* Legacy behavior (before 0.22.0): init templates accepted only a single
* parameter combining template name and optional arguments.
*
* Modern behavior (0.22.0+): init templates support multiple parameters
* for more flexible template initialization and configuration.
*
* Set to `true` for packages with minimumXpmRequired \< 0.22.0.
*/
singleParameterXpmInitTemplate = false
/**
* Indicates whether `xpm init` templates expose top-level properties.
*
* @remarks
* Legacy behavior (before 0.23.0): init templates exposed the configuration
* properties both as top-level and also grouped as `properties`
* in the liquid substitutions variables.
*
* Modern behavior (0.23.0+): init templates expose the configuration
* properties as `matrix` vs. the liquid substitutions variables as
* `properties` providing a clearer structure and avoiding conflicts.
*
* Set to `true` for packages with minimumXpmRequired \< 0.23.0.
*/
topPropertiesXpmInitTemplate = false
// --------------------------------------------------------------------------
// Constructor.
/**
* Constructs policy flags based on a minimum xpm version.
*
* @remarks
* The constructor evaluates each policy flag by comparing the provided
* minimum version against threshold versions where behavior changed.
*
* Evaluation process:
*
*
* - Validate that
minVersion is a valid semver string.
* - If invalid, retain default values (all flags false = modern
* behavior).
* - For each policy, use
semver.lt() to check if
* minVersion is less than
* the threshold version.
* - If
minVersion \< threshold, enable legacy behavior
* (flag = true).
* - If
minVersion \>= threshold, use modern behavior
* (flag = false).
*
*
* This ensures that packages explicitly declaring their minimum version
* get the behavior that was current at that version, while packages
* without a valid minimum version default to the most modern behavior.
*
* @param minVersion - The minimum xpm version to evaluate.
* @param log - The logger instance for output and diagnostics.
*/
constructor({ minVersion, log }: PoliciesConstructorParameters) {
log.trace(`${Policies.name}({minVersion: ${String(minVersion)}})`)
if (semver.valid(minVersion) !== null) {
assert(minVersion)
this.minVersion = minVersion
this.shareNpmDependencies = semver.lt(this.minVersion, '0.14.0')
this.nonHierarchicalLocalXpacksFolder = semver.lt(
this.minVersion,
'0.16.0'
)
this.onlyStringDependencies = semver.lt(this.minVersion, '0.16.0')
this.singleParameterXpmInitTemplate = semver.lt(this.minVersion, '0.22.0')
this.topPropertiesXpmInitTemplate = semver.lt(this.minVersion, '0.23.0')
}
log.trace('policies:', this)
}
}
// ----------------------------------------------------------------------------