@kybernesis/create 0.14.10 → 0.14.11
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/peer-tree.d.ts +37 -0
- package/dist/peer-tree.js +62 -0
- package/dist/upgrade.js +17 -3
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who, exactly, is unhappy about the installed eve.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* `kyb upgrade` refuses to touch a project whose peer tree is broken (KYB-538),
|
|
6
|
+
* because changing a project on a bad tree is how a half-upgraded host happens.
|
|
7
|
+
* That gate read `npm ls eve`'s exit code and nothing else, so any package
|
|
8
|
+
* anywhere in the tree could veto the whole upgrade.
|
|
9
|
+
*
|
|
10
|
+
* On 2026-09-08 one did. `@github-tools/eve-extension` still declares
|
|
11
|
+
* `>=0.44.0 <0.48.0` while every Kybernesis package declares the 0.49 line the
|
|
12
|
+
* host actually runs. The upgrade aborted before `repairHostArtifacts`, so
|
|
13
|
+
* kyber did not receive the KYB-542 reclaim job it was being upgraded for, and
|
|
14
|
+
* the job had to be installed by hand.
|
|
15
|
+
*
|
|
16
|
+
* A third-party range that trails the framework is not a broken install. A
|
|
17
|
+
* Kybernesis package disagreeing with the framework it is pinned against is.
|
|
18
|
+
* So the verdict distinguishes them, and anything it cannot attribute is
|
|
19
|
+
* treated as fatal rather than waved through.
|
|
20
|
+
*/
|
|
21
|
+
export interface PeerTreeVerdict {
|
|
22
|
+
/** Nothing in the tree objects to the installed version. */
|
|
23
|
+
readonly ok: boolean;
|
|
24
|
+
/** Packages whose declared range excludes the installed version, deduplicated and sorted. */
|
|
25
|
+
readonly offenders: readonly string[];
|
|
26
|
+
/** An objection from a Kybernesis package, or one that could not be attributed. */
|
|
27
|
+
readonly fatal: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read `npm ls <pkg> --json` and say whether the upgrade may proceed.
|
|
31
|
+
*
|
|
32
|
+
* Unparseable output is fatal: not knowing the shape of the tree is exactly the
|
|
33
|
+
* case the gate exists for.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assessPeerTree(stdout: string): PeerTreeVerdict;
|
|
36
|
+
/** What to print when a third-party range trails the framework but nothing else does. */
|
|
37
|
+
export declare function staleRangeWarning(offenders: readonly string[], dependency: string): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const OWNED_SCOPE = "@kybernesis/";
|
|
2
|
+
/** Every `from node_modules/<pkg>` in one npm `invalid` string. */
|
|
3
|
+
function requiredBy(invalid) {
|
|
4
|
+
const found = [];
|
|
5
|
+
const pattern = /from node_modules\/((?:@[^/\s,]+\/)?[^/\s,]+)/g;
|
|
6
|
+
let match = pattern.exec(invalid);
|
|
7
|
+
while (match !== null) {
|
|
8
|
+
if (match[1])
|
|
9
|
+
found.push(match[1]);
|
|
10
|
+
match = pattern.exec(invalid);
|
|
11
|
+
}
|
|
12
|
+
return found;
|
|
13
|
+
}
|
|
14
|
+
function collect(node, offenders, unattributed) {
|
|
15
|
+
if (!node || typeof node !== "object")
|
|
16
|
+
return;
|
|
17
|
+
const record = node;
|
|
18
|
+
if (typeof record.invalid === "string" && record.invalid.length > 0) {
|
|
19
|
+
const owners = requiredBy(record.invalid);
|
|
20
|
+
// An objection nobody signed is not evidence that the tree is fine.
|
|
21
|
+
if (owners.length === 0)
|
|
22
|
+
unattributed.seen = true;
|
|
23
|
+
for (const owner of owners)
|
|
24
|
+
offenders.add(owner);
|
|
25
|
+
}
|
|
26
|
+
const dependencies = record.dependencies;
|
|
27
|
+
if (dependencies && typeof dependencies === "object") {
|
|
28
|
+
for (const child of Object.values(dependencies)) {
|
|
29
|
+
collect(child, offenders, unattributed);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Read `npm ls <pkg> --json` and say whether the upgrade may proceed.
|
|
35
|
+
*
|
|
36
|
+
* Unparseable output is fatal: not knowing the shape of the tree is exactly the
|
|
37
|
+
* case the gate exists for.
|
|
38
|
+
*/
|
|
39
|
+
export function assessPeerTree(stdout) {
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(stdout);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { ok: false, offenders: [], fatal: true };
|
|
46
|
+
}
|
|
47
|
+
const offenders = new Set();
|
|
48
|
+
const unattributed = { seen: false };
|
|
49
|
+
collect(parsed, offenders, unattributed);
|
|
50
|
+
if (offenders.size === 0 && !unattributed.seen)
|
|
51
|
+
return { ok: true, offenders: [], fatal: false };
|
|
52
|
+
const sorted = [...offenders].sort();
|
|
53
|
+
const ours = sorted.some((name) => name.startsWith(OWNED_SCOPE));
|
|
54
|
+
return { ok: false, offenders: sorted, fatal: ours || unattributed.seen };
|
|
55
|
+
}
|
|
56
|
+
/** What to print when a third-party range trails the framework but nothing else does. */
|
|
57
|
+
export function staleRangeWarning(offenders, dependency) {
|
|
58
|
+
const names = offenders.join(", ");
|
|
59
|
+
return (`${names} declare${offenders.length === 1 ? "s" : ""} a ${dependency} range that excludes the installed version. ` +
|
|
60
|
+
`Every Kybernesis package agrees with it, and the runtime already runs on it, so the upgrade continues. ` +
|
|
61
|
+
`Upgrade or replace ${offenders.length === 1 ? "that package" : "those packages"} when a compatible release exists.`);
|
|
62
|
+
}
|
package/dist/upgrade.js
CHANGED
|
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { upsertEnv } from "./envfile.js";
|
|
7
7
|
import { reconcileHostArtifact } from "./host-artifacts.js";
|
|
8
|
+
import { assessPeerTree, staleRangeWarning } from "./peer-tree.js";
|
|
8
9
|
import { dockerPruneCronArtifact } from "./docker-prune-cron.js";
|
|
9
10
|
import { findMatchingAgentServiceUnit, repairManageRestart } from "./systemd.js";
|
|
10
11
|
import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
|
|
@@ -821,9 +822,22 @@ export async function upgrade(options = {}) {
|
|
|
821
822
|
}
|
|
822
823
|
const validation = commandResult("npm", ["ls", "eve"], cwd);
|
|
823
824
|
if (validation.status !== 0) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
825
|
+
/**
|
|
826
|
+
* Judge WHO objects, not merely that something did.
|
|
827
|
+
*
|
|
828
|
+
* A third-party extension whose declared range trails the framework is not
|
|
829
|
+
* a broken install, and letting it veto the upgrade left kyber without the
|
|
830
|
+
* host artifacts it was being upgraded for (KYB-548). A Kybernesis package
|
|
831
|
+
* disagreeing with the framework it is pinned against still stops
|
|
832
|
+
* everything, and so does an objection we cannot attribute.
|
|
833
|
+
*/
|
|
834
|
+
const verdict = assessPeerTree(commandResult("npm", ["ls", "eve", "--json"], cwd, false).stdout);
|
|
835
|
+
if (verdict.fatal || verdict.ok) {
|
|
836
|
+
fail("npm ls eve failed. The installed peer tree is not valid.");
|
|
837
|
+
recoveryGuidance(targets, stoppedBridge ?? undefined);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
console.log(yellow(` ! ${staleRangeWarning(verdict.offenders, "eve")}`));
|
|
827
841
|
}
|
|
828
842
|
if (stoppedBridge && !startStoppedBridge(cwd, stoppedBridge))
|
|
829
843
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.11",
|
|
4
4
|
"description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|