xpm
Version:
The xPack project manager command line tool
220 lines (196 loc) • 7.51 kB
text/typescript
/*
* 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 <b>xpm</b> version for policy
* evaluation and the logger for diagnostic output.
*/
export interface PoliciesConstructorParameters {
/**
* The minimum <b>xpm</b> version to evaluate.
*/
minVersion?: string
/**
* The logger instance for output and diagnostics.
*/
log: Logger
}
/**
* Computes feature policy flags based on a minimum <b>xpm</b> version.
*
* @remarks
* This class derives compatibility flags from a specified minimum
* version to preserve legacy behaviour when required.
*
* Policy versioning allows <b>xpm</b> 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
* <b>xpm</b> version:
*
* <ul>
* <li>Packages with minimumXpmRequired \< version threshold get
* legacy behavior.</li>
* <li>Packages with minimumXpmRequired \>= version threshold get
* new behavior.</li>
* </ul>
*
* This approach ensures that:
*
* <ol>
* <li>Existing packages continue to work with newer <b>xpm</b> versions without
* modification.</li>
* <li>New packages can opt into modern behavior by specifying a recent
* minimumXpmRequired.</li>
* <li>Breaking changes are tied to explicit version declarations rather than
* <b>xpm</b> installation version.</li>
* </ol>
*
* Policy flags are evaluated once during initialization and cached for the
* duration of the operation.
*/
export class Policies {
// --------------------------------------------------------------------------
// Public Members.
/**
* The minimum <b>xpm</b> 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
* <b>xpm</b> 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 <b>xpm</b> version.
*
* @remarks
* The constructor evaluates each policy flag by comparing the provided
* minimum version against threshold versions where behavior changed.
*
* Evaluation process:
*
* <ol>
* <li>Validate that <code>minVersion</code> is a valid semver string.</li>
* <li>If invalid, retain default values (all flags false = modern
* behavior).</li>
* <li>For each policy, use <code>semver.lt()</code> to check if
* <code>minVersion</code> is less than
* the threshold version.</li>
* <li>If <code>minVersion</code> \< threshold, enable legacy behavior
* (flag = <code>true</code>).</li>
* <li>If <code>minVersion</code> \>= threshold, use modern behavior
* (flag = <code>false</code>).</li>
* </ol>
*
* 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 <b>xpm</b> 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)
}
}
// ----------------------------------------------------------------------------