Skip to content

Commit

Permalink
refactor the GitHub plugin
Browse files Browse the repository at this point in the history
- rename package to indicate more features are coming
- make configuration and flags more consistent
- enable by default (and add mechanism to disable default plugins)
- fix docs
  • Loading branch information
Roy Razon committed Nov 26, 2023
1 parent 00e10e9 commit e4ed7c3
Show file tree
Hide file tree
Showing 38 changed files with 630 additions and 405 deletions.
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ Visit The full documentation here: https://preevy.dev/
- [Compose files](#compose-files)
- [`x-preevy`: Preevy-specific configuration in the Compose file(s)](#x-preevy-preevy-specific-configuration-in-the-compose-files)
- [Plugins](#plugins-1)
- [Default plugins](#default-plugins)
- [Enabling or disabling plugins](#enabling-or-disabling-plugins)
- [Docs and support](#docs-and-support)
- [Telemetry](#telemetry)
<!--lint enable double-link-->
Expand Down Expand Up @@ -282,23 +284,41 @@ See [Plugins](#plugins) below.

Plugins are a way to extend Preevy's functionality via externally-published NPM packages.

A plugin can execute code in response to events. It can also defined new commands, and add flags to existing commands to customize their behavior.
A plugin can add hooks which execute code in response to events. It can also define new commands, and add flags to existing commands to customize their behavior.

### Default plugins

The [GitHub integration plugin](packages/plugin-github) packaged as `@preevy/plugin-github` is bundled with Preevy and enabled by default.

### Enabling or disabling plugins

#### From the Docker Compose file

<!--lint disable double-link-->
Plugins are specified in the [Preevy configuration](#preevy-specific-configuration). Add a `plugins` section to the `x-preevy` top-level element:
Plugins can be configured in the [Preevy configuration](#x-preevy-preevy-specific-configuration-in-the-compose-files) section of your Compose file. Add a `plugins` section to the `x-preevy` top-level element:
<!--lint enable double-link-->

```yaml
services:
...
x-preevy:
plugins:
- module: '@preevy/plugin-github-pr-link'
- module: '@preevy/plugin-github'
disabled: false # optional, set to true to disable plugin
# ...additional plugin-specific configuration goes here
```

See the [included GitHub PR Link Plugin](packages/plugin-github-pr-link) for an example.
See the [included GitHub integration plugin](packages/plugin-github/README.md) for a detailed example.

#### From the environment

Plugins can be enabled or disabled by setting the `PREEVY_ENABLE_PLUGINS` and `PREEVY_DISABLE_PLUGINS` environment variables to a comma-separated list of packages.

Example: To disable the default GitHub integration plugin, set `PREEVY_DISABLE_PLUGINS=@preevy/plugin-github`.

#### From the CLI flags

Specify the global `--enable-plugin=<module>` and `--disable-plugin=<module>` flags to enable or disable plugins per command execution. CLI flags take priority over the Docker Compose and environment configuration.

## Docs and support

Expand Down
3 changes: 2 additions & 1 deletion packages/cli-common/src/commands/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from '@preevy/core'
import { asyncReduce } from 'iter-tools-es'
import { commandLogger } from '../lib/log'
import { composeFlags } from '../lib/common-flags'
import { composeFlags, pluginFlags } from '../lib/common-flags'

// eslint-disable-next-line no-use-before-define
export type Flags<T extends typeof Command> = Interfaces.InferredFlags<typeof BaseCommand['baseFlags'] & T['flags']>
Expand All @@ -30,6 +30,7 @@ abstract class BaseCommand<T extends typeof Command=typeof Command> extends Comm
],
}),
...composeFlags,
...pluginFlags,
}

protected flags!: Flags<T>
Expand Down
32 changes: 27 additions & 5 deletions packages/cli-common/src/hooks/init/load-plugins.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Hook as OclifHook, Command, Flags } from '@oclif/core'
import { Parser } from '@oclif/core/lib/parser/parse'
import { Config, Topic } from '@oclif/core/lib/interfaces'
import { BooleanFlag, Config, Topic } from '@oclif/core/lib/interfaces'
import { localComposeClient, ComposeModel, resolveComposeFiles, withSpinner, NoComposeFilesError } from '@preevy/core'
import { composeFlags } from '../../lib/common-flags'
import { cloneDeep } from 'lodash'
import { composeFlags, pluginFlags } from '../../lib/common-flags'
import { addPluginFlags, loadPlugins, hooksFromPlugins, addPluginCommands } from '../../lib/plugins'

type InternalConfig = Config & {
Expand All @@ -12,18 +13,34 @@ type InternalConfig = Config & {

const excludedCommandIds = ['init', 'version', /^profile:/, 'ls']

export const initHook: OclifHook<'init'> = async function hook({ config, id, argv }) {
const flagDefs = cloneDeep({
...composeFlags,
...pluginFlags,
json: Flags.boolean(),
}) as typeof composeFlags & typeof pluginFlags & { json: BooleanFlag<boolean> }

flagDefs['enable-plugin'].default = undefined

export const initHook: OclifHook<'init'> = async function hook(args) {
const { config, id, argv } = args
// workaround oclif bug when executing `preevy --flag1 --flag2` with no command
if (id?.startsWith('-')) {
await initHook.call(this, ({ ...args, id: undefined, argv: [id].concat(argv) }))
return
}

if (id && excludedCommandIds.some(excluded => (typeof excluded === 'string' ? excluded === id : excluded.test(id)))) {
return
}

const { flags, raw } = await new Parser({
flags: { ...composeFlags, json: Flags.boolean() },
flags: flagDefs,
strict: false,
args: {},
context: undefined,
argv,
} as const).parse()

const composeFiles = await resolveComposeFiles({
userSpecifiedFiles: flags.file,
userSpecifiedSystemFiles: flags['system-compose-file'],
Expand All @@ -45,7 +62,12 @@ export const initHook: OclifHook<'init'> = async function hook({ config, id, arg

const userModel = userModelOrError instanceof Error ? {} as ComposeModel : userModelOrError
const preevyConfig = userModel['x-preevy'] ?? {}
const loadedPlugins = await loadPlugins(preevyConfig, { userModel, oclifConfig: config, argv })
const loadedPlugins = await loadPlugins(
config,
flags,
preevyConfig.plugins,
{ preevyConfig, userModel, oclifConfig: config, argv },
)
const commands = addPluginFlags(addPluginCommands(config.commands, loadedPlugins), loadedPlugins)
const topics = [...config.topics, ...loadedPlugins.flatMap(({ initResults }) => initResults.topics ?? [])];

Expand Down
4 changes: 2 additions & 2 deletions packages/cli-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export * from './lib/plugins/model'
export * as text from './lib/text'
export { HookName, HookFunc, HooksListeners, Hooks } from './lib/hooks'
export { PluginContext, PluginInitContext } from './lib/plugins/context'
export { composeFlags, envIdFlags, tunnelServerFlags, urlFlags } from './lib/common-flags'
export { formatFlagsToArgs } from './lib/flags'
export { composeFlags, pluginFlags, envIdFlags, tunnelServerFlags, urlFlags } from './lib/common-flags'
export { formatFlagsToArgs, parseFlags, ParsedFlags } from './lib/flags'
export { initHook } from './hooks/init/load-plugins'
export { default as BaseCommand } from './commands/base-command'
19 changes: 19 additions & 0 deletions packages/cli-common/src/lib/common-flags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Flags } from '@oclif/core'
import { DEFAULT_PLUGINS } from './plugins/default-plugins'

const projectFlag = {
project: Flags.string({
Expand Down Expand Up @@ -31,6 +32,24 @@ export const composeFlags = {
...projectFlag,
} as const

export const pluginFlags = {
'enable-plugin': Flags.string({
description: 'Enable plugin with specified package name',
multiple: true,
delimiter: ',',
singleValue: true,
helpGroup: 'GLOBAL',
default: DEFAULT_PLUGINS,
}),
'disable-plugin': Flags.string({
description: 'Disable plugin with specified package name',
multiple: true,
delimiter: ',',
singleValue: true,
helpGroup: 'GLOBAL',
}),
} as const

export const envIdFlags = {
id: Flags.string({
description: 'Environment id - affects created URLs. If not specified, will try to detect automatically',
Expand Down
11 changes: 11 additions & 0 deletions packages/cli-common/src/lib/flags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Flag } from '@oclif/core/lib/interfaces'
import { Parser } from '@oclif/core/lib/parser/parse'

type FlagSpec<T> =Pick<Flag<T>, 'type' | 'default'>

Expand All @@ -23,3 +24,13 @@ export function formatFlagsToArgs(flags: Record<string, unknown>, spec: Record<s
return [`--${prefix}${key}`, `${value}`]
})
}

export const parseFlags = async <T extends {}>(def: T, argv: string[]) => (await new Parser({
flags: def,
strict: false,
args: {},
context: undefined,
argv,
}).parse()).flags

export type ParsedFlags<T extends {}> = Omit<Awaited<ReturnType<typeof parseFlags<T>>>, 'json'>
3 changes: 3 additions & 0 deletions packages/cli-common/src/lib/plugins/default-plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const DEFAULT_PLUGINS = [
'@preevy/plugin-github',
]
1 change: 1 addition & 0 deletions packages/cli-common/src/lib/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { loadPlugins } from './load'
export { addPluginFlags } from './flags'
export { addPluginCommands } from './commands'
export { hooksFromPlugins } from './hooks'
export { DEFAULT_PLUGINS } from './default-plugins'
32 changes: 27 additions & 5 deletions packages/cli-common/src/lib/plugins/load.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
import { Config } from '@oclif/core'
import { InferredFlags } from '@oclif/core/lib/interfaces'
import { config as coreConfig } from '@preevy/core'
import { InitResults, PluginModule } from './model'
import { PluginInitContext } from './context'
import PreevyPluginConfig = coreConfig.PreevyPluginConfig
import PreevyConfig = coreConfig.PreevyConfig
import { pluginFlags } from '../common-flags'
import { DEFAULT_PLUGINS } from './default-plugins'

export type LoadedPlugin = {
initResults: InitResults
config: PreevyPluginConfig
}

const mergePluginDefs = (
envConfig: Pick<Config, 'scopedEnvVar' | 'scopedEnvVarKey'>,
flags: InferredFlags<typeof pluginFlags>,
pluginConfig: PreevyPluginConfig[] | undefined,
) => {
const pluginDefinitions = DEFAULT_PLUGINS.map(m => ({ module: m }))
.concat(pluginConfig ?? [])
.concat(envConfig.scopedEnvVar('ENABLE_PLUGINS')?.split(',')?.map(m => ({ module: m })) ?? [])
.concat(envConfig.scopedEnvVar('DISABLE_PLUGINS')?.split(',')?.map(m => ({ module: m, disabled: true })) ?? [])
.concat(flags['enable-plugin']?.map(m => ({ module: m })) ?? [])
.concat(flags['disable-plugin']?.map(m => ({ module: m, disabled: true })) ?? [])
.map(p => [p.module, p] as [string, PreevyPluginConfig])
.reduce((acc, [k, v]) => ({ ...acc, ...{ [k]: v } }), {} as Record<string, PreevyPluginConfig>)

return Object.values(pluginDefinitions).filter(({ disabled }) => !disabled)
}

export const loadPlugins = async (
preevyConfig: Pick<PreevyConfig, 'plugins'>,
initArgs: Omit<PluginInitContext, 'preevyConfig' | 'pluginConfig'>,
envConfig: Pick<Config, 'scopedEnvVar' | 'scopedEnvVarKey'>,
flags: InferredFlags<typeof pluginFlags>,
pluginConfig: PreevyPluginConfig[] | undefined,
initArgs: Omit<PluginInitContext, 'pluginConfig'>,
): Promise<LoadedPlugin[]> => {
const pluginDefinitions = (preevyConfig.plugins ?? []).filter(p => !p.disabled)
const pluginDefinitions = mergePluginDefs(envConfig, flags, pluginConfig)

const plugins = await Promise.all(pluginDefinitions.map(
async p => ({ plugin: (await import(p.module) as PluginModule).preevyPlugin, config: p }),
))

return await Promise.all(
plugins.map(async p => ({
initResults: await p.plugin.init({ ...initArgs, preevyConfig, pluginConfig: p.config }),
initResults: await p.plugin.init({ ...initArgs, pluginConfig: p.config }),
config: p.config,
})),
)
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@preevy/driver-gce": "0.0.56",
"@preevy/driver-kube-pod": "0.0.56",
"@preevy/driver-lightsail": "0.0.56",
"@preevy/plugin-github-pr-link": "0.0.56",
"@preevy/plugin-github": "0.0.56",
"inquirer": "^8.0.0",
"inquirer-autocomplete-prompt": "^2.0.0",
"iter-tools-es": "^7.5.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export const writeUrlsToFile = async (
) => {
if (!outputUrlsTo) return
const contents = /\.ya?ml$/.test(outputUrlsTo) ? yaml.stringify(urls) : JSON.stringify(urls)
log.info(`Writing URLs to file ${text.code(outputUrlsTo)}`)
await fs.promises.writeFile(outputUrlsTo, contents, { encoding: 'utf8' })
log.info(`URLs written to file ${text.code(outputUrlsTo)}`)
}

export const printUrls = (
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
{ "path": "../driver-gce" },
{ "path": "../driver-azure" },
{ "path": "../driver-kube-pod" },
{ "path": "../plugin-github-pr-link" },
{ "path": "../plugin-github" },
]
}
2 changes: 2 additions & 0 deletions packages/core/src/ci-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export const ciProviders = {

export const detectCiProvider = (): CiProvider | undefined => Object.values(ciProviders)
.find(p => p.currentlyRunningInProvider())

export { CiProvider } from './base'
2 changes: 2 additions & 0 deletions packages/core/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export function gitContext(cwd: string = process.cwd()) {
remoteTrackingBranchUrl,
}
}

export type GitContext = ReturnType<typeof gitContext>
4 changes: 2 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ export { TunnelOpts } from './ssh'
export { Spinner } from './spinner'
export { withClosable } from './closable'
export { generateBasicAuthCredentials as getUserCredentials, jwtGenerator, jwkThumbprint, jwkThumbprintUri, parseKey } from './credentials'
export { ciProviders, detectCiProvider } from './ci-providers'
export { ciProviders, detectCiProvider, CiProvider } from './ci-providers'
export { paginationIterator } from './pagination'
export { ensureDefined, extractDefined, HasRequired } from './nulls'
export { pSeries } from './p-series'
export { gitContext } from './git'
export { gitContext, GitContext } from './git'
export * as config from './config'
export { login, getTokensFromLocalFs as getLivecycleTokensFromLocalFs, TokenExpiredError } from './login'
86 changes: 0 additions & 86 deletions packages/plugin-github-pr-link/README.md

This file was deleted.

Loading

0 comments on commit e4ed7c3

Please sign in to comment.