paratix 0.14.0 → 0.15.0
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-GB5QZWUM.js} +190 -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,135 @@ 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 raw = await ssh2.output(`apk version -v ${quoted}`);
|
|
10608
|
+
return parseApkVersion(raw.trim(), packageName);
|
|
10609
|
+
}
|
|
10610
|
+
if (pm === "apt") {
|
|
10611
|
+
const raw = await ssh2.output(`dpkg-query -W -f='\${Version}' ${quoted} 2>/dev/null`);
|
|
10612
|
+
const aptVersion = raw.trim();
|
|
10613
|
+
return aptVersion.length > 0 ? aptVersion : null;
|
|
10614
|
+
}
|
|
10615
|
+
const result = await ssh2.exec(
|
|
10616
|
+
`rpm -q --qf '%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n' ${quoted}`,
|
|
10617
|
+
VERSION_EXEC_OPTS
|
|
10618
|
+
);
|
|
10619
|
+
if (result.code !== 0) return null;
|
|
10620
|
+
const lines = result.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
10621
|
+
return lines.at(-1) ?? null;
|
|
10622
|
+
}
|
|
10623
|
+
async function versionsEqual(parameters) {
|
|
10624
|
+
const { installed, pinned, pm, ssh: ssh2 } = parameters;
|
|
10625
|
+
if (pm === "apt") {
|
|
10626
|
+
return ssh2.test(`dpkg --compare-versions ${shellQuote(installed)} eq ${shellQuote(pinned)}`);
|
|
10572
10627
|
}
|
|
10628
|
+
return installed === pinned;
|
|
10629
|
+
}
|
|
10630
|
+
function describePackages(packages) {
|
|
10631
|
+
return packages.map((p) => p.version === void 0 ? p.name : `${p.name}=${p.version}`).join(", ");
|
|
10632
|
+
}
|
|
10633
|
+
function aptInstallCommand(tokens, hasPin) {
|
|
10634
|
+
return hasPin ? `DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-downgrades -- ${tokens}` : `DEBIAN_FRONTEND=noninteractive apt-get install -y -- ${tokens}`;
|
|
10635
|
+
}
|
|
10636
|
+
var DOWNGRADE_COMMANDS = {
|
|
10637
|
+
dnf: (tokens) => `dnf downgrade -y -- ${tokens}`,
|
|
10638
|
+
yum: (tokens) => `yum downgrade -y -- ${tokens}`
|
|
10639
|
+
};
|
|
10640
|
+
function buildInstallCommand(baseInstall, pm, packages) {
|
|
10641
|
+
const tokens = packages.map((p) => installToken(pm, p.name, p.version)).join(" ");
|
|
10642
|
+
const hasPin = packages.some((p) => p.version !== void 0);
|
|
10643
|
+
return pm === "apt" ? aptInstallCommand(tokens, hasPin) : baseInstall(tokens);
|
|
10644
|
+
}
|
|
10645
|
+
async function installedIsHigher(ssh2, installed, pinned) {
|
|
10646
|
+
if (installed === pinned) return false;
|
|
10647
|
+
const result = await ssh2.exec(
|
|
10648
|
+
`printf '%s\\n%s\\n' ${shellQuote(pinned)} ${shellQuote(installed)} | sort -V | tail -n1`,
|
|
10649
|
+
VERSION_EXEC_OPTS
|
|
10650
|
+
);
|
|
10651
|
+
if (result.code !== 0) return false;
|
|
10652
|
+
return result.stdout.trim() === installed;
|
|
10653
|
+
}
|
|
10654
|
+
async function collectDowngradeTokens(ssh2, pm, packages) {
|
|
10655
|
+
const downgrade = [];
|
|
10656
|
+
const install = [];
|
|
10657
|
+
for (const package_ of packages) {
|
|
10658
|
+
if (package_.version === void 0) {
|
|
10659
|
+
install.push(package_);
|
|
10660
|
+
continue;
|
|
10661
|
+
}
|
|
10662
|
+
const installed = await getInstalledVersion(ssh2, pm, package_.name);
|
|
10663
|
+
if (installed !== null && await installedIsHigher(ssh2, installed, package_.version)) {
|
|
10664
|
+
downgrade.push(installToken(pm, package_.name, package_.version));
|
|
10665
|
+
} else {
|
|
10666
|
+
install.push(package_);
|
|
10667
|
+
}
|
|
10668
|
+
}
|
|
10669
|
+
return { downgrade, install };
|
|
10670
|
+
}
|
|
10671
|
+
async function runVersionedInstall(parameters) {
|
|
10672
|
+
const { baseInstall, execOpts, label, packages, pm, ssh: ssh2 } = parameters;
|
|
10673
|
+
const failure = (result) => failedCommand(`[package.installed: ${label}] package installation failed`, result);
|
|
10674
|
+
let installTargets = packages;
|
|
10675
|
+
if (pm === "dnf" || pm === "yum") {
|
|
10676
|
+
const split = await collectDowngradeTokens(ssh2, pm, packages);
|
|
10677
|
+
installTargets = split.install;
|
|
10678
|
+
if (split.downgrade.length > 0) {
|
|
10679
|
+
const down = await ssh2.exec(DOWNGRADE_COMMANDS[pm](split.downgrade.join(" ")), execOpts);
|
|
10680
|
+
if (down.code !== 0) return failure(down);
|
|
10681
|
+
}
|
|
10682
|
+
}
|
|
10683
|
+
if (installTargets.length === 0) return null;
|
|
10684
|
+
const install = await ssh2.exec(buildInstallCommand(baseInstall, pm, installTargets), execOpts);
|
|
10685
|
+
return install.code === 0 ? null : failure(install);
|
|
10686
|
+
}
|
|
10687
|
+
|
|
10688
|
+
// src/modules/package.ts
|
|
10689
|
+
var EXEC_OPTS12 = { ignoreExitCode: true, silent: true };
|
|
10690
|
+
function execOptions(options) {
|
|
10691
|
+
if (options?.timeout === void 0) return EXEC_OPTS12;
|
|
10692
|
+
return { ...EXEC_OPTS12, timeout: options.timeout };
|
|
10573
10693
|
}
|
|
10574
10694
|
var pmCache = /* @__PURE__ */ new WeakMap();
|
|
10575
10695
|
var INSTALL_COMMANDS = {
|
|
@@ -10635,35 +10755,45 @@ async function isPackageInstalled(ssh2, pm, packageName) {
|
|
|
10635
10755
|
}
|
|
10636
10756
|
}
|
|
10637
10757
|
}
|
|
10758
|
+
async function isPackageSatisfied(ssh2, pm, target) {
|
|
10759
|
+
if (target.version === void 0) {
|
|
10760
|
+
return isPackageInstalled(ssh2, pm, target.name);
|
|
10761
|
+
}
|
|
10762
|
+
const installed = await getInstalledVersion(ssh2, pm, target.name);
|
|
10763
|
+
if (installed === null) return false;
|
|
10764
|
+
return versionsEqual({ installed, pinned: target.version, pm, ssh: ssh2 });
|
|
10765
|
+
}
|
|
10638
10766
|
async function hasAnyMissingPackage(ssh2, pm, packages) {
|
|
10639
|
-
for (const
|
|
10640
|
-
if (!await
|
|
10767
|
+
for (const package_ of packages) {
|
|
10768
|
+
if (!await isPackageSatisfied(ssh2, pm, package_)) return true;
|
|
10641
10769
|
}
|
|
10642
10770
|
return false;
|
|
10643
10771
|
}
|
|
10644
10772
|
async function collectStillMissingPackages(ssh2, pm, packages) {
|
|
10645
10773
|
const stillMissing = [];
|
|
10646
|
-
for (const
|
|
10647
|
-
if (!await
|
|
10648
|
-
stillMissing.push(
|
|
10774
|
+
for (const package_ of packages) {
|
|
10775
|
+
if (!await isPackageSatisfied(ssh2, pm, package_)) {
|
|
10776
|
+
stillMissing.push(package_.name);
|
|
10649
10777
|
}
|
|
10650
10778
|
}
|
|
10651
10779
|
return stillMissing;
|
|
10652
10780
|
}
|
|
10653
10781
|
async function runInstallAndVerify(parameters) {
|
|
10654
10782
|
const { options, packages, pm, ssh: ssh2 } = parameters;
|
|
10655
|
-
const
|
|
10656
|
-
const
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10783
|
+
const label = describePackages(packages);
|
|
10784
|
+
const failure = await runVersionedInstall({
|
|
10785
|
+
baseInstall: INSTALL_COMMANDS[pm],
|
|
10786
|
+
execOpts: execOptions(options),
|
|
10787
|
+
label,
|
|
10788
|
+
packages,
|
|
10789
|
+
pm,
|
|
10790
|
+
ssh: ssh2
|
|
10791
|
+
});
|
|
10792
|
+
if (failure) return failure;
|
|
10663
10793
|
const stillMissing = await collectStillMissingPackages(ssh2, pm, packages);
|
|
10664
10794
|
if (stillMissing.length > 0) {
|
|
10665
10795
|
return failed(
|
|
10666
|
-
`[package.installed: ${
|
|
10796
|
+
`[package.installed: ${label}] packages still missing after install: ${stillMissing.join(", ")}`
|
|
10667
10797
|
);
|
|
10668
10798
|
}
|
|
10669
10799
|
return { status: "changed" };
|
|
@@ -10678,8 +10808,9 @@ var pkg = {
|
|
|
10678
10808
|
* Pass an `UpgradeOptions` object as the last argument to override the SSH
|
|
10679
10809
|
* timeout for slow remove operations.
|
|
10680
10810
|
*
|
|
10681
|
-
* @param packagesAndOptions - One or more package names
|
|
10682
|
-
*
|
|
10811
|
+
* @param packagesAndOptions - One or more package names (bare strings or
|
|
10812
|
+
* `PackageSpec` objects without a `version`), optionally followed by an
|
|
10813
|
+
* `UpgradeOptions` object as the last argument.
|
|
10683
10814
|
* @returns A Module that removes the packages if any are present.
|
|
10684
10815
|
*
|
|
10685
10816
|
* @example
|
|
@@ -10688,28 +10819,33 @@ var pkg = {
|
|
|
10688
10819
|
*/
|
|
10689
10820
|
absent(...packagesAndOptions) {
|
|
10690
10821
|
const { options, packages } = splitPackagesAndOptions(packagesAndOptions);
|
|
10691
|
-
|
|
10822
|
+
validatePackages("package.absent", packages);
|
|
10823
|
+
for (const p of packages) {
|
|
10824
|
+
if (p.version !== void 0) {
|
|
10825
|
+
throw new Error(
|
|
10826
|
+
`package.absent: version pinning is not supported (package ${JSON.stringify(p.name)})`
|
|
10827
|
+
);
|
|
10828
|
+
}
|
|
10829
|
+
}
|
|
10830
|
+
const names = packages.map((p) => p.name);
|
|
10831
|
+
const label = names.join(", ");
|
|
10692
10832
|
return {
|
|
10693
10833
|
async apply(ssh2) {
|
|
10694
|
-
if (!ssh2)
|
|
10695
|
-
return failed(`[package.absent: ${packages.join(", ")}] SSH connection is required`);
|
|
10834
|
+
if (!ssh2) return failed(`[package.absent: ${label}] SSH connection is required`);
|
|
10696
10835
|
const pm = await detectPackageManager(ssh2);
|
|
10697
|
-
if (!pm) return missingPackageManager(`package.absent: ${
|
|
10836
|
+
if (!pm) return missingPackageManager(`package.absent: ${label}`);
|
|
10698
10837
|
let anyInstalled = false;
|
|
10699
|
-
for (const packageName of
|
|
10838
|
+
for (const packageName of names) {
|
|
10700
10839
|
if (await isPackageInstalled(ssh2, pm, packageName)) {
|
|
10701
10840
|
anyInstalled = true;
|
|
10702
10841
|
break;
|
|
10703
10842
|
}
|
|
10704
10843
|
}
|
|
10705
10844
|
if (!anyInstalled) return { status: "ok" };
|
|
10706
|
-
const quoted =
|
|
10845
|
+
const quoted = names.map((p) => shellQuote(p)).join(" ");
|
|
10707
10846
|
const result = await ssh2.exec(REMOVE_COMMANDS[pm](quoted), execOptions(options));
|
|
10708
10847
|
if (result.code !== 0) {
|
|
10709
|
-
return failedCommand(
|
|
10710
|
-
`[package.absent: ${packages.join(", ")}] package removal failed`,
|
|
10711
|
-
result
|
|
10712
|
-
);
|
|
10848
|
+
return failedCommand(`[package.absent: ${label}] package removal failed`, result);
|
|
10713
10849
|
}
|
|
10714
10850
|
return { status: "changed" };
|
|
10715
10851
|
},
|
|
@@ -10717,12 +10853,12 @@ var pkg = {
|
|
|
10717
10853
|
if (!ssh2) return NEEDS_APPLY;
|
|
10718
10854
|
const pm = await detectPackageManager(ssh2);
|
|
10719
10855
|
if (!pm) return NEEDS_APPLY;
|
|
10720
|
-
for (const p of
|
|
10856
|
+
for (const p of names) {
|
|
10721
10857
|
if (await isPackageInstalled(ssh2, pm, p)) return NEEDS_APPLY;
|
|
10722
10858
|
}
|
|
10723
10859
|
return "ok";
|
|
10724
10860
|
},
|
|
10725
|
-
name: `package.absent: ${
|
|
10861
|
+
name: `package.absent: ${label}`
|
|
10726
10862
|
};
|
|
10727
10863
|
},
|
|
10728
10864
|
/**
|
|
@@ -10734,24 +10870,32 @@ var pkg = {
|
|
|
10734
10870
|
* Pass an `UpgradeOptions` object as the last argument to override the SSH
|
|
10735
10871
|
* timeout for slow install operations.
|
|
10736
10872
|
*
|
|
10737
|
-
*
|
|
10738
|
-
*
|
|
10873
|
+
* Pass a `PackageSpec` (`{ name, version }`) to pin a package to an exact
|
|
10874
|
+
* version; `check` then reports drift against the installed version and
|
|
10875
|
+
* `apply` converges on the pin, including a downgrade. No package holds are
|
|
10876
|
+
* created — only the requested version is installed.
|
|
10877
|
+
*
|
|
10878
|
+
* @param packagesAndOptions - One or more packages (bare name strings or
|
|
10879
|
+
* `PackageSpec` objects), optionally followed by an `UpgradeOptions` object
|
|
10880
|
+
* as the last argument.
|
|
10739
10881
|
* @returns A Module that installs missing packages.
|
|
10740
10882
|
*
|
|
10741
10883
|
* @example
|
|
10742
10884
|
* pkg.installed("git", "curl", "unzip")
|
|
10743
10885
|
* pkg.installed("texlive-full", { timeout: 900_000 })
|
|
10886
|
+
* pkg.installed({ name: "grafana", version: "13.1.0" })
|
|
10744
10887
|
*/
|
|
10745
10888
|
installed(...packagesAndOptions) {
|
|
10746
10889
|
const { options, packages } = splitPackagesAndOptions(packagesAndOptions);
|
|
10747
|
-
|
|
10890
|
+
validatePackages("package.installed", packages);
|
|
10891
|
+
const label = describePackages(packages);
|
|
10748
10892
|
return {
|
|
10749
10893
|
async apply(ssh2) {
|
|
10750
10894
|
if (!ssh2) {
|
|
10751
|
-
return failed(`[package.installed: ${
|
|
10895
|
+
return failed(`[package.installed: ${label}] SSH connection is required`);
|
|
10752
10896
|
}
|
|
10753
10897
|
const pm = await detectPackageManager(ssh2);
|
|
10754
|
-
if (!pm) return missingPackageManager(`package.installed: ${
|
|
10898
|
+
if (!pm) return missingPackageManager(`package.installed: ${label}`);
|
|
10755
10899
|
const anyMissing = await hasAnyMissingPackage(ssh2, pm, packages);
|
|
10756
10900
|
if (!anyMissing) return { status: "ok" };
|
|
10757
10901
|
return runInstallAndVerify({ options, packages, pm, ssh: ssh2 });
|
|
@@ -10761,11 +10905,11 @@ var pkg = {
|
|
|
10761
10905
|
const pm = await detectPackageManager(ssh2);
|
|
10762
10906
|
if (!pm) return NEEDS_APPLY;
|
|
10763
10907
|
for (const p of packages) {
|
|
10764
|
-
if (!await
|
|
10908
|
+
if (!await isPackageSatisfied(ssh2, pm, p)) return NEEDS_APPLY;
|
|
10765
10909
|
}
|
|
10766
10910
|
return "ok";
|
|
10767
10911
|
},
|
|
10768
|
-
name: `package.installed: ${
|
|
10912
|
+
name: `package.installed: ${label}`
|
|
10769
10913
|
};
|
|
10770
10914
|
},
|
|
10771
10915
|
/**
|
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.0-645d2fd");
|
|
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.0-645d2fd");
|
|
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 |
|