Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit fcc3bd0

Browse files
committed
fix(packages/core): process clientRequired plugins before clientHosted plugins
Fixes #3191
1 parent 0a1a2c6 commit fcc3bd0

File tree

6 files changed

+111
-45
lines changed

6 files changed

+111
-45
lines changed

packages/core/src/core/command-tree.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ debug('loading')
2020

2121
import {
2222
CommandHandler,
23+
CommandOverrideHandler,
2324
CommandRegistrar,
2425
CommandTree,
2526
CommandTreeResolution,
2627
ExecType,
28+
EvaluatorArgs,
2729
CatchAllOffer,
2830
Command,
2931
CommandHandlerWithEvents,
@@ -415,7 +417,7 @@ const _read = async (
415417
// loaded, yet; so: invoke the plugin resolver and retry
416418
//
417419
const route = `/${argv.join('/')}`
418-
await resolver.resolve(route)
420+
await resolver.resolve(route, { tryCatchalls: !contextRetry || contextRetry.length === 0 })
419421
leaf = treeMatch(getModelInternal().root, argv, true) // true means read-only, don't modify the context model please
420422
evaluator = leaf && leaf.$
421423
}
@@ -550,7 +552,7 @@ export const read = async (
550552
let cmd: false | CodedError | CommandHandlerWithEvents
551553

552554
if (!noRetry) {
553-
await resolver.resolve(`/${argv.join('/')}`)
555+
await resolver.resolve(`/${argv.join('/')}`, { tryCatchalls: false })
554556
cmd = await internalRead(root, argv)
555557
}
556558

@@ -627,11 +629,30 @@ export class ImplForPlugins implements CommandRegistrar {
627629
return _subtreeSynonym(route, master, options)
628630
}
629631

630-
public async find(route: string, noOverride = true) {
632+
public async override(
633+
route: string,
634+
fromPlugin: string,
635+
overrideHandler: CommandOverrideHandler,
636+
options?: CommandOptions
637+
): Promise<Command> {
638+
const currentHandler = (await this.find(route, fromPlugin)).$
639+
if (!currentHandler) {
640+
throw new Error(`Cannot find desired command handler for ${route} from plugin ${fromPlugin}`)
641+
}
642+
643+
const handler: CommandHandler = (args: EvaluatorArgs) => overrideHandler(args, currentHandler)
644+
return this.listen(route, handler, options)
645+
}
646+
647+
public async find(route: string, fromPlugin?: string, noOverride = true) {
631648
const cmd = match(route.split('/').slice(1), true)
632649
if (!cmd || cmd.route !== route || (!noOverride && resolver && resolver.isOverridden(cmd.route))) {
633650
if (resolver) {
634-
await resolver.resolve(route)
651+
if (fromPlugin) {
652+
await resolver.resolveOne(fromPlugin)
653+
} else {
654+
await resolver.resolve(route, { tryCatchalls: false })
655+
}
635656
}
636657
return match(route.split('/').slice(1), true)
637658
} else {

packages/core/src/models/command.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ export type Response = Entity
170170
/** base command handler */
171171
export type CommandHandler<T = Response, O = ParsedOptions> = (args: EvaluatorArgs<O>) => T | Promise<T>
172172

173+
/** command handler when overriding commands from other plugins */
174+
export type CommandOverrideHandler<T = Response, O = ParsedOptions> = (
175+
args: EvaluatorArgs<O>,
176+
underlyingHandler: CommandHandler<T, O>
177+
) => T | Promise<T>
178+
173179
/**
174180
* Evaluator
175181
*
@@ -255,8 +261,14 @@ export interface CatchAllHandler extends Command {
255261
type CommandListener = (route: string, handler: CommandHandler, options?: CommandOptions) => Command
256262

257263
export interface CommandRegistrar {
258-
find: (route: string, noOverride?: boolean) => Promise<Command>
264+
find: (route: string, fromPlugin?: string, noOverride?: boolean) => Promise<Command>
259265
listen: CommandListener
266+
override: (
267+
route: string,
268+
fromPlugin: string,
269+
handler: CommandOverrideHandler,
270+
options?: CommandOptions
271+
) => Promise<Command>
260272
synonym: (route: string, handler: CommandHandler, master: Command, options?: CommandOptions) => void
261273
subtree: (route: string, options: CommandOptions) => Command
262274
subtreeSynonym: (route: string, masterTree: Command, options?: CommandOptions) => void

packages/core/src/plugins/resolver.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,35 @@ import { KuiPlugin, PluginRegistration } from '../models/plugin'
2525
const debug = Debug('core/plugins/resolver')
2626

2727
export interface PluginResolver {
28-
resolve: (route: string, options?: { subtree: boolean }) => void
28+
/**
29+
* Look for a plugin that might be able to handle the given
30+
* `route`. If `tryCatchalls` is true, then the caller is desperate,
31+
* and wants us to see if there are any plugins that might have a
32+
* catchall that could possibly service the given `route`.
33+
*
34+
*/
35+
resolve: (route: string, options?: { subtree?: boolean; tryCatchalls: boolean }) => void
36+
2937
disambiguate: (route: string) => CommandBase[]
3038
disambiguatePartial: (partial: string) => string[]
31-
resolveOne: (route: string) => Promise<KuiPlugin>
39+
40+
/**
41+
* Unconditionally resolve the given named `plugin`
42+
*
43+
*/
44+
resolveOne: (plugin: string) => Promise<KuiPlugin>
45+
46+
/**
47+
* Has the given `route` been overridden by some plugin?
48+
*
49+
*/
3250
isOverridden: (route: string) => boolean
51+
52+
/**
53+
* Is the given `plugin` the definitive master of the given `route`?
54+
* it might not be, if some other plugin has overridden it
55+
*/
56+
isAlpha: (route: string, plugin: string) => boolean
3357
}
3458

3559
/** export the prequire function */
@@ -120,6 +144,8 @@ export const makeResolver = (prescan: PrescanModel, registrar: Record<string, Ku
120144
/** a plugin resolver impl */
121145
const resolver = {
122146
isOverridden: (route: string): boolean => prescan.overrides[route] !== undefined,
147+
isAlpha: (route: string, plugin: string): boolean =>
148+
prescan.overrides[route] === undefined || prescan.overrides[route] === plugin,
123149

124150
resolveOne,
125151

@@ -143,7 +169,7 @@ export const makeResolver = (prescan: PrescanModel, registrar: Record<string, Ku
143169
},
144170

145171
/** load any plugins required by the given command */
146-
resolve: async (command: string, { subtree = false } = {}): Promise<void> => {
172+
resolve: async (command: string, { subtree = false, tryCatchalls = true } = {}): Promise<void> => {
147173
// subpath if we are looking for plugins for a subtree, e.g. for cd /auth
148174
let plugin: string
149175
let matchLen: number
@@ -160,7 +186,7 @@ export const makeResolver = (prescan: PrescanModel, registrar: Record<string, Ku
160186
}
161187
if (plugin) {
162188
await resolveOne(plugin)
163-
} else if (prescan.catchalls.length > 0) {
189+
} else if (prescan.catchalls.length > 0 && tryCatchalls) {
164190
// see if we have catchall
165191
await Promise.all(prescan.catchalls.map(_ => resolveOne(_.plugin))).catch(err => {
166192
console.error(

packages/core/src/plugins/scanner.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,9 +357,14 @@ const resolveFromLocalFilesystem = async (scanCache: ScanCache, opts: PrescanOpt
357357
}
358358
}
359359

360-
// we take the union of the client-provided plugins and the client-required plugins
361-
plugins = Object.assign({}, clientHosted.plugins, clientRequired.plugins)
362-
preloads = Object.assign({}, clientHosted.preloads, clientRequired.preloads)
360+
// we take the union of the client-provided plugins and the
361+
// client-required plugins note: place clientRequired first, so
362+
// that we process the registrations in clientRequired plugins
363+
// first (this processing occurs in topologicalSortForScan);
364+
// nodejs guarantees FIFO iteration order for object properties
365+
// https://github.com/IBM/kui/issues/3191
366+
plugins = Object.assign({}, clientRequired.plugins, clientHosted.plugins)
367+
preloads = Object.assign({}, clientRequired.preloads, clientHosted.preloads)
363368
}
364369

365370
debug('availablePlugins %s', JSON.stringify(plugins))

plugins/plugin-apache-composer/src/lib/controller/cmd/session-get.ts

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -79,38 +79,40 @@ export default async (commandTree: Commands.Registrar) => {
7979
{ usage: sessionGet('get') }
8080
)
8181

82-
// override wsk activation get
83-
const activationGet = (await commandTree.find('/wsk/activation/get')).$
84-
synonyms('activations').forEach(syn => {
85-
commandTree.listen(
86-
`/wsk/${syn}/get`,
87-
opts => {
88-
if (!activationGet) {
89-
return Promise.reject(new Error())
90-
}
91-
const last = opts.parsedOptions.last
82+
await Promise.all(
83+
synonyms('activations').map(syn => {
84+
// override wsk activation get
85+
return commandTree.override(
86+
`/wsk/${syn}/get`,
87+
'plugin-openwhisk',
88+
(opts, activationGet) => {
89+
if (!activationGet) {
90+
return Promise.reject(new Error())
91+
}
92+
const last = opts.parsedOptions.last
9293

93-
if (last) {
94-
return opts.REPL.qexec<ActivationListTable>(
95-
`wsk activation list --limit 1` + (typeof last === 'string' ? ` --name ${last}` : '')
96-
)
97-
.then(activations => activations.body)
98-
.then(activations => {
99-
if (activations.length === 0) {
100-
throw new Error('No such activation found')
101-
} else {
102-
return opts.REPL.qexec<Activation>(`wsk activation get ${activations[0].activationId}`)
103-
}
104-
})
105-
}
94+
if (last) {
95+
return opts.REPL.qexec<ActivationListTable>(
96+
`wsk activation list --limit 1` + (typeof last === 'string' ? ` --name ${last}` : '')
97+
)
98+
.then(activations => activations.body)
99+
.then(activations => {
100+
if (activations.length === 0) {
101+
throw new Error('No such activation found')
102+
} else {
103+
return opts.REPL.qexec<Activation>(`wsk activation get ${activations[0].activationId}`)
104+
}
105+
})
106+
}
106107

107-
return Promise.resolve(activationGet(opts))
108-
.then(response => view.formatSessionGet(opts, response))
109-
.catch(err => {
110-
throw err
111-
})
112-
},
113-
{}
114-
)
115-
})
108+
return Promise.resolve(activationGet(opts))
109+
.then(response => view.formatSessionGet(opts, response))
110+
.catch(err => {
111+
throw err
112+
})
113+
},
114+
{}
115+
)
116+
})
117+
)
116118
}

plugins/plugin-openwhisk-editor-extensions/src/lib/cmds/new.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ const checkForConformance = action => {
110110
const err = new Error('Editing of binary actions not yet supported')
111111
err['code'] = 406 // 406: Not Acceptable http status code
112112
throw err
113-
} else if (action.ast) {
113+
} else if (action.ast || action.annotations.find(_ => _.key === 'composerVersion')) {
114114
// try to find the source for this composition
115115
debug('trying to find source for composition')
116116
return compositionPersister.getCode(action)

0 commit comments

Comments
 (0)