UNPKG

@zowe/imperative

Version:
419 lines 19.9 kB
"use strict"; /* * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. * */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigSchema = void 0; const path = require("path"); const lodash = require("lodash"); const TextUtils_1 = require("../../utilities/src/TextUtils"); const ImperativeConfig_1 = require("../../utilities/src/ImperativeConfig"); const Logger_1 = require("../../logger/src/Logger"); const Config_1 = require("./Config"); const ImperativeError_1 = require("../../error/src/ImperativeError"); class ConfigSchema { /** * Transform an Imperative profile schema to a JSON schema. Removes any * non-JSON-schema properties and translates anything useful. * @param schema The Imperative profile schema * @returns JSON schema for profile properties */ static generateSchema(schema) { const properties = {}; const secureProps = []; for (const [k, v] of Object.entries(schema.properties || {})) { properties[k] = { type: v.type }; const cmdProp = v; if (cmdProp.optionDefinition != null) { properties[k].description = cmdProp.optionDefinition.description; if (cmdProp.optionDefinition.defaultValue != null) { properties[k].default = cmdProp.optionDefinition.defaultValue; } if (cmdProp.optionDefinition.allowableValues != null) { properties[k].enum = cmdProp.optionDefinition.allowableValues.values; } } if (v.secure) { secureProps.push(k); } } const propertiesSchema = { type: schema.type, title: schema.title, description: schema.description, properties }; if (schema.required) { propertiesSchema.required = schema.required; } const secureSchema = { items: { enum: secureProps } }; if (secureProps.length > 0) { return { properties: propertiesSchema, secure: secureSchema }; } else { return { properties: propertiesSchema }; } } /** * Transform a JSON schema to an Imperative profile schema. * @param schema The JSON schema for profile properties * @returns Imperative profile schema * @internal */ static parseSchema(schema) { var _a, _b, _c; const properties = {}; for (const [k, v] of Object.entries((schema.properties.properties || {}))) { properties[k] = { type: v.type }; // Backward compatibility for schema versions <0.4 (prefixItems -> items) if ((_c = (((_a = schema.secure) === null || _a === void 0 ? void 0 : _a.items) || ((_b = schema.secure) === null || _b === void 0 ? void 0 : _b.prefixItems))) === null || _c === void 0 ? void 0 : _c.enum.includes(k)) { properties[k].secure = true; } if (v.description != null || v.default != null || v.enum != null) { properties[k].optionDefinition = { name: k, type: v.type, description: v.description, defaultValue: v.default, allowableValues: v.enum ? { values: v.enum } : undefined }; } } return { title: schema.properties.title, description: schema.properties.description, type: schema.properties.type, properties, required: schema.properties.required }; } /** * HELPER function for updating the active layer's schema files * This operation is divided in 2 steps: * 1. Update the schema file corresponding to the active layer * 2. Update the opposite (user/non-user) layer if it exists * * @param opts The various properties needed to accomplish a recursive UpdateSchema operation * @param forceSetSchema Indicates if we should force the creation of the schema file even if the config doesn't exist (e.g. config init) * @param checkContrastingLayer Indicates if we should check for the opposite (user/non-user) layer * @returns Object containing the updated schema paths */ static _updateSchemaActive(opts, forceSetSchema = false, checkContrastingLayer = true) { let updatedPaths = opts.updatedPaths; const layer = opts.config.layerActive(); if (opts.config.layerExists(path.dirname(layer.path), layer.user) || forceSetSchema) { Logger_1.Logger.getAppLogger().debug(`Updating "${layer.path}" with: \n` + TextUtils_1.TextUtils.prettyJson(TextUtils_1.TextUtils.explainObject(opts.updateOptions.schema, ConfigSchema.explainSchemaSummary, false), null, false)); opts.config.setSchema(opts.updateOptions.schema); // Get the schema information to gather a list of updated paths const schemaInfo = opts.config.getSchemaInfo(); updatedPaths = { [layer.path]: { schema: schemaInfo.original, updated: schemaInfo.local } }; } if (opts.config.layerExists(path.dirname(layer.path), !layer.user) && checkContrastingLayer) { opts.config.api.layers.activate(!layer.user, layer.global, path.dirname(layer.path)); updatedPaths = Object.assign(Object.assign({}, updatedPaths), this._updateSchemaActive(opts, forceSetSchema, false)); // Back to previous layer opts.config.api.layers.activate(layer.user, layer.global, path.dirname(layer.path)); } return updatedPaths; } /** * HELPER function for updating global schema files * This operation is divided in 2 steps: * 1. Activate the global layer * 2. Call the Active helper * * @param opts The various properties needed to accomplish a recursive UpdateSchema operation * @returns Object containing the updated schema paths */ static _updateSchemaGlobal(opts) { let updatedPaths = opts.updatedPaths; // Activate the Global configuration before updating it opts.config.api.layers.activate(true, true); updatedPaths = Object.assign(Object.assign({}, updatedPaths), this._updateSchemaActive(opts)); // Back to initial layer opts.config.api.layers.activate(opts.layer.user, opts.layer.global, path.dirname(opts.layer.path)); return updatedPaths; } /** * HELPER function for recursively updating schema files * This operation is divided in 3 steps: * 1. Traverse UP the directory structure while updating the corresponding schema files * 2. Update both (User and Non-User) Global configuration's schema files * 3. Traverse DOWN the directory structure based on the depth specified * * @param opts The various properties needed to accomplish a recursive UpdateSchema operation * @returns Object containing the updated schema paths */ static _updateSchemaAll(opts) { let updatedPaths = opts.updatedPaths; // Loop through layers starting at the initial one let nextSchemaLocation = opts.layer.path; //___________________________________________________________________________________ // Traverse UP while (nextSchemaLocation != null) { opts.config.api.layers.activate(true, false, path.dirname(nextSchemaLocation)); const currentLayer = opts.config.api.layers.get(); // Update the current layer updatedPaths = Object.assign(Object.assign({}, updatedPaths), this._updateSchemaActive(opts)); // Move on to the next directory up the tree nextSchemaLocation = Config_1.Config.search(opts.config.schemaName, { startDir: path.join(path.dirname(currentLayer.path), "..") }); } //___________________________________________________________________________________ // Update Global Layers if (!opts.layer.global) { // Do not update the global layer if that's where we started from updatedPaths = Object.assign(Object.assign({}, updatedPaths), this._updateSchemaGlobal(opts)); } //___________________________________________________________________________________ // Traverse DOWN if (opts.updateOptions.depth > 0) { // Look for <APP>.schema.json const fg = require("fast-glob"); // The `cwd` does not participate in Fast-glob's depth calculation, hence the + 1 const matches = fg.sync(`**/${opts.config.schemaName}`, { dot: true, onlyFiles: true, deep: opts.updateOptions.depth + 1 }); const globalProjConfig = opts.config.findLayer(false, true); const globalUserConfig = opts.config.findLayer(true, true); // Loop through all matches of <APP>.schema.json matches.forEach(schemaLoc => { // Check if a layer/config exists in the directory where we found the <APP>.schema.json if (opts.config.layerExists(path.dirname(schemaLoc))) { // Activate the layer before updating it opts.config.api.layers.activate(false, false, path.dirname(schemaLoc)); const layer = opts.config.layerActive(); // NOTE: Configs are assumed to be always local (because of path.resolve(layer.path)), // if we want to support Config URLs here, we need to call the config import APIs if (path.resolve(layer.path) !== globalProjConfig.path && path.resolve(layer.path) !== globalUserConfig.path) { updatedPaths = Object.assign(Object.assign({}, updatedPaths), this._updateSchemaActive(opts)); } } }); } // Back to initial layer opts.config.api.layers.activate(opts.layer.user, opts.layer.global, path.dirname(opts.layer.path)); return updatedPaths; } /** * Dynamically builds the config schema for this CLI. * @param profiles The profiles supported by this CLI * @returns JSON schema for all supported profile types */ static buildSchema(profiles) { const andEntries = []; const defaultProperties = {}; profiles.forEach((profile) => { andEntries.push({ if: { properties: { type: { const: profile.type } } }, then: { properties: this.generateSchema(profile.schema) } }); defaultProperties[profile.type] = { description: `Default ${profile.type} profile`, type: "string" }; }); return { $schema: ConfigSchema.JSON_SCHEMA, $version: ConfigSchema.SCHEMA_VERSION, type: "object", description: "Zowe configuration", properties: { profiles: { type: "object", description: "Mapping of profile names to profile configurations", patternProperties: { "^\\S*$": { type: "object", description: "Profile configuration object", properties: { type: { description: "Profile type", type: "string", enum: Object.keys(defaultProperties) }, properties: { description: "Profile properties object", type: "object" }, profiles: { description: "Optional subprofile configurations", type: "object", $ref: "#/properties/profiles" }, secure: { description: "Secure property names", type: "array", items: { type: "string" }, uniqueItems: true } }, allOf: [ { if: { properties: { type: false } }, then: { properties: { properties: { title: "Missing profile type" } } } }, ...andEntries ] } } }, defaults: { type: "object", description: "Mapping of profile types to default profile names", properties: defaultProperties }, autoStore: { type: "boolean", description: "If true, values you enter when prompted are stored for future use" }, // plugins: { // description: "CLI plug-in names to load from node_modules (experimental)", // type: "array", // items: { type: "string" }, // uniqueItems: true // } } }; } /** * Loads Imperative profile schema objects from a schema JSON file. * @param schema The schema JSON for config */ static loadSchema(schema) { const patternName = Object.keys(schema.properties.profiles.patternProperties)[0]; const profileSchemas = []; for (const obj of schema.properties.profiles.patternProperties[patternName].allOf) { if (obj.if.properties.type) { profileSchemas.push({ type: obj.if.properties.type.const, schema: this.parseSchema(obj.then.properties) }); } } return profileSchemas; } /** * Updates Imperative Config Schema objects from a schema JSON file. * @param options The options object * @param options.layer The layer in which we should update the schema file(s). Defaults to the active layer. * @param options.schema The optional schema object to use. If not provided, we build the schema object based on loadedConfig.profiles * @returns List of updated paths with the new/loaded or given schema */ static updateSchema(options) { var _a; // Handle default values const opts = Object.assign({ layer: "active", depth: 0 }, options !== null && options !== void 0 ? options : {}); // Build schema from loaded config if needed opts.schema = (_a = opts.schema) !== null && _a !== void 0 ? _a : ConfigSchema.buildSchema(ImperativeConfig_1.ImperativeConfig.instance.loadedConfig.profiles); const config = ImperativeConfig_1.ImperativeConfig.instance.config; const layer = config.api.layers.get(); let updatedPaths = {}; const _updateSchemaOptions = { config, layer, updatedPaths, updateOptions: opts }; // Operate based on the given layer switch (opts.layer) { case "active": { // Call the _updateSchemaActive helper function updatedPaths = Object.assign(Object.assign({}, updatedPaths), this._updateSchemaActive(_updateSchemaOptions, typeof options === "undefined")); break; } case "global": { // Call the _updateSchemaGlobal helper function updatedPaths = Object.assign(Object.assign({}, updatedPaths), this._updateSchemaGlobal(_updateSchemaOptions)); break; } case "all": { // Call the _updateSchemaAll helper function updatedPaths = Object.assign(Object.assign({}, updatedPaths), this._updateSchemaAll(_updateSchemaOptions)); break; } default: { throw new ImperativeError_1.ImperativeError({ msg: "Unrecognized layer parameter for ConfigSchema.updateSchemas" }); } } return updatedPaths; } /** * Find the type of a property based on schema info. * @param path Path to JSON property in config JSON * @param config Team config properties * @param schema Config schema definition. Defaults to profile schemas defined in Imperative config. */ static findPropertyType(path, config, schema) { var _a; if (!path.includes(".properties.")) { return; } const pathSegments = path.split("."); const propertyName = pathSegments.pop(); const profilePath = pathSegments.slice(0, -1).join("."); let profileType = lodash.get(config, `${profilePath}.type`); if (profileType == null || profileType === "" || Array.isArray(profileType)) { Logger_1.Logger.getAppLogger().warn(`Profile type not found or supported for path: ${path}`); Logger_1.Logger.getAppLogger().debug(`Using default profile type 'base' to determine property type`); profileType = "base"; // Assuming base profile for now } const profileSchemas = schema ? this.loadSchema(schema) : ImperativeConfig_1.ImperativeConfig.instance.loadedConfig.profiles; const profileSchema = (_a = profileSchemas.find(p => p.type === profileType)) === null || _a === void 0 ? void 0 : _a.schema; if (profileSchema != null && profileSchema.properties[propertyName] != null) { const property = profileSchema.properties[propertyName]; if (property != null) { const propertyType = profileSchema.properties[propertyName].type; Logger_1.Logger.getAppLogger().debug(`Found property type for ${propertyName} in profile ${profileType}: ${propertyType}`); // TODO How to handle profile property with multiple types return Array.isArray(propertyType) ? propertyType[0] : propertyType; } } } } exports.ConfigSchema = ConfigSchema; /** * JSON schema URI stored in $schema property of the schema * @readonly * @memberof ConfigSchema */ ConfigSchema.JSON_SCHEMA = "https://json-schema.org/draft/2020-12/schema"; /** * Version number stored in $version property of the schema * @readonly * @memberof ConfigSchema */ ConfigSchema.SCHEMA_VERSION = "1.0"; /** * Pretty explanation of the schema objects * @readonly * @memberof ConfigSchema */ ConfigSchema.explainSchemaSummary = { $schema: "URL", $version: "Version", properties: { defaults: "Default Definitions", explainedParentKey: "Properties", ignoredKeys: null }, explainedParentKey: "Schema", ignoredKeys: null }; //# sourceMappingURL=ConfigSchema.js.map