From 030230045dc556e8a5eea07f441e022c10091d90 Mon Sep 17 00:00:00 2001 From: Miki Date: Tue, 20 Sep 2022 16:14:11 -0700 Subject: [PATCH] Extends plugin-helpers to be used for automating version changes Signed-off-by: Miki --- packages/osd-plugin-helpers/README.md | 59 +++++++----- packages/osd-plugin-helpers/src/cli.ts | 62 +++++++++++- .../src/{build_context.ts => contexts.ts} | 21 ++++ .../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 | 95 +++++++++++++++++++ .../src/tasks/write_public_assets.ts | 2 +- .../src/tasks/write_server_files.ts | 2 +- .../src/tasks/yarn_install.ts | 2 +- 11 files changed, 220 insertions(+), 30 deletions(-) rename packages/osd-plugin-helpers/src/{build_context.ts => contexts.ts} (79%) create mode 100644 packages/osd-plugin-helpers/src/tasks/update_versions.ts diff --git a/packages/osd-plugin-helpers/README.md b/packages/osd-plugin-helpers/README.md index 3b1dd0c874cc..1fe16b6bb4d9 100644 --- a/packages/osd-plugin-helpers/README.md +++ b/packages/osd-plugin-helpers/README.md @@ -25,10 +25,11 @@ 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. Called with the `version` +command, a plugin's manifest and package files can be updated to match the version of OpenSearch Dashboards or one 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 +38,44 @@ $ 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 + 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 + + version + Updates the version to a provided parameter, or syncs it with the version of OpenSearch Dashboards - 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 - + Options: + --sync Update the versions to match Dashboards' (default) + --set Update the version to a specific value + 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 ``` ## 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 +92,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 manifest and package files. +`set` | Defines the version to be used in the manifest and package 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 4c0af8cf725a..0284f2a4cb78 100644 --- a/packages/osd-plugin-helpers/src/cli.ts +++ b/packages/osd-plugin-helpers/src/cli.ts @@ -35,10 +35,13 @@ 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_MATCH_INPUT = 'set'; + export function runCli() { new RunWithCommands({ description: 'Some helper tasks for plugin-authors', @@ -62,7 +65,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 +121,60 @@ export function runCli() { } }, }) + .command({ + name: 'version', + description: ` + Updates the version to a provided parameter, or syncs it with the version of OpenSearch Dashboards + `, + flags: { + boolean: ['sync'], + string: ['set'], + help: ` + --${VERSION_PARAM_MATCH_DASHBOARDS} Update the versions to match Dashboards' (default) + --${VERSION_PARAM_MATCH_INPUT} Update the version to a specific value + `, + }, + 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 versionValue = flags[VERSION_PARAM_MATCH_INPUT]; + if (versionValue !== undefined) { + if (typeof versionValue !== 'string') + throw createFlagError(`expected a single --${VERSION_PARAM_MATCH_INPUT} flag`); + if (versionValue && !/^\d+\.\d+\.\d+(-\S+)?$/.test(versionValue)) + throw createFlagError( + `expected a valid version following the --${VERSION_PARAM_MATCH_INPUT} flag` + ); + } + + const doSync = flags[VERSION_PARAM_MATCH_DASHBOARDS]; + if (doSync !== undefined && typeof doSync !== 'boolean') { + throw createFlagError(`expected a single --${VERSION_PARAM_MATCH_DASHBOARDS} flag`); + } + + if (!versionValue || doSync) { + const dashBoardsPackage = await import(Path.join(process.cwd(), '../../package.json')); + versionValue = dashBoardsPackage?.version; + + if (typeof versionValue !== 'string' || !/^\d+\.\d+\.\d+(-\S+)?$/.test(versionValue)) + throw createFailError( + `Failed to extract version from Dashboards: ${dashBoardsPackage?.version}` + ); + } + + const context: VersionContext = { + log, + sourceDir: process.cwd(), + newVersion: versionValue, + }; + + 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 79% rename from packages/osd-plugin-helpers/src/build_context.ts rename to packages/osd-plugin-helpers/src/contexts.ts index dd5a837ca5ca..fe39abf9a1cb 100644 --- a/packages/osd-plugin-helpers/src/build_context.ts +++ b/packages/osd-plugin-helpers/src/contexts.ts @@ -41,3 +41,24 @@ export interface BuildContext { buildDir: string; opensearchDashboardsVersion: string; } + +export interface VersionContext { + log: ToolingLog; + sourceDir: string; + newVersion: string; +} + +interface NestedObject { + [key: string]: NestedObject | string; +} + +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 b8b3e4d0de42..a8bf738d4db5 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 7b0e7cb61687..566fc83ff212 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 3a9e6a5c4725..c5f0789dc22f 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 b66fc79ad4b1..97da34cba52d 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 000000000000..21ec559a1771 --- /dev/null +++ b/packages/osd-plugin-helpers/src/tasks/update_versions.ts @@ -0,0 +1,95 @@ +/* + * 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, + newVersion, +}: VersionContext): Promise { + if (!newVersion) throw createFailError('Missing new version value'); + if (!/^\d+\.\d+\.\d+/.test(newVersion)) + throw createFailError('The new version is not a valid semantic version'); + + const cleanVersion = newVersion.replace(/^(\d+\.\d+\.\d+)(-.*)?$/, '$1'); + + const updateManifestFile = updateFile({ + log, + file: path.join(sourceDir, 'opensearch_dashboards.json'), + updates: { + version: `${cleanVersion}.0`, + opensearchDashboardsVersion: cleanVersion, + }, + }); + + const updatePackageJson = updateFile({ + log, + file: path.join(sourceDir, 'package.json'), + updates: { + version: `${cleanVersion}.0`, + opensearchDashboards: { + version: cleanVersion, + templateVersion: cleanVersion, + }, + }, + }); + + 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); + } else { + 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 6bddce7e4748..5bd4b7f98b3a 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 693bd0148c52..45db624f3d67 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 d8f595a96a85..29879afe5d69 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);