paratix 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-475OT32R.js → chunk-C5KXNY6R.js} +195 -46
- package/dist/cli.js +2 -2
- package/dist/{index-DlZISXJx.d.ts → index-Da9ccU5K.d.ts} +36 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.js +1 -1
- package/llm-guide.md +61 -10
- package/package.json +1 -1
|
@@ -10541,18 +10541,19 @@ var op = {
|
|
|
10541
10541
|
}
|
|
10542
10542
|
};
|
|
10543
10543
|
|
|
10544
|
-
// src/modules/
|
|
10545
|
-
var
|
|
10546
|
-
function
|
|
10547
|
-
|
|
10548
|
-
return { ...EXEC_OPTS12, timeout: options.timeout };
|
|
10544
|
+
// src/modules/packageVersion.ts
|
|
10545
|
+
var VERSION_EXEC_OPTS = { ignoreExitCode: true, silent: true };
|
|
10546
|
+
function isPackageSpec(value) {
|
|
10547
|
+
return "name" in value && typeof value.name === "string";
|
|
10549
10548
|
}
|
|
10550
10549
|
function splitPackagesAndOptions(values) {
|
|
10551
10550
|
const packages = [];
|
|
10552
10551
|
let options;
|
|
10553
10552
|
for (const [index, value] of values.entries()) {
|
|
10554
10553
|
if (typeof value === "string") {
|
|
10555
|
-
packages.push(value);
|
|
10554
|
+
packages.push({ name: value, version: void 0 });
|
|
10555
|
+
} else if (isPackageSpec(value)) {
|
|
10556
|
+
packages.push({ name: value.name, version: value.version });
|
|
10556
10557
|
} else if (index === values.length - 1) {
|
|
10557
10558
|
options = value;
|
|
10558
10559
|
}
|
|
@@ -10560,16 +10561,140 @@ function splitPackagesAndOptions(values) {
|
|
|
10560
10561
|
return { options, packages };
|
|
10561
10562
|
}
|
|
10562
10563
|
var PACKAGE_NAME_PATTERN = new RegExp("^[a-z0-9][a-z0-9+._\\-]*[a-z0-9.]$", "v");
|
|
10563
|
-
|
|
10564
|
+
var PACKAGE_VERSION_PATTERN = new RegExp("^[A-Za-z0-9][\\w.+~:\\-]*$", "v");
|
|
10565
|
+
function isValidPackageName(packageName) {
|
|
10566
|
+
const isSingleAlnum = packageName.length === 1 && new RegExp("^[a-z0-9]$", "v").test(packageName);
|
|
10567
|
+
return isSingleAlnum || PACKAGE_NAME_PATTERN.test(packageName);
|
|
10568
|
+
}
|
|
10569
|
+
function validatePackages(moduleName, packages) {
|
|
10564
10570
|
if (packages.length === 0) {
|
|
10565
10571
|
throw new Error(`${moduleName}: at least one package name is required`);
|
|
10566
10572
|
}
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
if (!
|
|
10570
|
-
throw new Error(`${moduleName}: invalid package name ${JSON.stringify(
|
|
10573
|
+
const seenVersions = /* @__PURE__ */ new Map();
|
|
10574
|
+
for (const { name, version } of packages) {
|
|
10575
|
+
if (!isValidPackageName(name)) {
|
|
10576
|
+
throw new Error(`${moduleName}: invalid package name ${JSON.stringify(name)}`);
|
|
10577
|
+
}
|
|
10578
|
+
if (version !== void 0 && !PACKAGE_VERSION_PATTERN.test(version)) {
|
|
10579
|
+
throw new Error(
|
|
10580
|
+
`${moduleName}: invalid package version ${JSON.stringify(version)} for ${JSON.stringify(name)}`
|
|
10581
|
+
);
|
|
10582
|
+
}
|
|
10583
|
+
if (seenVersions.has(name) && seenVersions.get(name) !== version) {
|
|
10584
|
+
throw new Error(
|
|
10585
|
+
`${moduleName}: conflicting versions requested for package ${JSON.stringify(name)}`
|
|
10586
|
+
);
|
|
10571
10587
|
}
|
|
10588
|
+
seenVersions.set(name, version);
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10591
|
+
function installToken(pm, packageName, version) {
|
|
10592
|
+
if (version === void 0) return shellQuote(packageName);
|
|
10593
|
+
const separator = pm === "dnf" || pm === "yum" ? "-" : "=";
|
|
10594
|
+
return shellQuote(`${packageName}${separator}${version}`);
|
|
10595
|
+
}
|
|
10596
|
+
function parseApkVersion(out, packageName) {
|
|
10597
|
+
if (out.length === 0) return null;
|
|
10598
|
+
const firstToken = out.split(new RegExp("\\s+", "v"))[0] ?? "";
|
|
10599
|
+
const prefix = `${packageName}-`;
|
|
10600
|
+
if (!firstToken.startsWith(prefix)) return null;
|
|
10601
|
+
const version = firstToken.slice(prefix.length);
|
|
10602
|
+
return version.length > 0 ? version : null;
|
|
10603
|
+
}
|
|
10604
|
+
async function getInstalledVersion(ssh2, pm, packageName) {
|
|
10605
|
+
const quoted = shellQuote(packageName);
|
|
10606
|
+
if (pm === "apk") {
|
|
10607
|
+
const apkResult = await ssh2.exec(`apk version -v ${quoted}`, VERSION_EXEC_OPTS);
|
|
10608
|
+
if (apkResult.code !== 0) return null;
|
|
10609
|
+
return parseApkVersion(apkResult.stdout.trim(), packageName);
|
|
10610
|
+
}
|
|
10611
|
+
if (pm === "apt") {
|
|
10612
|
+
const aptResult = await ssh2.exec(
|
|
10613
|
+
`dpkg-query -W -f='\${Version}' ${quoted} 2>/dev/null`,
|
|
10614
|
+
VERSION_EXEC_OPTS
|
|
10615
|
+
);
|
|
10616
|
+
if (aptResult.code !== 0) return null;
|
|
10617
|
+
const aptVersion = aptResult.stdout.trim();
|
|
10618
|
+
return aptVersion.length > 0 ? aptVersion : null;
|
|
10619
|
+
}
|
|
10620
|
+
const result = await ssh2.exec(
|
|
10621
|
+
`rpm -q --qf '%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n' ${quoted}`,
|
|
10622
|
+
VERSION_EXEC_OPTS
|
|
10623
|
+
);
|
|
10624
|
+
if (result.code !== 0) return null;
|
|
10625
|
+
const lines = result.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
10626
|
+
return lines.at(-1) ?? null;
|
|
10627
|
+
}
|
|
10628
|
+
async function versionsEqual(parameters) {
|
|
10629
|
+
const { installed, pinned, pm, ssh: ssh2 } = parameters;
|
|
10630
|
+
if (pm === "apt") {
|
|
10631
|
+
return ssh2.test(`dpkg --compare-versions ${shellQuote(installed)} eq ${shellQuote(pinned)}`);
|
|
10572
10632
|
}
|
|
10633
|
+
return installed === pinned;
|
|
10634
|
+
}
|
|
10635
|
+
function describePackages(packages) {
|
|
10636
|
+
return packages.map((p) => p.version === void 0 ? p.name : `${p.name}=${p.version}`).join(", ");
|
|
10637
|
+
}
|
|
10638
|
+
function aptInstallCommand(tokens, hasPin) {
|
|
10639
|
+
return hasPin ? `DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-downgrades -- ${tokens}` : `DEBIAN_FRONTEND=noninteractive apt-get install -y -- ${tokens}`;
|
|
10640
|
+
}
|
|
10641
|
+
var DOWNGRADE_COMMANDS = {
|
|
10642
|
+
dnf: (tokens) => `dnf downgrade -y -- ${tokens}`,
|
|
10643
|
+
yum: (tokens) => `yum downgrade -y -- ${tokens}`
|
|
10644
|
+
};
|
|
10645
|
+
function buildInstallCommand(baseInstall, pm, packages) {
|
|
10646
|
+
const tokens = packages.map((p) => installToken(pm, p.name, p.version)).join(" ");
|
|
10647
|
+
const hasPin = packages.some((p) => p.version !== void 0);
|
|
10648
|
+
return pm === "apt" ? aptInstallCommand(tokens, hasPin) : baseInstall(tokens);
|
|
10649
|
+
}
|
|
10650
|
+
async function installedIsHigher(ssh2, installed, pinned) {
|
|
10651
|
+
if (installed === pinned) return false;
|
|
10652
|
+
const result = await ssh2.exec(
|
|
10653
|
+
`printf '%s\\n%s\\n' ${shellQuote(pinned)} ${shellQuote(installed)} | sort -V | tail -n1`,
|
|
10654
|
+
VERSION_EXEC_OPTS
|
|
10655
|
+
);
|
|
10656
|
+
if (result.code !== 0) return false;
|
|
10657
|
+
return result.stdout.trim() === installed;
|
|
10658
|
+
}
|
|
10659
|
+
async function collectDowngradeTokens(ssh2, pm, packages) {
|
|
10660
|
+
const downgrade = [];
|
|
10661
|
+
const install = [];
|
|
10662
|
+
for (const package_ of packages) {
|
|
10663
|
+
if (package_.version === void 0) {
|
|
10664
|
+
install.push(package_);
|
|
10665
|
+
continue;
|
|
10666
|
+
}
|
|
10667
|
+
const installed = await getInstalledVersion(ssh2, pm, package_.name);
|
|
10668
|
+
if (installed !== null && await installedIsHigher(ssh2, installed, package_.version)) {
|
|
10669
|
+
downgrade.push(installToken(pm, package_.name, package_.version));
|
|
10670
|
+
} else {
|
|
10671
|
+
install.push(package_);
|
|
10672
|
+
}
|
|
10673
|
+
}
|
|
10674
|
+
return { downgrade, install };
|
|
10675
|
+
}
|
|
10676
|
+
async function runVersionedInstall(parameters) {
|
|
10677
|
+
const { baseInstall, execOpts, label, packages, pm, ssh: ssh2 } = parameters;
|
|
10678
|
+
const failure = (result) => failedCommand(`[package.installed: ${label}] package installation failed`, result);
|
|
10679
|
+
let installTargets = packages;
|
|
10680
|
+
if (pm === "dnf" || pm === "yum") {
|
|
10681
|
+
const split = await collectDowngradeTokens(ssh2, pm, packages);
|
|
10682
|
+
installTargets = split.install;
|
|
10683
|
+
if (split.downgrade.length > 0) {
|
|
10684
|
+
const down = await ssh2.exec(DOWNGRADE_COMMANDS[pm](split.downgrade.join(" ")), execOpts);
|
|
10685
|
+
if (down.code !== 0) return failure(down);
|
|
10686
|
+
}
|
|
10687
|
+
}
|
|
10688
|
+
if (installTargets.length === 0) return null;
|
|
10689
|
+
const install = await ssh2.exec(buildInstallCommand(baseInstall, pm, installTargets), execOpts);
|
|
10690
|
+
return install.code === 0 ? null : failure(install);
|
|
10691
|
+
}
|
|
10692
|
+
|
|
10693
|
+
// src/modules/package.ts
|
|
10694
|
+
var EXEC_OPTS12 = { ignoreExitCode: true, silent: true };
|
|
10695
|
+
function execOptions(options) {
|
|
10696
|
+
if (options?.timeout === void 0) return EXEC_OPTS12;
|
|
10697
|
+
return { ...EXEC_OPTS12, timeout: options.timeout };
|
|
10573
10698
|
}
|
|
10574
10699
|
var pmCache = /* @__PURE__ */ new WeakMap();
|
|
10575
10700
|
var INSTALL_COMMANDS = {
|
|
@@ -10635,35 +10760,45 @@ async function isPackageInstalled(ssh2, pm, packageName) {
|
|
|
10635
10760
|
}
|
|
10636
10761
|
}
|
|
10637
10762
|
}
|
|
10763
|
+
async function isPackageSatisfied(ssh2, pm, target) {
|
|
10764
|
+
if (target.version === void 0) {
|
|
10765
|
+
return isPackageInstalled(ssh2, pm, target.name);
|
|
10766
|
+
}
|
|
10767
|
+
const installed = await getInstalledVersion(ssh2, pm, target.name);
|
|
10768
|
+
if (installed === null) return false;
|
|
10769
|
+
return versionsEqual({ installed, pinned: target.version, pm, ssh: ssh2 });
|
|
10770
|
+
}
|
|
10638
10771
|
async function hasAnyMissingPackage(ssh2, pm, packages) {
|
|
10639
|
-
for (const
|
|
10640
|
-
if (!await
|
|
10772
|
+
for (const package_ of packages) {
|
|
10773
|
+
if (!await isPackageSatisfied(ssh2, pm, package_)) return true;
|
|
10641
10774
|
}
|
|
10642
10775
|
return false;
|
|
10643
10776
|
}
|
|
10644
10777
|
async function collectStillMissingPackages(ssh2, pm, packages) {
|
|
10645
10778
|
const stillMissing = [];
|
|
10646
|
-
for (const
|
|
10647
|
-
if (!await
|
|
10648
|
-
stillMissing.push(
|
|
10779
|
+
for (const package_ of packages) {
|
|
10780
|
+
if (!await isPackageSatisfied(ssh2, pm, package_)) {
|
|
10781
|
+
stillMissing.push(package_.name);
|
|
10649
10782
|
}
|
|
10650
10783
|
}
|
|
10651
10784
|
return stillMissing;
|
|
10652
10785
|
}
|
|
10653
10786
|
async function runInstallAndVerify(parameters) {
|
|
10654
10787
|
const { options, packages, pm, ssh: ssh2 } = parameters;
|
|
10655
|
-
const
|
|
10656
|
-
const
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10788
|
+
const label = describePackages(packages);
|
|
10789
|
+
const failure = await runVersionedInstall({
|
|
10790
|
+
baseInstall: INSTALL_COMMANDS[pm],
|
|
10791
|
+
execOpts: execOptions(options),
|
|
10792
|
+
label,
|
|
10793
|
+
packages,
|
|
10794
|
+
pm,
|
|
10795
|
+
ssh: ssh2
|
|
10796
|
+
});
|
|
10797
|
+
if (failure) return failure;
|
|
10663
10798
|
const stillMissing = await collectStillMissingPackages(ssh2, pm, packages);
|
|
10664
10799
|
if (stillMissing.length > 0) {
|
|
10665
10800
|
return failed(
|
|
10666
|
-
`[package.installed: ${
|
|
10801
|
+
`[package.installed: ${label}] packages still missing after install: ${stillMissing.join(", ")}`
|
|
10667
10802
|
);
|
|
10668
10803
|
}
|
|
10669
10804
|
return { status: "changed" };
|
|
@@ -10678,8 +10813,9 @@ var pkg = {
|
|
|
10678
10813
|
* Pass an `UpgradeOptions` object as the last argument to override the SSH
|
|
10679
10814
|
* timeout for slow remove operations.
|
|
10680
10815
|
*
|
|
10681
|
-
* @param packagesAndOptions - One or more package names
|
|
10682
|
-
*
|
|
10816
|
+
* @param packagesAndOptions - One or more package names (bare strings or
|
|
10817
|
+
* `PackageSpec` objects without a `version`), optionally followed by an
|
|
10818
|
+
* `UpgradeOptions` object as the last argument.
|
|
10683
10819
|
* @returns A Module that removes the packages if any are present.
|
|
10684
10820
|
*
|
|
10685
10821
|
* @example
|
|
@@ -10688,28 +10824,33 @@ var pkg = {
|
|
|
10688
10824
|
*/
|
|
10689
10825
|
absent(...packagesAndOptions) {
|
|
10690
10826
|
const { options, packages } = splitPackagesAndOptions(packagesAndOptions);
|
|
10691
|
-
|
|
10827
|
+
validatePackages("package.absent", packages);
|
|
10828
|
+
for (const p of packages) {
|
|
10829
|
+
if (p.version !== void 0) {
|
|
10830
|
+
throw new Error(
|
|
10831
|
+
`package.absent: version pinning is not supported (package ${JSON.stringify(p.name)})`
|
|
10832
|
+
);
|
|
10833
|
+
}
|
|
10834
|
+
}
|
|
10835
|
+
const names = packages.map((p) => p.name);
|
|
10836
|
+
const label = names.join(", ");
|
|
10692
10837
|
return {
|
|
10693
10838
|
async apply(ssh2) {
|
|
10694
|
-
if (!ssh2)
|
|
10695
|
-
return failed(`[package.absent: ${packages.join(", ")}] SSH connection is required`);
|
|
10839
|
+
if (!ssh2) return failed(`[package.absent: ${label}] SSH connection is required`);
|
|
10696
10840
|
const pm = await detectPackageManager(ssh2);
|
|
10697
|
-
if (!pm) return missingPackageManager(`package.absent: ${
|
|
10841
|
+
if (!pm) return missingPackageManager(`package.absent: ${label}`);
|
|
10698
10842
|
let anyInstalled = false;
|
|
10699
|
-
for (const packageName of
|
|
10843
|
+
for (const packageName of names) {
|
|
10700
10844
|
if (await isPackageInstalled(ssh2, pm, packageName)) {
|
|
10701
10845
|
anyInstalled = true;
|
|
10702
10846
|
break;
|
|
10703
10847
|
}
|
|
10704
10848
|
}
|
|
10705
10849
|
if (!anyInstalled) return { status: "ok" };
|
|
10706
|
-
const quoted =
|
|
10850
|
+
const quoted = names.map((p) => shellQuote(p)).join(" ");
|
|
10707
10851
|
const result = await ssh2.exec(REMOVE_COMMANDS[pm](quoted), execOptions(options));
|
|
10708
10852
|
if (result.code !== 0) {
|
|
10709
|
-
return failedCommand(
|
|
10710
|
-
`[package.absent: ${packages.join(", ")}] package removal failed`,
|
|
10711
|
-
result
|
|
10712
|
-
);
|
|
10853
|
+
return failedCommand(`[package.absent: ${label}] package removal failed`, result);
|
|
10713
10854
|
}
|
|
10714
10855
|
return { status: "changed" };
|
|
10715
10856
|
},
|
|
@@ -10717,12 +10858,12 @@ var pkg = {
|
|
|
10717
10858
|
if (!ssh2) return NEEDS_APPLY;
|
|
10718
10859
|
const pm = await detectPackageManager(ssh2);
|
|
10719
10860
|
if (!pm) return NEEDS_APPLY;
|
|
10720
|
-
for (const p of
|
|
10861
|
+
for (const p of names) {
|
|
10721
10862
|
if (await isPackageInstalled(ssh2, pm, p)) return NEEDS_APPLY;
|
|
10722
10863
|
}
|
|
10723
10864
|
return "ok";
|
|
10724
10865
|
},
|
|
10725
|
-
name: `package.absent: ${
|
|
10866
|
+
name: `package.absent: ${label}`
|
|
10726
10867
|
};
|
|
10727
10868
|
},
|
|
10728
10869
|
/**
|
|
@@ -10734,24 +10875,32 @@ var pkg = {
|
|
|
10734
10875
|
* Pass an `UpgradeOptions` object as the last argument to override the SSH
|
|
10735
10876
|
* timeout for slow install operations.
|
|
10736
10877
|
*
|
|
10737
|
-
*
|
|
10738
|
-
*
|
|
10878
|
+
* Pass a `PackageSpec` (`{ name, version }`) to pin a package to an exact
|
|
10879
|
+
* version; `check` then reports drift against the installed version and
|
|
10880
|
+
* `apply` converges on the pin, including a downgrade. No package holds are
|
|
10881
|
+
* created — only the requested version is installed.
|
|
10882
|
+
*
|
|
10883
|
+
* @param packagesAndOptions - One or more packages (bare name strings or
|
|
10884
|
+
* `PackageSpec` objects), optionally followed by an `UpgradeOptions` object
|
|
10885
|
+
* as the last argument.
|
|
10739
10886
|
* @returns A Module that installs missing packages.
|
|
10740
10887
|
*
|
|
10741
10888
|
* @example
|
|
10742
10889
|
* pkg.installed("git", "curl", "unzip")
|
|
10743
10890
|
* pkg.installed("texlive-full", { timeout: 900_000 })
|
|
10891
|
+
* pkg.installed({ name: "grafana", version: "13.1.0" })
|
|
10744
10892
|
*/
|
|
10745
10893
|
installed(...packagesAndOptions) {
|
|
10746
10894
|
const { options, packages } = splitPackagesAndOptions(packagesAndOptions);
|
|
10747
|
-
|
|
10895
|
+
validatePackages("package.installed", packages);
|
|
10896
|
+
const label = describePackages(packages);
|
|
10748
10897
|
return {
|
|
10749
10898
|
async apply(ssh2) {
|
|
10750
10899
|
if (!ssh2) {
|
|
10751
|
-
return failed(`[package.installed: ${
|
|
10900
|
+
return failed(`[package.installed: ${label}] SSH connection is required`);
|
|
10752
10901
|
}
|
|
10753
10902
|
const pm = await detectPackageManager(ssh2);
|
|
10754
|
-
if (!pm) return missingPackageManager(`package.installed: ${
|
|
10903
|
+
if (!pm) return missingPackageManager(`package.installed: ${label}`);
|
|
10755
10904
|
const anyMissing = await hasAnyMissingPackage(ssh2, pm, packages);
|
|
10756
10905
|
if (!anyMissing) return { status: "ok" };
|
|
10757
10906
|
return runInstallAndVerify({ options, packages, pm, ssh: ssh2 });
|
|
@@ -10761,11 +10910,11 @@ var pkg = {
|
|
|
10761
10910
|
const pm = await detectPackageManager(ssh2);
|
|
10762
10911
|
if (!pm) return NEEDS_APPLY;
|
|
10763
10912
|
for (const p of packages) {
|
|
10764
|
-
if (!await
|
|
10913
|
+
if (!await isPackageSatisfied(ssh2, pm, p)) return NEEDS_APPLY;
|
|
10765
10914
|
}
|
|
10766
10915
|
return "ok";
|
|
10767
10916
|
},
|
|
10768
|
-
name: `package.installed: ${
|
|
10917
|
+
name: `package.installed: ${label}`
|
|
10769
10918
|
};
|
|
10770
10919
|
},
|
|
10771
10920
|
/**
|
package/dist/cli.js
CHANGED
|
@@ -6262,7 +6262,7 @@ async function runApplyCommand(file, options, run = runPlaybook) {
|
|
|
6262
6262
|
if (options.diff && !options.dryRun) {
|
|
6263
6263
|
throw new CliUsageError("--diff requires --dry-run");
|
|
6264
6264
|
}
|
|
6265
|
-
printCliHeader("0.
|
|
6265
|
+
printCliHeader("0.15.1-0840e3c");
|
|
6266
6266
|
const environmentOverrides = applyCliEnvironmentOverrides(options.env, {
|
|
6267
6267
|
firstRun: options.firstRun
|
|
6268
6268
|
});
|
|
@@ -6300,7 +6300,7 @@ function renderSubcommandOptionsHelp(command) {
|
|
|
6300
6300
|
return ["", `Options for "paratix ${command.name()} <file>":`, ...rows].join("\n");
|
|
6301
6301
|
}
|
|
6302
6302
|
var program = new Command();
|
|
6303
|
-
program.name("paratix").description("Idempotent VPS setup tool in TypeScript").version("0.
|
|
6303
|
+
program.name("paratix").description("Idempotent VPS setup tool in TypeScript").version("0.15.1-0840e3c");
|
|
6304
6304
|
var applyCommand = program.command("apply <file>").description("Apply a server definition").option(
|
|
6305
6305
|
"--diff",
|
|
6306
6306
|
"When combined with --dry-run, show a unified diff per module that would change.",
|
|
@@ -339,6 +339,27 @@ type UpgradeOptions = {
|
|
|
339
339
|
/** Override the SSH layer's command timeout (milliseconds). */
|
|
340
340
|
timeout?: number;
|
|
341
341
|
};
|
|
342
|
+
/**
|
|
343
|
+
* A package to install, optionally pinned to an exact version.
|
|
344
|
+
*
|
|
345
|
+
* A bare string package name is equivalent to `{ name }` (no version pin). The
|
|
346
|
+
* package-manager-specific version syntax (`name=version`, `name-version`) is
|
|
347
|
+
* never accepted as a pass-through string — it is built from the structured
|
|
348
|
+
* `name`/`version` fields so the raw PM syntax can never leak into a playbook.
|
|
349
|
+
*/
|
|
350
|
+
type PackageSpec = {
|
|
351
|
+
/** Package name (validated against {@link PACKAGE_NAME_PATTERN}). */
|
|
352
|
+
name: string;
|
|
353
|
+
/**
|
|
354
|
+
* Exact version to pin to. When set, `check` reports drift against the
|
|
355
|
+
* installed version and `apply` converges on this version (including a
|
|
356
|
+
* downgrade). When omitted, only presence is enforced.
|
|
357
|
+
*/
|
|
358
|
+
version?: string;
|
|
359
|
+
};
|
|
360
|
+
/** Argument accepted by the variadic `installed`/`absent` package methods. */
|
|
361
|
+
type PackageArgument = PackageSpec | string | UpgradeOptions;
|
|
362
|
+
|
|
342
363
|
/**
|
|
343
364
|
* Distro-agnostic package management module.
|
|
344
365
|
*
|
|
@@ -367,15 +388,16 @@ declare const pkg: {
|
|
|
367
388
|
* Pass an `UpgradeOptions` object as the last argument to override the SSH
|
|
368
389
|
* timeout for slow remove operations.
|
|
369
390
|
*
|
|
370
|
-
* @param packagesAndOptions - One or more package names
|
|
371
|
-
*
|
|
391
|
+
* @param packagesAndOptions - One or more package names (bare strings or
|
|
392
|
+
* `PackageSpec` objects without a `version`), optionally followed by an
|
|
393
|
+
* `UpgradeOptions` object as the last argument.
|
|
372
394
|
* @returns A Module that removes the packages if any are present.
|
|
373
395
|
*
|
|
374
396
|
* @example
|
|
375
397
|
* pkg.absent("vim", "nano")
|
|
376
398
|
* pkg.absent("vim", "nano", { timeout: 600_000 })
|
|
377
399
|
*/
|
|
378
|
-
absent(...packagesAndOptions:
|
|
400
|
+
absent(...packagesAndOptions: PackageArgument[]): Module;
|
|
379
401
|
/**
|
|
380
402
|
* Ensure the given packages are installed.
|
|
381
403
|
*
|
|
@@ -385,15 +407,22 @@ declare const pkg: {
|
|
|
385
407
|
* Pass an `UpgradeOptions` object as the last argument to override the SSH
|
|
386
408
|
* timeout for slow install operations.
|
|
387
409
|
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
410
|
+
* Pass a `PackageSpec` (`{ name, version }`) to pin a package to an exact
|
|
411
|
+
* version; `check` then reports drift against the installed version and
|
|
412
|
+
* `apply` converges on the pin, including a downgrade. No package holds are
|
|
413
|
+
* created — only the requested version is installed.
|
|
414
|
+
*
|
|
415
|
+
* @param packagesAndOptions - One or more packages (bare name strings or
|
|
416
|
+
* `PackageSpec` objects), optionally followed by an `UpgradeOptions` object
|
|
417
|
+
* as the last argument.
|
|
390
418
|
* @returns A Module that installs missing packages.
|
|
391
419
|
*
|
|
392
420
|
* @example
|
|
393
421
|
* pkg.installed("git", "curl", "unzip")
|
|
394
422
|
* pkg.installed("texlive-full", { timeout: 900_000 })
|
|
423
|
+
* pkg.installed({ name: "grafana", version: "13.1.0" })
|
|
395
424
|
*/
|
|
396
|
-
installed(...packagesAndOptions:
|
|
425
|
+
installed(...packagesAndOptions: PackageArgument[]): Module;
|
|
397
426
|
/**
|
|
398
427
|
* Refresh the package manager's package lists once per dated flag.
|
|
399
428
|
*
|
|
@@ -2108,4 +2137,4 @@ declare const user: {
|
|
|
2108
2137
|
present(name: string, options?: UserOptions): Module;
|
|
2109
2138
|
};
|
|
2110
2139
|
|
|
2111
|
-
export { op as A, pkg as B, quadlet as C, releaseUpgrade as D, type Environment as E, rsync as F, script as G, service as H, ssh as I, sshd as J, swap as K, sysctl as L, type Module as M, NEEDS_APPLY as N, type OrchestrationStep as O, system as P, systemd as Q, timer as R, type SshdPortMetaEntry as S, ufw as T, user as U, type
|
|
2140
|
+
export { op as A, pkg as B, quadlet as C, releaseUpgrade as D, type Environment as E, rsync as F, script as G, service as H, ssh as I, sshd as J, swap as K, sysctl as L, type Module as M, NEEDS_APPLY as N, type OrchestrationStep as O, system as P, systemd as Q, timer as R, type SshdPortMetaEntry as S, ufw as T, user as U, type PackageSpec as V, type UpgradeOptions as W, type ModuleMetaEntry as a, type MetaEnvironmentValue as b, type EnvironmentMetaEntry as c, type SystemHostMetaEntry as d, type SystemRebootMetaEntry as e, type ModuleResult as f, type ExecResult as g, type ModuleStatus as h, type SshConnection as i, type ShutdownSignal as j, type ServerDefinition as k, type EnvironmentValue as l, type ExecOptions as m, type SshConfig as n, apt as o, archive as p, command as q, compose as r, cron as s, download as t, file as u, git as v, group as w, hostname as x, mount as y, net as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as Environment, M as Module, a as ModuleMetaEntry, b as MetaEnvironmentValue, c as EnvironmentMetaEntry, S as SshdPortMetaEntry, d as SystemHostMetaEntry, e as SystemRebootMetaEntry, f as ModuleResult, g as ExecResult, h as ModuleStatus, i as SshConnection, O as OrchestrationStep, j as ShutdownSignal, k as ServerDefinition } from './index-
|
|
2
|
-
export { l as EnvironmentValue, m as ExecOptions, N as NEEDS_APPLY, n as SshConfig, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from './index-
|
|
1
|
+
import { E as Environment, M as Module, a as ModuleMetaEntry, b as MetaEnvironmentValue, c as EnvironmentMetaEntry, S as SshdPortMetaEntry, d as SystemHostMetaEntry, e as SystemRebootMetaEntry, f as ModuleResult, g as ExecResult, h as ModuleStatus, i as SshConnection, O as OrchestrationStep, j as ShutdownSignal, k as ServerDefinition } from './index-Da9ccU5K.js';
|
|
2
|
+
export { l as EnvironmentValue, m as ExecOptions, N as NEEDS_APPLY, n as SshConfig, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from './index-Da9ccU5K.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Fail the run if a condition on the current env is not satisfied.
|
package/dist/index.js
CHANGED
package/dist/modules/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { V as UpgradeOptions, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from '../index-
|
|
1
|
+
export { V as PackageSpec, W as UpgradeOptions, o as apt, p as archive, q as command, r as compose, s as cron, t as download, u as file, v as git, w as group, x as hostname, y as mount, z as net, A as op, B as package, C as quadlet, D as releaseUpgrade, F as rsync, G as script, H as service, I as ssh, J as sshd, K as swap, L as sysctl, P as system, Q as systemd, R as timer, T as ufw, U as user } from '../index-Da9ccU5K.js';
|
package/dist/modules/index.js
CHANGED
package/llm-guide.md
CHANGED
|
@@ -277,12 +277,12 @@ that are expected to respond more slowly.
|
|
|
277
277
|
|
|
278
278
|
Import with renaming: `import { package as pkg } from "paratix/modules"`. The word `package` is reserved in JavaScript, so you must alias it.
|
|
279
279
|
|
|
280
|
-
| Method | Signature
|
|
281
|
-
| ------------------- |
|
|
282
|
-
| `package.installed` | `(...packagesAndOptions: Array<string \| UpgradeOptions>): Module` | Yes |
|
|
283
|
-
| `package.absent` | `(...packagesAndOptions: Array<string \| UpgradeOptions>): Module` | Yes |
|
|
284
|
-
| `package.update` | `(date: string, options?: UpgradeOptions): Module`
|
|
285
|
-
| `package.upgrade` | `(date: string, options?: UpgradeOptions): Module`
|
|
280
|
+
| Method | Signature | Idempotent |
|
|
281
|
+
| ------------------- | --------------------------------------------------------------------------------- | -------------------- |
|
|
282
|
+
| `package.installed` | `(...packagesAndOptions: Array<string \| PackageSpec \| UpgradeOptions>): Module` | Yes |
|
|
283
|
+
| `package.absent` | `(...packagesAndOptions: Array<string \| PackageSpec \| UpgradeOptions>): Module` | Yes |
|
|
284
|
+
| `package.update` | `(date: string, options?: UpgradeOptions): Module` | Yes (versioned flag) |
|
|
285
|
+
| `package.upgrade` | `(date: string, options?: UpgradeOptions): Module` | Yes (versioned flag) |
|
|
286
286
|
|
|
287
287
|
#### `UpgradeOptions`
|
|
288
288
|
|
|
@@ -293,10 +293,10 @@ type UpgradeOptions = {
|
|
|
293
293
|
}
|
|
294
294
|
```
|
|
295
295
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
296
|
+
Für `package.installed` / `package.absent` wird das Options-Objekt als **letztes** Argument
|
|
297
|
+
nach den Paketnamen übergeben; bestehende variadische Aufrufe wie `pkg.installed("git", "curl")`
|
|
298
|
+
funktionieren unverändert weiter. Verwende einen höheren `timeout` für langsame Operationen auf
|
|
299
|
+
Produktionsservern mit großem Update-Rückstand:
|
|
300
300
|
|
|
301
301
|
```typescript
|
|
302
302
|
pkg.upgrade("2026-05-01", { timeout: 900_000 })
|
|
@@ -304,6 +304,57 @@ pkg.installed("texlive-full", { timeout: 900_000 })
|
|
|
304
304
|
apt.distUpgrade("2026-05-01", { timeout: 1_200_000 })
|
|
305
305
|
```
|
|
306
306
|
|
|
307
|
+
#### `PackageSpec` — Versions-Pinning
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
type PackageSpec = {
|
|
311
|
+
name: string
|
|
312
|
+
version?: string // Exakte Zielversion. Ohne `version` wird nur die Anwesenheit erzwungen.
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Ein Paket kann auf eine exakte Version festgenagelt werden, indem statt eines Strings ein
|
|
317
|
+
`PackageSpec`-Objekt übergeben wird. Ein bloßer String `"grafana"` ist äquivalent zu
|
|
318
|
+
`{ name: "grafana" }` (kein Pin). Die paketmanager-spezifische Syntax (`name=version`,
|
|
319
|
+
`name-version`) wird **niemals** als Pass-through-String akzeptiert — ein String mit `=` bleibt
|
|
320
|
+
ein ungültiger Paketname. Die Argumente werden über ihre **Form** unterschieden, nicht über die
|
|
321
|
+
Position: ein Objekt **mit** `name`-Feld ist ein `PackageSpec` (darf auch an letzter Stelle
|
|
322
|
+
stehen), ein Objekt **ohne** `name`-Feld an letzter Position ist das `UpgradeOptions`-Objekt.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
pkg.installed({ name: "grafana", version: "13.1.0" })
|
|
326
|
+
pkg.installed("curl", { name: "grafana", version: "13.1.0" }, { timeout: 600_000 })
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Übersetzung des Install-Tokens je Paketmanager:
|
|
330
|
+
|
|
331
|
+
| Paketmanager | Install-Token | Beispiel |
|
|
332
|
+
| ------------ | -------------- | ---------------------- |
|
|
333
|
+
| apt | `name=version` | `grafana=13.1.0` |
|
|
334
|
+
| apk | `name=version` | `grafana=13.1.0-r0` |
|
|
335
|
+
| dnf / yum | `name-version` | `grafana-13.1.0-1.el9` |
|
|
336
|
+
|
|
337
|
+
Semantik:
|
|
338
|
+
|
|
339
|
+
- **`check`** vergleicht die installierte gegen die gepinnte Version; jede Abweichung ergibt
|
|
340
|
+
`needs-apply`. Ohne gepinnte Version bleibt es eine reine Anwesenheitsprüfung.
|
|
341
|
+
- **`apply`** installiert exakt die gepinnte Version — auch als **Downgrade**. Für apt wird bei
|
|
342
|
+
gepinnter Version automatisch `--allow-downgrades` ergänzt; für dnf/yum wird bei einer bereits
|
|
343
|
+
installierten höheren Version `dnf downgrade` / `yum downgrade` statt `install` verwendet
|
|
344
|
+
(apk installiert die exakte Version auch abwärts ohne Extra-Flag). Die Richtungsbestimmung für
|
|
345
|
+
dnf/yum nutzt einen versionsbewussten Vergleich auf dem Zielhost (`sort -V`), damit mehrstellige
|
|
346
|
+
Komponenten wie `13.9.0` vs. `13.10.0` korrekt geordnet werden.
|
|
347
|
+
- Nach der Installation wird zusätzlich die Version verifiziert: eine Teil- oder Falschversions-
|
|
348
|
+
Installation meldet `failed`, nicht `changed`.
|
|
349
|
+
- **Kein Hold.** Es wird ausschließlich die Version installiert — es werden **keine**
|
|
350
|
+
`apt-mark hold`-Marker und **keine** `preferences.d`-Dateien angelegt. Ohne einen erneuten
|
|
351
|
+
Lauf mit gepinnter Version kann ein späterer `apt upgrade` das Paket also wieder anheben.
|
|
352
|
+
|
|
353
|
+
Gültige Versionen beginnen alphanumerisch und dürfen `[A-Za-z0-9]` sowie `. + ~ : _ -` enthalten
|
|
354
|
+
(deckt apt-Epoch/Revision wie `1:2.3-1ubuntu0.2` und rpm-`version-release` wie `13.1.0-1.el9` ab).
|
|
355
|
+
Whitespace und Shell-Metazeichen werden abgelehnt. `package.absent` ist rein namensbasiert; ein
|
|
356
|
+
`PackageSpec` mit gesetzter `version` führt dort zu einem klaren Fehler.
|
|
357
|
+
|
|
307
358
|
### `quadlet`
|
|
308
359
|
|
|
309
360
|
| Method | Signature | Idempotent |
|