@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.
Files changed (34) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/package.json +8 -8
  3. package/src/cli/linux-standalone-update.ts +118 -0
  4. package/src/cli/plugin-cli.ts +21 -14
  5. package/src/cli/update-cli.ts +11 -0
  6. package/src/cli.ts +4 -1
  7. package/src/extensibility/extensions/loader.ts +9 -1
  8. package/src/extensibility/extensions/types.ts +2 -0
  9. package/src/extensibility/plugins/marketplace/builtin-marketplace-snapshot.ts +1 -1
  10. package/src/extensibility/plugins/marketplace/builtin-marketplace.provenance.json +3 -3
  11. package/src/extensibility/plugins/marketplace/fetcher.ts +6 -0
  12. package/src/extensibility/plugins/marketplace/manager.ts +3 -0
  13. package/src/extensibility/plugins/marketplace/runtime.ts +10 -0
  14. package/src/extensibility/plugins/marketplace/types.ts +1 -0
  15. package/src/host/archive.ts +182 -0
  16. package/src/host/host.ts +78 -0
  17. package/src/host/management.ts +63 -0
  18. package/src/host/software.ts +370 -0
  19. package/src/index.ts +3 -0
  20. package/src/integrations/registry.ts +37 -5
  21. package/src/integrations/setup.ts +19 -3
  22. package/src/integrations/types.ts +17 -1
  23. package/src/internal-urls/api-catalog-qmd-index.generated.ts +2 -2
  24. package/src/internal-urls/build-info.generated.ts +8 -8
  25. package/src/internal-urls/docs-index.generated.ts +1 -1
  26. package/src/modes/components/btw-panel.ts +8 -6
  27. package/src/modes/components/plan-preview.ts +2 -6
  28. package/src/modes/components/question-transcript.ts +7 -19
  29. package/src/modes/components/searchable-select-list.ts +20 -6
  30. package/src/modes/components/selector-frame.ts +72 -4
  31. package/src/modes/components/transcript-notice.ts +8 -16
  32. package/src/modes/controllers/selector-controller.ts +10 -2
  33. package/src/person-profile/machine-collectors.ts +4 -86
  34. 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
- !validateArgv(step.argv) ||
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
- const setupPlan = freezePlan(definition.setup);
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 (!current?.definition.setup || reviewedPlan !== current.definition.setup)
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(step => ` ${step.kind}: ${JSON.stringify(step.argv)} (timeout ${step.timeoutMs}ms)`),
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
- return executeReviewedSetup(handle, handle.setupPlan!, options.run, options.signal);
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 kind: "install" | "login";
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>>;