From 679866e24e020a57bc155a2d6fffc3f444fd69ae Mon Sep 17 00:00:00 2001 From: Kawika Avilla Date: Fri, 30 Sep 2022 15:56:44 -0700 Subject: [PATCH] [Plugin Helpers] Facilitate version changes (#2398) (#2469) Signed-off-by: Miki --- CHANGELOG.md | 3 + packages/osd-plugin-helpers/README.md | 93 ++++++++--- packages/osd-plugin-helpers/src/cli.ts | 144 +++++++++++++++++- .../src/{build_context.ts => contexts.ts} | 22 +++ .../osd-plugin-helpers/src/tasks/clean.ts | 2 +- .../src/tasks/create_archive.ts | 2 +- .../osd-plugin-helpers/src/tasks/index.ts | 1 + .../osd-plugin-helpers/src/tasks/optimize.ts | 2 +- .../src/tasks/update_versions.ts | 100 ++++++++++++ .../src/tasks/write_public_assets.ts | 2 +- .../src/tasks/write_server_files.ts | 2 +- .../src/tasks/yarn_install.ts | 2 +- 12 files changed, 340 insertions(+), 35 deletions(-) rename packages/osd-plugin-helpers/src/{build_context.ts => contexts.ts} (77%) create mode 100644 packages/osd-plugin-helpers/src/tasks/update_versions.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index affe81b4932..107a97f4b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 📈 Features/Enhancements +* [MD] Support legacy client for data source ([#2204](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2204)) +* [Plugin Helpers] Facilitate version changes ([#2398](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2398)) + ### 🐛 Bug Fixes * [Vis Builder] Fixes auto bounds for timeseries bar chart visualization ([2401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2401)) * [Vis Builder] Fixes visualization shift when editing agg ([2401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2401)) diff --git a/packages/osd-plugin-helpers/README.md b/packages/osd-plugin-helpers/README.md index 3b1dd0c874c..4a16bc9afe2 100644 --- a/packages/osd-plugin-helpers/README.md +++ b/packages/osd-plugin-helpers/README.md @@ -25,10 +25,13 @@ yarn osd bootstrap ## Usage -This simple CLI has a build task that plugin devs can run from to easily package OpenSearch Dashboards plugins. +This CLI has a `build` command that plugin devs can run to easily package OpenSearch Dashboards plugins. It also has a `version` +command which updates a plugin's `version` and `opensearchDashboardsVersion` in the `opensearch_dashboards.json` and the `version`, +`opensearchDashboards.version`, and `opensearchDashboards.templateVersion` in the `package.json` files to match the version of +OpenSearch Dashboards or ones supplied. -Previously you could also use that tool to start and test your plugin. Currently you can run -your plugin along with OpenSearch Dashboards running `yarn start` in the OpenSearch Dashboards repository root folder. Finally to test +Previously you could also use that tool to start and test your plugin. Currently, you can run +your plugin along with OpenSearch Dashboards running `yarn start` in the OpenSearch Dashboards repository root folder. Finally, to test your plugin you should now configure and use your own tools. ```sh @@ -37,37 +40,76 @@ $ plugin-helpers help Usage: plugin-helpers [command] [options] Commands: - build - Copies files from the source into a zip archive that can be distributed for - installation into production OpenSearch Dashboards installs. The archive includes the non- - development npm dependencies and builds itself using raw files in the source - directory so make sure they are clean/up to date. The resulting archive can - be found at: - - build/{plugin.id}-{opensearchDashboardsVersion}.zip - - Options: - --skip-archive Don't create the zip file, just create the build/opensearch-dashboards directory - --opensearch-dashboards-version, -v OpenSearch Dashboards version that the + build + Copies files from the source into a zip archive that can be distributed for installation into production + OpenSearch Dashboards installs. The archive includes the non-development npm dependencies and builds itself using + raw files in the source directory so make sure they are clean/up to date. The resulting archive can be found at: + + build/{plugin.id}-{opensearchDashboardsVersion}.zip + + Options: + --skip-archive Don't create the zip file, just create the build/opensearch-dashboards directory + --opensearch-dashboards-version, -v OpenSearch Dashboards version that the built plugin will target + + version + Without any options, it would display information about the versions found in the manifest file. With options, it + updates the version and opensearchDashboardsVersion in the opensearch_dashboards.json and the version, + opensearchDashboards.version, and opensearchDashboards.templateVersion in the package.json files to the values + provided or syncs them with the version of OpenSearch Dashboards. The versions are expected to start with #.#.# + where # are numbers. + + Options: + --sync Update the versions to match OpenSearch Dashboards' + --plugin-version Update the plugin's version to the one specified + --compatibility-version Update the plugin's compatibility version to the one specified + Global options: - --verbose, -v Log verbosely - --debug Log debug messages (less than verbose) - --quiet Only log errors - --silent Don't log anything - --help Show this message + --verbose, -v Log verbosely + --debug Log debug messages (less than verbose) + --quiet Only log errors + --silent Don't log anything + --help Show this message + +``` + +### Examples + +To produce build artifacts of a plugin in the `build/opensearch-dashboards` directory, without generating a zip archive, and while targeting OpenSearch Dashboards 3.0.0: +``` +yarn plugin-helpers build --skip-archive --opensearch-dashboards-version="3.0.0" +``` + +To synchronize the versions used in a plugin's `opensearch_dashboards.json` and `package.json` files with the version of OpenSearch Dashboards: +``` +yarn plugin-helpers version --sync +``` +If legacy plugin versions are required: +``` +yarn plugin-helpers version --sync legacy +``` + +To update the compatibility version of the plugin in the `opensearch_dashboards.json` and `package.json` files: +``` +yarn plugin-helpers version --compatibility-version="3.0.0" +// or +yarn plugin-helpers version --compatibility-version 3.0.0 +``` +To synchronize the compatibility version of the plugin with the version of OpenSearch Dashboards but set a specific version for the plugin: +``` +yarn plugin-helpers version --sync --plugin-version 1.1.0 ``` ## Versions -The plugins helpers in the OpenSearch Dashboards repo are available for OpenSearch Dashboards 1.0 and greater. Just checkout the branch of OpenSearch Dashboards you want to build against and the plugin helpers should be up to date for that version of OpenSearch Dashboards. +The plugins helpers in the OpenSearch Dashboards repo are available for OpenSearch Dashboards 1.0 and greater. Just checkout the branch of OpenSearch Dashboards you want to build against and the plugin helpers should be up-to-date for that version of OpenSearch Dashboards. ## Configuration -`plugin-helpers` accepts a number of settings, which can be specified at runtime, or included in a `.opensearch_dashboards-plugin-helpers.json` file if you'd like to bundle those settings with your project. +`plugin-helpers` accepts a number of settings for the `build` command which can be specified at runtime or included in a `.opensearch_dashboards-plugin-helpers.json` file if you'd like to bundle those settings with your project. It will also observe a `.opensearch_dashboards-plugin-helpers.dev.json`, much like OpenSearch Dashboards does, which we encourage you to add to your `.gitignore` file and use for local settings that you don't intend to share. These "dev" settings will override any settings in the normal json config. @@ -84,6 +126,13 @@ Setting | Description `skipInstallDependencies` | Don't install dependencies defined in package.json into build output `opensearchDashboardsVersion` | OpenSearch Dashboards version for the build output (added to package.json) +### Settings for `version` + +Setting | Description +------- | ----------- +`sync` | As the default behavior, it uses the version of OpenSearch Dashboards to update the plugin's `opensearch_dashboards.json` and `package.json` files. +`set` | Defines the version to be used in the plugin's `opensearch_dashboards.json` and `package.json` files. + ## TypeScript support Plugin code can be written in [TypeScript](http://www.typescriptlang.org/) if desired. To enable TypeScript support create a `tsconfig.json` file at the root of your plugin that looks something like this: diff --git a/packages/osd-plugin-helpers/src/cli.ts b/packages/osd-plugin-helpers/src/cli.ts index 4c0af8cf725..82bd9f5adbe 100644 --- a/packages/osd-plugin-helpers/src/cli.ts +++ b/packages/osd-plugin-helpers/src/cli.ts @@ -35,10 +35,14 @@ import { RunWithCommands, createFlagError, createFailError } from '@osd/dev-util import { findOpenSearchDashboardsJson } from './find_opensearch_dashboards_json'; import { loadOpenSearchDashboardsPlatformPlugin } from './load_opensearch_dashboards_platform_plugin'; import * as Tasks from './tasks'; -import { BuildContext } from './build_context'; +import { BuildContext, VersionContext } from './contexts'; import { resolveOpenSearchDashboardsVersion } from './resolve_opensearch_dashboards_version'; import { loadConfig } from './config'; +const VERSION_PARAM_MATCH_DASHBOARDS = 'sync'; +const VERSION_PARAM_USE_INPUT_FOR_PLUGIN = 'plugin-version'; +const VERSION_PARAM_USE_INPUT_FOR_COMPATIBILITY = 'compatibility-version'; + export function runCli() { new RunWithCommands({ description: 'Some helper tasks for plugin-authors', @@ -46,11 +50,9 @@ export function runCli() { .command({ name: 'build', description: ` - Copies files from the source into a zip archive that can be distributed for - installation into production OpenSearch Dashboards installs. The archive includes the non- - development npm dependencies and builds itself using raw files in the source - directory so make sure they are clean/up to date. The resulting archive can - be found at: + Copies files from the source into a zip archive that can be distributed for installation into production + OpenSearch Dashboards installs. The archive includes the non-development npm dependencies and builds itself using + raw files in the source directory so make sure they are clean/up to date. The resulting archive can be found at: build/{plugin.id}-{opensearchDashboardsVersion}.zip @@ -62,7 +64,7 @@ export function runCli() { k: 'opensearch-dashboards-version', }, help: ` - --skip-archive Don't create the zip file, just create the build/opensearch-dashboards directory + --skip-archive Don't create the zip file, just create the build/opensearch-dashboards directory --opensearch-dashboards-version, -v OpenSearch version that the `, }, @@ -118,5 +120,133 @@ export function runCli() { } }, }) + .command({ + name: 'version', + description: ` + Without any options, it would display information about the versions found in the manifest file. With options, it + updates the version and opensearchDashboardsVersion in the opensearch_dashboards.json and the version, + opensearchDashboards.version, and opensearchDashboards.templateVersion in the package.json files to the values + provided or syncs them with the version of OpenSearch Dashboards. The versions are expected to start with #.#.# + where # are numbers. + `, + flags: { + string: [VERSION_PARAM_USE_INPUT_FOR_PLUGIN, VERSION_PARAM_USE_INPUT_FOR_COMPATIBILITY], + help: ` + --${VERSION_PARAM_MATCH_DASHBOARDS.padEnd( + 35, + ' ' + )}Update the versions to match OpenSearch Dashboards' + --${VERSION_PARAM_USE_INPUT_FOR_PLUGIN.padEnd( + 35, + ' ' + )}Update the plugin's version to the one specified + --${VERSION_PARAM_USE_INPUT_FOR_COMPATIBILITY.padEnd( + 35, + ' ' + )}Update the plugin's compatibility version to the one specified + `, + allowUnexpected: true, + }, + async run({ log, flags }) { + const pluginDir = await findOpenSearchDashboardsJson(process.cwd()); + if (!pluginDir) { + throw createFailError( + `Unable to find OpenSearch Dashboards Platform plugin in [${process.cwd()}] or any of its parent directories. Has it been migrated properly? Does it have a opensearch_dashboards.json file?` + ); + } + + let dashboardsPackage; + try { + dashboardsPackage = await import(Path.join(process.cwd(), '../../package.json')); + } catch (ex) { + throw createFailError(`Unable to parse the OpenSearch Dashboards' package.json file`); + } + + let pluginPackage; + try { + pluginPackage = await import(Path.join(process.cwd(), 'package.json')); + } catch (ex) { + throw createFailError(`Unable to parse the plugin's package.json file`); + } + + let manifestFile; + try { + manifestFile = await import(Path.join(process.cwd(), 'opensearch_dashboards.json')); + } catch (ex) { + throw createFailError(`Unable to parse the plugin's opensearch_dashboards.json file`); + } + + const dashboardsVersion = dashboardsPackage.version; + const pluginVersion = pluginPackage.version; + const manifestCompatibilityVersion = manifestFile.opensearchDashboardsVersion; + + log.info( + `The plugin is on v${pluginVersion} and requires OpenSearch Dashboards v${manifestCompatibilityVersion}, ${ + manifestCompatibilityVersion === dashboardsVersion ? 'and' : 'but' + } the one found in the directory hierarchy is on v${dashboardsVersion}.` + ); + + let askedToChange = false; + let updatedPluginVersion = pluginVersion; + let updatedCompatibilityVersion = manifestCompatibilityVersion; + + const doSync = flags[VERSION_PARAM_MATCH_DASHBOARDS]; + if (doSync) { + if (!['boolean', 'string'].includes(typeof doSync)) + throw createFlagError(`expected a single --${VERSION_PARAM_MATCH_DASHBOARDS} flag`); + + /* if using legacy versions, the plugin's version has a now-redundant `.0` appended to the semantic + * version number of OpenSearch Dashboards. If OpenSearch Dashboards has a version with a suffix, the + * suffix has to be removed before we append the `.0`. + */ + updatedPluginVersion = + doSync === 'legacy' + ? `${dashboardsVersion.replace(/^(\d+\.\d+\.\d+)(-.*)?$/, '$1')}.0` + : dashboardsVersion; + updatedCompatibilityVersion = `${dashboardsVersion}`; + + askedToChange = true; + } + + const pluginVersionValue = flags[VERSION_PARAM_USE_INPUT_FOR_PLUGIN]; + if (pluginVersionValue) { + if (typeof pluginVersionValue !== 'string') + throw createFlagError(`expected a single --${VERSION_PARAM_USE_INPUT_FOR_PLUGIN} flag`); + if (!/^\d+(\.\d+){2,}(-\S+)?$/.test(pluginVersionValue)) + throw createFlagError( + `expected a valid version starting with #.#.# where # are numbers to follow the --${VERSION_PARAM_USE_INPUT_FOR_PLUGIN} flag` + ); + + updatedPluginVersion = pluginVersionValue; + askedToChange = true; + } + + const compatibilityVersionValue = flags[VERSION_PARAM_USE_INPUT_FOR_COMPATIBILITY]; + if (compatibilityVersionValue) { + if (typeof compatibilityVersionValue !== 'string') + throw createFlagError( + `expected a single --${VERSION_PARAM_USE_INPUT_FOR_COMPATIBILITY} flag` + ); + if (!/^\d+(\.\d+){2,}(-\S+)?$/.test(compatibilityVersionValue)) + throw createFlagError( + `expected a valid version starting with #.#.# where # are numbers to follow the --${VERSION_PARAM_USE_INPUT_FOR_COMPATIBILITY} flag` + ); + + updatedCompatibilityVersion = compatibilityVersionValue; + askedToChange = true; + } + + if (!askedToChange) return; + + const context: VersionContext = { + log, + sourceDir: process.cwd(), + pluginVersion: updatedPluginVersion, + compatibilityVersion: updatedCompatibilityVersion, + }; + + await Tasks.updateVersions(context); + }, + }) .execute(); } diff --git a/packages/osd-plugin-helpers/src/build_context.ts b/packages/osd-plugin-helpers/src/contexts.ts similarity index 77% rename from packages/osd-plugin-helpers/src/build_context.ts rename to packages/osd-plugin-helpers/src/contexts.ts index dd5a837ca5c..1b7420b31cf 100644 --- a/packages/osd-plugin-helpers/src/build_context.ts +++ b/packages/osd-plugin-helpers/src/contexts.ts @@ -41,3 +41,25 @@ export interface BuildContext { buildDir: string; opensearchDashboardsVersion: string; } + +export interface VersionContext { + log: ToolingLog; + sourceDir: string; + pluginVersion?: string; + compatibilityVersion?: string; +} + +interface NestedObject { + [key: string]: NestedObject | string | undefined; +} + +export interface FileUpdateContext { + log: ToolingLog; + file: string; + updates: NestedObject; +} + +export interface ObjectUpdateContext { + original: { [key: string]: any }; + updates: NestedObject; +} diff --git a/packages/osd-plugin-helpers/src/tasks/clean.ts b/packages/osd-plugin-helpers/src/tasks/clean.ts index b8b3e4d0de4..a8bf738d4db 100644 --- a/packages/osd-plugin-helpers/src/tasks/clean.ts +++ b/packages/osd-plugin-helpers/src/tasks/clean.ts @@ -33,7 +33,7 @@ import { promisify } from 'util'; import del from 'del'; -import { BuildContext } from '../build_context'; +import { BuildContext } from '../contexts'; const asyncMkdir = promisify(Fs.mkdir); diff --git a/packages/osd-plugin-helpers/src/tasks/create_archive.ts b/packages/osd-plugin-helpers/src/tasks/create_archive.ts index 7b0e7cb6168..566fc83ff21 100644 --- a/packages/osd-plugin-helpers/src/tasks/create_archive.ts +++ b/packages/osd-plugin-helpers/src/tasks/create_archive.ts @@ -36,7 +36,7 @@ import del from 'del'; import vfs from 'vinyl-fs'; import zip from 'gulp-zip'; -import { BuildContext } from '../build_context'; +import { BuildContext } from '../contexts'; const asyncPipeline = promisify(pipeline); diff --git a/packages/osd-plugin-helpers/src/tasks/index.ts b/packages/osd-plugin-helpers/src/tasks/index.ts index 3a9e6a5c472..c5f0789dc22 100644 --- a/packages/osd-plugin-helpers/src/tasks/index.ts +++ b/packages/osd-plugin-helpers/src/tasks/index.ts @@ -31,6 +31,7 @@ export * from './clean'; export * from './create_archive'; export * from './optimize'; +export * from './update_versions'; export * from './write_public_assets'; export * from './write_server_files'; export * from './yarn_install'; diff --git a/packages/osd-plugin-helpers/src/tasks/optimize.ts b/packages/osd-plugin-helpers/src/tasks/optimize.ts index b66fc79ad4b..97da34cba52 100644 --- a/packages/osd-plugin-helpers/src/tasks/optimize.ts +++ b/packages/osd-plugin-helpers/src/tasks/optimize.ts @@ -35,7 +35,7 @@ import { promisify } from 'util'; import { REPO_ROOT } from '@osd/utils'; import { OptimizerConfig, runOptimizer, logOptimizerState } from '@osd/optimizer'; -import { BuildContext } from '../build_context'; +import { BuildContext } from '../contexts'; const asyncRename = promisify(Fs.rename); diff --git a/packages/osd-plugin-helpers/src/tasks/update_versions.ts b/packages/osd-plugin-helpers/src/tasks/update_versions.ts new file mode 100644 index 00000000000..0acfa12c909 --- /dev/null +++ b/packages/osd-plugin-helpers/src/tasks/update_versions.ts @@ -0,0 +1,100 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// @ts-ignore +import { readFile, writeFile } from 'fs/promises'; +import path from 'path'; +import { createFailError } from '@osd/dev-utils'; +import { FileUpdateContext, ObjectUpdateContext, VersionContext } from '../contexts'; + +export async function updateVersions({ + log, + sourceDir, + pluginVersion, + compatibilityVersion, +}: VersionContext): Promise { + // If any of the versions are falsy, we will skip updating them in updateObject + if (pluginVersion && !/^\d+(\.\d+){2,}/.test(pluginVersion)) + throw createFailError('The plugin version should start with #.#.# where # are numbers'); + + if (compatibilityVersion && !/^\d+(\.\d+){2,}/.test(compatibilityVersion)) + throw createFailError( + `The plugin's compatibility version should start with #.#.# where # are numbers` + ); + + const updateManifestFile = updateFile({ + log, + file: path.join(sourceDir, 'opensearch_dashboards.json'), + updates: { + version: pluginVersion, + opensearchDashboardsVersion: compatibilityVersion, + }, + }); + + const updatePackageJson = updateFile({ + log, + file: path.join(sourceDir, 'package.json'), + updates: { + version: pluginVersion, + opensearchDashboards: { + version: compatibilityVersion, + templateVersion: compatibilityVersion, + }, + }, + }); + + await Promise.all([updateManifestFile, updatePackageJson]); + + return true; +} + +async function updateFile({ log, file, updates }: FileUpdateContext) { + log.info('Updating', file); + + let json; + + try { + json = JSON.parse(await readFile(file, 'utf8')); + } catch (ex) { + log.error(ex); + throw createFailError(`Failed to parse ${file}`); + } + + const context: ObjectUpdateContext = { + original: json, + updates, + }; + updateObject(context); + + try { + await writeFile(file, JSON.stringify(json, null, 2), 'utf8'); + } catch (ex) { + log.error(ex); + throw createFailError(`Failed to update ${file}`); + } + + log.success(`Updated`, file); +} + +// Copies values in `updates` onto `obj` only if the keys exist +function updateObject({ original, updates }: ObjectUpdateContext) { + for (const key in updates) { + if (!updates[key]) continue; + + // If `key` is not found in `original`, just skip it + if (key in original) { + // If both are objects, merge them + if (updates[key] === 'object' && typeof original[key] === 'object') { + updateObject({ + original: original[key], + updates: updates[key], + } as ObjectUpdateContext); + // If the updated value is falsy, skip it + } else if (updates[key]) { + original[key] = updates[key]; + } + } + } +} diff --git a/packages/osd-plugin-helpers/src/tasks/write_public_assets.ts b/packages/osd-plugin-helpers/src/tasks/write_public_assets.ts index 6bddce7e474..5bd4b7f98b3 100644 --- a/packages/osd-plugin-helpers/src/tasks/write_public_assets.ts +++ b/packages/osd-plugin-helpers/src/tasks/write_public_assets.ts @@ -33,7 +33,7 @@ import { promisify } from 'util'; import vfs from 'vinyl-fs'; -import { BuildContext } from '../build_context'; +import { BuildContext } from '../contexts'; const asyncPipeline = promisify(pipeline); diff --git a/packages/osd-plugin-helpers/src/tasks/write_server_files.ts b/packages/osd-plugin-helpers/src/tasks/write_server_files.ts index 693bd0148c5..45db624f3d6 100644 --- a/packages/osd-plugin-helpers/src/tasks/write_server_files.ts +++ b/packages/osd-plugin-helpers/src/tasks/write_server_files.ts @@ -34,7 +34,7 @@ import { promisify } from 'util'; import vfs from 'vinyl-fs'; import { transformFileWithBabel, transformFileStream } from '@osd/dev-utils'; -import { BuildContext } from '../build_context'; +import { BuildContext } from '../contexts'; const asyncPipeline = promisify(pipeline); diff --git a/packages/osd-plugin-helpers/src/tasks/yarn_install.ts b/packages/osd-plugin-helpers/src/tasks/yarn_install.ts index d8f595a96a8..29879afe5d6 100644 --- a/packages/osd-plugin-helpers/src/tasks/yarn_install.ts +++ b/packages/osd-plugin-helpers/src/tasks/yarn_install.ts @@ -33,7 +33,7 @@ import Path from 'path'; import execa from 'execa'; -import { BuildContext } from '../build_context'; +import { BuildContext } from '../contexts'; const winVersion = (path: string) => (process.platform === 'win32' ? `${path}.cmd` : path);