@f5-sales-demo/xcsh 22.4.3 → 22.4.6
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/CHANGELOG.md +2 -0
- package/package.json +8 -8
- package/src/cli/linux-standalone-update.ts +118 -0
- package/src/cli/plugin-cli.ts +21 -14
- package/src/cli/update-cli.ts +11 -0
- package/src/cli.ts +4 -1
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/extensibility/plugins/marketplace/builtin-marketplace-snapshot.ts +1 -1
- package/src/extensibility/plugins/marketplace/builtin-marketplace.provenance.json +3 -3
- package/src/extensibility/plugins/marketplace/fetcher.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +3 -0
- package/src/extensibility/plugins/marketplace/runtime.ts +10 -0
- package/src/extensibility/plugins/marketplace/types.ts +1 -0
- package/src/host/archive.ts +182 -0
- package/src/host/host.ts +78 -0
- package/src/host/management.ts +63 -0
- package/src/host/software.ts +370 -0
- package/src/index.ts +3 -0
- package/src/integrations/registry.ts +37 -5
- package/src/integrations/setup.ts +19 -3
- package/src/integrations/types.ts +17 -1
- package/src/internal-urls/api-catalog-qmd-index.generated.ts +2 -2
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/components/btw-panel.ts +8 -6
- package/src/modes/components/plan-preview.ts +2 -6
- package/src/modes/components/question-transcript.ts +7 -19
- package/src/modes/components/searchable-select-list.ts +20 -6
- package/src/modes/components/selector-frame.ts +72 -4
- package/src/modes/components/transcript-notice.ts +8 -16
- package/src/modes/controllers/selector-controller.ts +10 -2
- package/src/person-profile/machine-collectors.ts +4 -86
- package/src/slash-commands/builtin-registry.ts +6 -2
package/src/index.ts
CHANGED
|
@@ -30,6 +30,9 @@ export * from "./extensibility/extensions";
|
|
|
30
30
|
export * from "./extensibility/skills";
|
|
31
31
|
// Slash commands
|
|
32
32
|
export { type FileSlashCommand, loadSlashCommands as discoverSlashCommands } from "./extensibility/slash-commands";
|
|
33
|
+
export { type HostInfo, host } from "./host/host";
|
|
34
|
+
export { type SoftwareRecipe, type SoftwareResolution, software } from "./host/software";
|
|
35
|
+
export type { IntegrationDefinition, IntegrationHandle, IntegrationSetupPlan } from "./integrations/types";
|
|
33
36
|
export type * from "./lsp";
|
|
34
37
|
// Main entry point
|
|
35
38
|
export * from "./main";
|
|
@@ -33,6 +33,8 @@ function freezeStrings(values: readonly string[]): readonly string[] {
|
|
|
33
33
|
function freezePlan(plan: IntegrationSetupPlan | undefined): IntegrationSetupPlan | undefined {
|
|
34
34
|
if (!plan) return undefined;
|
|
35
35
|
const frozen: IntegrationSetupPlan = {
|
|
36
|
+
...(plan.notes ? { notes: freezeStrings(plan.notes) } : {}),
|
|
37
|
+
...(plan.validate ? { validate: plan.validate } : {}),
|
|
36
38
|
pluginDependencies: freezeStrings(plan.pluginDependencies),
|
|
37
39
|
requiredEnvironment: freezeStrings(plan.requiredEnvironment),
|
|
38
40
|
profileFields: freezeStrings(plan.profileFields),
|
|
@@ -41,6 +43,14 @@ function freezePlan(plan: IntegrationSetupPlan | undefined): IntegrationSetupPla
|
|
|
41
43
|
Object.freeze({
|
|
42
44
|
...step,
|
|
43
45
|
argv: freezeStrings(step.argv),
|
|
46
|
+
...(step.archive
|
|
47
|
+
? {
|
|
48
|
+
archive: Object.freeze({
|
|
49
|
+
...step.archive,
|
|
50
|
+
versionArgs: freezeStrings(step.archive.versionArgs),
|
|
51
|
+
}),
|
|
52
|
+
}
|
|
53
|
+
: {}),
|
|
44
54
|
...(step.environment ? { environment: freezeStrings(step.environment) } : {}),
|
|
45
55
|
}),
|
|
46
56
|
),
|
|
@@ -82,8 +92,13 @@ function validatePlan(plan: IntegrationSetupPlan | undefined): void {
|
|
|
82
92
|
plan.steps.some(
|
|
83
93
|
step =>
|
|
84
94
|
!step ||
|
|
85
|
-
!(["install", "login"] as const).includes(step.kind) ||
|
|
86
|
-
|
|
95
|
+
!(["install", "login", "archive-install"] as const).includes(step.kind) ||
|
|
96
|
+
(step.kind === "archive-install"
|
|
97
|
+
? !step.archive ||
|
|
98
|
+
!/^\d+\.\d+\.\d+$/.test(step.archive.version) ||
|
|
99
|
+
!/^[a-f0-9]{64}$/.test(step.archive.sha256) ||
|
|
100
|
+
!validateArgv(step.archive.versionArgs)
|
|
101
|
+
: !validateArgv(step.argv)) ||
|
|
87
102
|
!Number.isInteger(step.timeoutMs) ||
|
|
88
103
|
step.timeoutMs < 1_000 ||
|
|
89
104
|
step.timeoutMs > MAX_SETUP_STEP_TIMEOUT_MS ||
|
|
@@ -134,7 +149,8 @@ export class IntegrationRegistry {
|
|
|
134
149
|
const existing = this.#entries.get(definition.id);
|
|
135
150
|
if (existing && existing.owner !== owner) throw new Error(`Integration ${definition.id} is already registered`);
|
|
136
151
|
|
|
137
|
-
|
|
152
|
+
let setupPlan = freezePlan(definition.setup);
|
|
153
|
+
let preparationRevision = 0;
|
|
138
154
|
const frozenDefinition = Object.freeze({
|
|
139
155
|
...definition,
|
|
140
156
|
dependencies: definition.dependencies ? freezeStrings(definition.dependencies) : undefined,
|
|
@@ -145,12 +161,28 @@ export class IntegrationRegistry {
|
|
|
145
161
|
id: definition.id,
|
|
146
162
|
name: definition.name,
|
|
147
163
|
plugin: definition.plugin,
|
|
148
|
-
setupPlan
|
|
164
|
+
get setupPlan() {
|
|
165
|
+
return setupPlan;
|
|
166
|
+
},
|
|
167
|
+
prepareSetup: async (signal?: AbortSignal) => {
|
|
168
|
+
const revision = ++preparationRevision;
|
|
169
|
+
if (!definition.prepareSetup) {
|
|
170
|
+
if (!setupPlan) throw new Error("No setup is declared");
|
|
171
|
+
return setupPlan;
|
|
172
|
+
}
|
|
173
|
+
const prepared = await definition.prepareSetup(signal);
|
|
174
|
+
signal?.throwIfAborted();
|
|
175
|
+
validatePlan(prepared);
|
|
176
|
+
if (revision !== preparationRevision || this.#entries.get(definition.id)?.handle !== handle)
|
|
177
|
+
throw new Error("Setup preparation changed; review again");
|
|
178
|
+
setupPlan = freezePlan(prepared)!;
|
|
179
|
+
return setupPlan;
|
|
180
|
+
},
|
|
149
181
|
get: (signal?: AbortSignal) => this.#get(definition.id, false, signal) as Promise<IntegrationSnapshot<T>>,
|
|
150
182
|
invalidate: () => this.invalidate(definition.id),
|
|
151
183
|
verifyAfterSetup: (reviewedPlan: IntegrationSetupPlan, signal?: AbortSignal) => {
|
|
152
184
|
const current = this.#entries.get(definition.id);
|
|
153
|
-
if (
|
|
185
|
+
if (current?.handle !== handle || reviewedPlan !== setupPlan)
|
|
154
186
|
throw new Error("Setup requires the current reviewed setup plan");
|
|
155
187
|
return this.#get(definition.id, true, signal) as Promise<IntegrationSnapshot<T>>;
|
|
156
188
|
},
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { installArchive } from "../host/archive";
|
|
2
|
+
import { host } from "../host/host";
|
|
1
3
|
import type { IntegrationHandle, IntegrationSetupPlan, IntegrationSetupStep, IntegrationSnapshot } from "./types";
|
|
2
4
|
|
|
3
5
|
export type PluginSetupTrigger = "direct-install" | "bulk-install" | "upgrade" | "cache-refresh" | "dependency-install";
|
|
@@ -9,6 +11,7 @@ export interface InstallAuthorizedSetupOptions {
|
|
|
9
11
|
readonly handles: readonly IntegrationHandle<unknown>[];
|
|
10
12
|
readonly run?: SetupStepRunner;
|
|
11
13
|
readonly signal?: AbortSignal;
|
|
14
|
+
readonly review?: (handle: IntegrationHandle<unknown>, plan: IntegrationSetupPlan) => Promise<void>;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export type SetupStepRunner = (step: IntegrationSetupStep, signal?: AbortSignal) => Promise<number>;
|
|
@@ -64,12 +67,15 @@ export function describeSetupPlan(handle: IntegrationHandle<unknown>): string {
|
|
|
64
67
|
`Required environment names: ${plan.requiredEnvironment.join(", ") || "none"}`,
|
|
65
68
|
`Person-profile categories: ${plan.profileFields.join(", ") || "none"}`,
|
|
66
69
|
];
|
|
70
|
+
lines.push(...(plan.notes ?? []));
|
|
67
71
|
if (plan.guidedAction?.kind === "context_wizard") {
|
|
68
72
|
lines.push("Guided action: native xcsh context wizard");
|
|
69
73
|
} else {
|
|
70
74
|
lines.push(
|
|
71
75
|
"Commands:",
|
|
72
|
-
...plan.steps.map(
|
|
76
|
+
...plan.steps.map(
|
|
77
|
+
step => ` ${step.kind}: ${JSON.stringify(step.archive ?? step.argv)} (timeout ${step.timeoutMs}ms)`,
|
|
78
|
+
),
|
|
73
79
|
"Verification:",
|
|
74
80
|
...plan.verification.map(step => ` ${JSON.stringify(step.argv)} (timeout ${step.timeoutMs}ms)`),
|
|
75
81
|
);
|
|
@@ -82,6 +88,11 @@ export function createSetupStepRunner(
|
|
|
82
88
|
options: SetupStepRunnerOptions = {},
|
|
83
89
|
): SetupStepRunner {
|
|
84
90
|
return async (step, signal) => {
|
|
91
|
+
if (step.kind === "archive-install") {
|
|
92
|
+
if (!step.archive) throw new Error("Missing archive installation specification");
|
|
93
|
+
await installArchive(step.archive, signal, step.timeoutMs);
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
85
96
|
const environment = { ...process.env };
|
|
86
97
|
for (const name of step.environment ?? []) {
|
|
87
98
|
const value = resolveEnvironment(name);
|
|
@@ -93,7 +104,7 @@ export function createSetupStepRunner(
|
|
|
93
104
|
const interactiveInput = step.stdin === "inherit" || step.kind === "login";
|
|
94
105
|
const output = step.kind === "login" ? "inherit" : (options.nonInteractiveOutput ?? "inherit");
|
|
95
106
|
try {
|
|
96
|
-
const child = Bun.spawn([...step.argv], {
|
|
107
|
+
const child = Bun.spawn([host.findExecutable(step.argv[0]) ?? step.argv[0], ...step.argv.slice(1)], {
|
|
97
108
|
env: environment,
|
|
98
109
|
stdin: interactiveInput ? "inherit" : "ignore",
|
|
99
110
|
stdout: output,
|
|
@@ -117,6 +128,9 @@ export async function executeReviewedSetup<T>(
|
|
|
117
128
|
): Promise<IntegrationSnapshot<T>> {
|
|
118
129
|
if (reviewedPlan !== handle.setupPlan) throw new Error("Setup requires the current reviewed setup plan");
|
|
119
130
|
if (reviewedPlan.guidedAction) throw new Error("Guided setup must run through its native interactive action");
|
|
131
|
+
if (signal?.aborted) throw new Error("Integration setup cancelled");
|
|
132
|
+
await reviewedPlan.validate?.(signal);
|
|
133
|
+
if (reviewedPlan !== handle.setupPlan) throw new Error("Setup requires the current reviewed setup plan");
|
|
120
134
|
for (const step of reviewedPlan.steps) {
|
|
121
135
|
if (signal?.aborted) throw new Error("Integration setup cancelled");
|
|
122
136
|
if ((await run(step, signal)) !== 0) throw new Error(`Integration ${step.kind} step failed`);
|
|
@@ -154,5 +168,7 @@ export async function executeInstallAuthorizedSetup(
|
|
|
154
168
|
const handle = matches[0];
|
|
155
169
|
const current = await handle.get(options.signal);
|
|
156
170
|
if (current.state !== "setup_required" && current.state !== "degraded") return current;
|
|
157
|
-
|
|
171
|
+
const plan = handle.prepareSetup ? await handle.prepareSetup(options.signal) : handle.setupPlan!;
|
|
172
|
+
await options.review?.(handle, plan);
|
|
173
|
+
return executeReviewedSetup(handle, plan, options.run, options.signal);
|
|
158
174
|
}
|
|
@@ -20,8 +20,20 @@ export interface IntegrationProbeResult<T> {
|
|
|
20
20
|
readonly retryAfterMs?: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export interface ArchiveInstall {
|
|
24
|
+
readonly version: string;
|
|
25
|
+
readonly url: string;
|
|
26
|
+
readonly sha256: string;
|
|
27
|
+
readonly destination: string;
|
|
28
|
+
readonly link: string;
|
|
29
|
+
readonly baseDir: string;
|
|
30
|
+
readonly executable: string;
|
|
31
|
+
readonly versionArgs: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
export interface IntegrationSetupStep {
|
|
24
|
-
readonly
|
|
35
|
+
readonly archive?: ArchiveInstall;
|
|
36
|
+
readonly kind: "install" | "login" | "archive-install";
|
|
25
37
|
readonly argv: readonly string[];
|
|
26
38
|
readonly timeoutMs: number;
|
|
27
39
|
/** Environment variable names inherited by this step. Values are never part of the plan. */
|
|
@@ -45,6 +57,8 @@ export interface IntegrationSetupPlan {
|
|
|
45
57
|
readonly steps: readonly IntegrationSetupStep[];
|
|
46
58
|
readonly verification: readonly IntegrationVerificationStep[];
|
|
47
59
|
readonly guidedAction?: IntegrationGuidedSetupAction;
|
|
60
|
+
readonly notes?: readonly string[];
|
|
61
|
+
readonly validate?: (signal?: AbortSignal) => Promise<void>;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
export interface IntegrationDefinition<T> {
|
|
@@ -55,6 +69,7 @@ export interface IntegrationDefinition<T> {
|
|
|
55
69
|
readonly dependencies?: readonly string[];
|
|
56
70
|
readonly successTtlMs?: number;
|
|
57
71
|
readonly setup?: IntegrationSetupPlan;
|
|
72
|
+
readonly prepareSetup?: (signal?: AbortSignal) => Promise<IntegrationSetupPlan>;
|
|
58
73
|
readonly probe: (signal?: AbortSignal) => Promise<IntegrationProbeResult<T>>;
|
|
59
74
|
/** Optional private profile projection. The registry never persists `value` itself. */
|
|
60
75
|
readonly profile?: (value: T) => ProfileCollection;
|
|
@@ -77,6 +92,7 @@ export interface IntegrationHandle<T> {
|
|
|
77
92
|
readonly name: string;
|
|
78
93
|
readonly plugin?: string;
|
|
79
94
|
readonly setupPlan?: IntegrationSetupPlan;
|
|
95
|
+
prepareSetup?(signal?: AbortSignal): Promise<IntegrationSetupPlan>;
|
|
80
96
|
get(signal?: AbortSignal): Promise<IntegrationSnapshot<T>>;
|
|
81
97
|
invalidate(): void;
|
|
82
98
|
verifyAfterSetup(reviewedPlan: IntegrationSetupPlan, signal?: AbortSignal): Promise<IntegrationSnapshot<T>>;
|