From c571fa71a48140c873c7c3915422e2c5a852cb08 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 1 Apr 2026 10:24:42 -0700 Subject: [PATCH 1/2] APIGOV-32330 Engage CLI - Commands - APPLY --- package-lock.json | 168 ++++++++++++++++- package.json | 2 + src/commands/engage/apply.ts | 109 +++++++++++ src/commands/engage/get.ts | 2 + src/commands/engage/index.ts | 2 + src/lib/clients-external/apiserverclient.ts | 8 +- src/lib/request.ts | 2 +- src/lib/results/DefinitionsManager.ts | 52 +++--- src/lib/results/renderer.ts | 12 +- src/lib/types.ts | 24 +++ src/lib/utils/basic-prompts.ts | 190 ++++++++++++++++++++ src/lib/utils/utils.ts | 136 ++++++++++++-- temp.yaml | 58 ++++++ 13 files changed, 715 insertions(+), 50 deletions(-) create mode 100644 src/commands/engage/apply.ts create mode 100644 src/lib/utils/basic-prompts.ts create mode 100644 temp.yaml diff --git a/package-lock.json b/package-lock.json index f2e6f268..b6fcb759 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,9 @@ "got": "^14.6.2", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", + "inquirer": "^8.2.4", "jose": "^6.1.0", + "js-yaml": "^4.1.0", "keytar": "7.9.0", "lodash": "^4.17.21", "node-cache": "^5.1.2", @@ -1582,7 +1584,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { @@ -3631,6 +3632,30 @@ "node": ">= 4.9.1" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4427,6 +4452,116 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/inquirer": { + "version": "8.2.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", + "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.0", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/inquirer/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/inquirer/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" + }, + "node_modules/inquirer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4980,7 +5115,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -6351,6 +6485,24 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -6985,6 +7137,12 @@ "node": ">=6" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -7141,6 +7299,12 @@ "strip-bom": "^3.0.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tsscmp": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", diff --git a/package.json b/package.json index 9c6838dc..639723e4 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,9 @@ "got": "^14.6.2", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", + "inquirer": "^8.2.4", "jose": "^6.1.0", + "js-yaml": "^4.1.0", "keytar": "7.9.0", "lodash": "^4.17.21", "node-cache": "^5.1.2", diff --git a/src/commands/engage/apply.ts b/src/commands/engage/apply.ts new file mode 100644 index 00000000..25a3e769 --- /dev/null +++ b/src/commands/engage/apply.ts @@ -0,0 +1,109 @@ +import Command from '../../lib/command.js'; +import { Flags } from '@oclif/core'; +import { commonFlags } from '../../lib/engage/flags.js'; +import logger, { highlight } from '../../lib/logger.js'; +import { OutputTypes, LanguageTypes, ApiServerClientApplyResult, GenericResource, YesNo, YesNoChoices } from '../../lib/types.js'; +import { loadAndVerifySpecs, verifyFile } from '../../lib/utils/utils.js'; +import Renderer from '../../lib/results/renderer.js'; +import { ApiServerClient } from '../../lib/clients-external/apiserverclient.js'; +import { DefinitionsManager } from '../../lib/results/DefinitionsManager.js'; +import { askList } from '../../lib/utils/basic-prompts.js'; +import chalk from 'chalk'; + +export default class EngageApply extends Command { + static override summary = 'Update resources from a file.'; + + static override aliases = [ 'central:apply' ]; + + static override description = `You must be authenticated to update one or more resources. + Run ${highlight('"axway auth login"')} to authenticate.`; + + static override examples = [ + { + description: 'Update a resource from a file', + command: '<%= config.bin %> <%= command.id %> --file ', + }, + ]; + + static override flags = { + ...commonFlags, + output: Flags.string({ + char: 'o', + description: `Additional output formats. One of: ${OutputTypes.yaml} | ${OutputTypes.json}`, + }), + file: Flags.string({ + char: 'f', + description: 'Filename to use to create or update the resources. One of: yaml | json', + }), + yes: Flags.boolean({ + char: 'y', + description: 'Automatically reply "yes" to any command prompts.', + }), + language: Flags.string({ + description: `Language Codes. One of: Comma Separated values of ${LanguageTypes.French} | ${LanguageTypes.US} | ${LanguageTypes.German} | ${LanguageTypes.Portugese}`, + }), + subresource: Flags.string({ + description: 'Name of the 1 subresource to update. Will prevent main resource and all other subresources from being updated.', + }), + }; + + async run(): Promise { + const log = logger('EngageApply'); + + const { flags, account } = await this.parse(EngageApply); + + let isCmdError = false; + + // need to verify args here since if "-f" is required + log('verifying args'); + if (!flags.file) { + throw new Error('File name is required, please provide -f, --file [path] option'); + } + + log(`verifying file: ${flags.file}`); + verifyFile(flags.file); + + let results: ApiServerClientApplyResult[] = []; + const render = new Renderer(console, flags.output).startSpin('Creating or updating resource(s)'); + const client = new ApiServerClient({ account, region: flags.region, useCache: flags.cache }); + const defsManager = new DefinitionsManager(client); + + log('executing api calls'); + try { + await defsManager.init(); + log('loading and verifying specs'); + const { docs, isMissingName } = await loadAndVerifySpecs(flags.file, defsManager.getAllKindsList()); + if (!flags.yes && isMissingName) { + render.stopSpin(); + if ( + (await askList({ + msg: `As your file contains resources with missing logical names, their logical names will be autogenerated. \nRun ${chalk.cyan( + 'axway engage apply -f -o [yaml|json] -y > ' + )} to capture the resource(s) with the autogenerated logical name(s) so you can use them again. \nNOTE: To suppress this prompt in the future, please use the '-y' flag. \nWould you like to continue *without* capturing the resource names in a new file?`, + choices: YesNoChoices, + default: YesNo.Yes, + })) === YesNo.No + ) { process.exit(1); } + render.startSpin('Creating or updating resource(s)'); + } + const sortedKindsMap = defsManager.getSortedKindsMap(); + results = await client.bulkCreateOrUpdate(docs as GenericResource[], sortedKindsMap, flags.language, flags.subresource); + render.bulkCreateOrUpdateResult(results); + isCmdError = results.some((nextResult) => (nextResult.error?.length ?? 0) > 0); + } catch (e: any) { + log('command error', e); + isCmdError = true; + if (results.some((nextResult) => nextResult.data)) { + // Render the results that have completed. + render.bulkCreateOrUpdateResult(results); + } + render.anyError(e); + } finally { + log(`command finished, exit with error = ${isCmdError}`); + render.stopSpin(); + if (isCmdError) { + process.exit(1); + } + } + } +} diff --git a/src/commands/engage/get.ts b/src/commands/engage/get.ts index 70e453e3..1247abc0 100644 --- a/src/commands/engage/get.ts +++ b/src/commands/engage/get.ts @@ -13,6 +13,8 @@ import { resolveTeamNames } from '../../lib/results/resultsrenderer.js'; export default class EngageGet extends Command { static override summary = 'List one or more resources.'; + static override aliases = [ 'central:get' ]; + static override description = `You must be authenticated to list one or more resources. Run ${highlight('"axway auth login"')} to authenticate.`; diff --git a/src/commands/engage/index.ts b/src/commands/engage/index.ts index 2b3b7637..76fa07b3 100644 --- a/src/commands/engage/index.ts +++ b/src/commands/engage/index.ts @@ -4,6 +4,8 @@ import { highlight } from '../../lib/logger.js'; export default class EngageCommand extends Command { static override hidden = true; + static override aliases = [ 'central' ]; + static override summary = 'Manage APIs, services and publish to the Amplify Marketplace.'; static override description = `You must be authenticated to manage Engage Operations. diff --git a/src/lib/clients-external/apiserverclient.ts b/src/lib/clients-external/apiserverclient.ts index 61e5fa4d..5c52eb5b 100644 --- a/src/lib/clients-external/apiserverclient.ts +++ b/src/lib/clients-external/apiserverclient.ts @@ -320,14 +320,14 @@ export class ApiServerClient { resources.map((resource) => { const resourceDef = sortedDefsArray.find( (def) => - def.spec.kind === resource.kind - && def.spec.scope?.kind === resource.metadata?.scope?.kind, + def.spec?.kind === resource.kind + && def.spec?.scope?.kind === resource.metadata?.scope?.kind, ); const scopeDef = resource.metadata?.scope ? sortedDefsArray.find( (def) => - def.spec.kind === resource.metadata!.scope!.kind - && !def.spec.scope, + def.spec?.kind === resource.metadata!.scope!.kind + && !def.spec?.scope, ) : undefined; const scopeName = resource.metadata?.scope?.name; diff --git a/src/lib/request.ts b/src/lib/request.ts index 7450ffef..ef95b463 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -412,7 +412,7 @@ export const dataService = async ({ // eslint-disable-next-line no-loop-func limit(async () => { allPages[thisPageIndex] = await (this as DataServiceMethods).get( - fullUrl, + url, params ); pageDownloadCount++; diff --git a/src/lib/results/DefinitionsManager.ts b/src/lib/results/DefinitionsManager.ts index 36940cc8..8c9763a0 100644 --- a/src/lib/results/DefinitionsManager.ts +++ b/src/lib/results/DefinitionsManager.ts @@ -47,7 +47,7 @@ export class DefinitionsManager { return curr.unsorted.reduce<{ sorted: ResourceDefinition[]; unsorted: ResourceDefinition[] }>( (a, c, _, arr) => { // IF any unsorted reference found, push current definition to unsorted too and skip - const unsortedRefs = c.spec.references.toResources.find((ref) => { + const unsortedRefs = c.spec?.references?.toResources?.find((ref) => { return ( loadash.findLastIndex(arr, (def) => def.spec.kind === ref.kind && def.spec.scope?.kind === ref.scopeKind) !== -1 ); @@ -72,10 +72,8 @@ export class DefinitionsManager { // this should never happen only if the api-server is missing some corresponding // references so nothing found if (sortedListIndex === -1 && fromListIndex === -1) { - log('reduceByReferenceLinks, startIndex error, def: ', JSON.stringify(c, null, 2), '\nref: ', ref); - throw Error( - `References calculation error, startIndex for kind: ${c.spec.kind} in scope: ${c.spec.scope?.kind}` - ); + log('reduceByReferenceLinks, startIndex not found for ref: ', ref, ' in def: ', c.spec.kind); + return null; } else { // if nothing found in sorted and pre return null so it will put it back to unsorted return sortedListIndex === -1 ? 0 : sortedListIndex; } @@ -83,7 +81,7 @@ export class DefinitionsManager { ) ?? null; const stopIndex = loadash.min( - c.spec.references.fromResources.map((ref) => { + c.spec?.references?.fromResources?.map((ref) => { const i = loadash.findIndex( a.sorted, (def) => def.spec.kind === ref.kind && def.spec.scope?.kind === ref.scopeKind @@ -93,10 +91,8 @@ export class DefinitionsManager { ) ?? null; if ((startIndex && stopIndex && startIndex >= stopIndex) || startIndex === null) { - log('reduceByReferenceLinks, indexes error, definition: ', JSON.stringify(c, null, 2)); - throw Error( - `References calculation error, indexes for kind: ${c.spec.kind} in scope: ${c.spec.scope?.kind}` - ); + log('reduceByReferenceLinks, indexes error, skipping definition: ', c.spec.kind, ' in scope: ', c.spec.scope?.kind); + a.unsorted.push(c); } else { a.sorted.splice(startIndex + 1, 0, c); } @@ -122,8 +118,8 @@ export class DefinitionsManager { to: ResourceDefinition[]; // defs with only "to" references }>( (a, c) => { - const fromRefsNum = c.spec.references.fromResources.length; - const toRefsNum = c.spec.references.toResources.length; + const fromRefsNum = c.spec?.references?.fromResources?.length ?? 0; + const toRefsNum = c.spec?.references?.toResources?.length ?? 0; if (fromRefsNum && toRefsNum) { a.fromTo.push(c); } else if (!fromRefsNum && !toRefsNum) { @@ -153,7 +149,7 @@ export class DefinitionsManager { // On average function should not take more than 5 loops currently. // Lets signal that something is wrong here. if (loopCount === 1000) { - throw Error('Definition references calculation error, max loop count reached'); + log('sortByReferences, max loop count reached, some definitions may be out of order: ', result.unsorted.map((d) => d.spec?.kind)); } return [ ...groupedDefs.noRefs, ...groupedDefs.from, ...result.sorted, ...groupedDefs.to ]; } @@ -198,7 +194,9 @@ export class DefinitionsManager { } const result: Set = new Set([]); this.resources.forEach((v) => { - result.add(v.spec.kind); + if (v?.spec?.kind) { + result.add(v.spec.kind); + } }); return result; } @@ -217,6 +215,9 @@ export class DefinitionsManager { // 2. if it is a scoped resource, add a manual "to" reference to the scope resource and a corresponding "from" // reference in the scope resource this.resources.forEach((definition) => { + if (!definition?.spec?.references) { + return; + } // 1. remove the references from the sub-resources and circular references (to self) // TODO: circular references support: https://jira.axway.com/browse/APIGOV-20808 definition.spec.references.toResources = definition.spec.references.toResources.filter( @@ -227,7 +228,10 @@ export class DefinitionsManager { ); // 2. add references between scope and scoped resources if (definition.spec.scope) { - const scopeDef = [ ...this.resources.values() ].find((res) => res.spec.kind === definition.spec.scope!.kind)!; // mind the non-null assertion here + const scopeDef = [ ...this.resources.values() ].find((res) => res.spec?.kind === definition.spec.scope!.kind); + if (!scopeDef) { + return; + } // modify current definition by adding "toResources" link to scopeDef if (!definition.spec.references.toResources.find((ref) => ref.kind === scopeDef.spec.kind)) { definition.spec.references.toResources.push({ @@ -250,7 +254,7 @@ export class DefinitionsManager { } }); // execute the sorting, note that the returning map is using the "name" field as keys. - const res = this.sortByReferences([ ...this.resources.values() ]); + const res = this.sortByReferences([ ...this.resources.values() ].filter((v) => v?.spec?.references)); return new Map(res.map((v) => [ v.name, v ])); } @@ -311,10 +315,10 @@ export class DefinitionsManager { } const cliKv = [ ...this.cli ].filter( ([ _, v ]) => - v.spec.names.plural === word - || v.spec.names.singular === word - || v.spec.names.shortNames.includes(word) - || v.spec.names.shortNamesAlias?.includes(word) + v.spec?.names.plural === word + || v.spec?.names.singular === word + || v.spec?.names.shortNames.includes(word) + || v.spec?.names.shortNamesAlias?.includes(word) ); // no match found returning null if (!cliKv.length) { @@ -324,10 +328,10 @@ export class DefinitionsManager { { resource: ResourceDefinition; cli: CommandLineInterface; scope?: ResourceDefinition }[] >((a, [ _, cliDef ]) => { if ( - cliDef.spec.names.plural === word - || cliDef.spec.names.singular === word - || cliDef.spec.names.shortNames.includes(word) - || cliDef.spec.names.shortNamesAlias?.includes(word) + cliDef.spec?.names.plural === word + || cliDef.spec?.names.singular === word + || cliDef.spec?.names.shortNames.includes(word) + || cliDef.spec?.names.shortNamesAlias?.includes(word) ) { // note: mind non-null assertion const resource = this.resources.get(cliDef.spec.resourceDefinition)!; diff --git a/src/lib/results/renderer.ts b/src/lib/results/renderer.ts index cae46c83..6809e007 100644 --- a/src/lib/results/renderer.ts +++ b/src/lib/results/renderer.ts @@ -76,7 +76,7 @@ export default class Renderer { success(text: string, spinnerOnly: boolean = false): void { this.output && !spinnerOnly ? this.console(text) - : this.spinner && this.spinner.succeed(chalk`{greenBright ${text}}`); + : this.spinner && this.spinner.succeed(chalk.greenBright(text)); } /** @@ -87,7 +87,7 @@ export default class Renderer { * only when spinner is in use (which mean no output param has been provided) */ warning(text: string, spinnerOnly: boolean = false): void { - this.output && !spinnerOnly ? this.console(text) : this.spinner && this.spinner.warn(chalk`{yellow ${text}}`); + this.output && !spinnerOnly ? this.console(text) : this.spinner && this.spinner.warn(chalk.yellow(text)); } /** @@ -98,7 +98,7 @@ export default class Renderer { * only when spinner is in use (which mean no output param has been provided) */ error(text: string, spinnerOnly: boolean = false): void { - this.output && !spinnerOnly ? this.console(text) : this.spinner && this.spinner.fail(chalk`{red ${text}}`); + this.output && !spinnerOnly ? this.console(text) : this.spinner && this.spinner.fail(chalk.red(text)); } /** @@ -162,6 +162,7 @@ export default class Renderer { */ productizationResult(bulkResultMap: Map): void { bulkResultMap.forEach((value, key) => { + // eslint-disable-next-line no-useless-concat console.log('\n\n' + 'API Service: ' + key); if (value.warning && value.warning.length > 0) { value.warning?.forEach((r) => @@ -174,6 +175,7 @@ export default class Renderer { this.warning(`Unable to productize API Service '${key}' for the above errors.`); } } else { + // eslint-disable-next-line no-useless-concat console.log('API Service ' + '\'' + key + '\' has been successfully productized.'); } }); @@ -378,10 +380,10 @@ export default class Renderer { case 401: this.error(this.#createApiServerErrorMessage(prefixMsg, err.title || 'Looks like you\'re not authenticated!')); this.console('\nTry running:'); - this.console(chalk`{cyan axway auth login}`); + this.console(chalk.cyan('axway auth login')); this.console('Or if using a service account:'); this.console( - chalk`{cyan axway auth login --client-id --secret-file }` + chalk.cyan('axway auth login --client-id --secret-file ') ); break; case 400: diff --git a/src/lib/types.ts b/src/lib/types.ts index 8beb998a..ff48f211 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -343,3 +343,27 @@ export enum BasePaths { DockerAgentAPIRepoPath = '/artifactory/api/docker/ampc-public-docker-release/v2/agent', } +export interface ValidatedDocs { + docs: Record[]; + isMissingName: boolean; +} + +export enum TrueFalse { + True = 'True', + False = 'False', +} + +export const TrueFalseChoices = [ + { name: TrueFalse.True, value: TrueFalse.True }, + { name: TrueFalse.False, value: TrueFalse.False }, +]; + +export enum YesNo { + Yes = 'Yes', + No = 'No', +} + +export const YesNoChoices = [ + { name: YesNo.Yes, value: YesNo.Yes }, + { name: YesNo.No, value: YesNo.No }, +]; diff --git a/src/lib/utils/basic-prompts.ts b/src/lib/utils/basic-prompts.ts new file mode 100644 index 00000000..46f09a1c --- /dev/null +++ b/src/lib/utils/basic-prompts.ts @@ -0,0 +1,190 @@ +import inquirer from 'inquirer'; +import Separator from 'inquirer/lib/objects/separator.js'; +import { lstatSync, Stats } from 'fs'; +import { extname } from 'path'; +// +// Basic Prompts +// + +export type InputValidation = (input: string | number) => boolean | string; + +/** + * @param validators At least one InputValidation func + * @description Executes the provided InputValidation funcs until all are successful, or one returns an error. + * Pass to the askInput for validation when input validation becomes complex. Provides an easy way + * to break down input validation into their own small functions. + */ +export const runValidations + = (...validators: InputValidation[]): InputValidation => + (input: string | number) => { + for (const validator of validators) { + const res = validator(input); + if (typeof res === 'string') { + return res; + } + } + return true; + }; + +export const validateRegex + = (regexp: string, message: string): InputValidation => + (input: string | number) => { + return input.toString().match(regexp) ? true : message; + }; + +export const validateInputLength + = (length: number, message: string): InputValidation => + (input: string | number) => { + return input.toString().length <= length ? true : message; + }; + +export const MAX_FILE_SIZE = process.env.NODE_ENV === 'test' ? 1e5 : 20 * 1024 * 1024; + +export const verifyApigeeXCredentialFile = (): InputValidation => (input: string | number) => { + let stats: Stats; + let fileExtension: string = ''; + try { + stats = lstatSync(input as string); + fileExtension = extname(input as string); + if (!stats.isFile()) { + throw new Error(`Couldn't load the credential file: ${input}`); + } else if (stats.size >= MAX_FILE_SIZE) { + throw new Error('File size too large'); + } else if (fileExtension !== '.json') { + throw new Error('File extension is invalid, please provide \'.json\' file'); + } + return true; + } catch (e) { + throw new Error(`Couldn't find the credential file: ${input}`); + } +}; + +export const validateValidRegex = (): InputValidation => (input: string | number) => { + try { + new RegExp(input.toString()); + } catch (error) { + return 'Please provide a valid regular expression.'; + } + return true; +}; + +export const validateInputIsNew + = (options: string[], error: string): InputValidation => + (input: string | number) => { + const isFound = options.find((opt) => opt === input); + return isFound ? error : true; + }; + +export const validateValueRange + = (lowerLimit?: number, upperLimit?: number): InputValidation => + (input: string | number) => { + const inputNum = Number(input); + if (isNaN(inputNum)) { + return 'Please provide a number.'; + } + + let msg = ''; + if (lowerLimit !== undefined && upperLimit !== undefined) { + msg = `Please provide a number from ${lowerLimit} to ${upperLimit}`; + } else if (lowerLimit !== undefined) { + msg = `Please provide a minimum number of ${lowerLimit}`; + } else if (upperLimit !== undefined) { + msg = `Please provide a maximum number of ${upperLimit}`; + } + + if (lowerLimit !== undefined && inputNum < (lowerLimit as number)) { + return msg; + } + + if (upperLimit !== undefined && inputNum > (upperLimit as number)) { + return msg; + } + + return true; + }; + +// exporting for test +export const validateNonEmptyInput: InputValidation = (input: string | number) => { + return String(input).length ? true : 'Please provide a non-empty value.'; +}; + +// exporting for test +export const filterEmptyNumberInput = (input: string) => { + // clear the invalid input + return Number.isNaN(input as any) ? '' : Number(input); +}; + +export const askInputValidation + = (allowEmptyInput: boolean, validate?: InputValidation): InputValidation => + (input: string | number) => { + if (allowEmptyInput && !String(input).length) { + return true; + } + const isEmpty = allowEmptyInput ? true : validateNonEmptyInput(input); + if (typeof isEmpty === 'string') { + return isEmpty; + } + return validate ? validate(input) : true; + }; + +export const askInput = async ({ + msg, + defaultValue, + type = 'string', + validate, + allowEmptyInput = false, +}: { + msg: string; + defaultValue?: string | number; + type?: 'string' | 'number'; + validate?: InputValidation; + allowEmptyInput?: boolean; +}): Promise => { + const answers = await inquirer.prompt({ + type: type === 'string' ? 'input' : 'number', + name: 'value', + message: `${msg}: `, + default: defaultValue, + validate: askInputValidation(allowEmptyInput, validate), + filter: type === 'number' ? filterEmptyNumberInput : undefined, + }); + return answers.value; +}; + +export const askList = async (opts: { + msg: string; + choices: (string | { name: string; value: string } | Separator)[]; + default?: string; +}): Promise => { + const answers = await inquirer.prompt({ + type: 'list', + name: 'value', + message: `${opts.msg}: `, + choices: opts.choices, + default: opts.default, + }); + return answers.value; +}; + +export const askUsernameAndPassword = async ( + msg: string, + defaultUsername: string +): Promise<{ username: string; password: string }> => { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'username', + message: `Enter ${msg} username: `, + default: defaultUsername, + validate: validateNonEmptyInput, + }, + { + type: 'password', + name: 'password', + mask: '*', + message: `Enter ${msg} password: `, + validate: validateNonEmptyInput, + }, + ]); + return answers; +}; diff --git a/src/lib/utils/utils.ts b/src/lib/utils/utils.ts index 27013af7..a4d41d27 100644 --- a/src/lib/utils/utils.ts +++ b/src/lib/utils/utils.ts @@ -1,5 +1,28 @@ import { writeFileSync } from '../fs.js'; +import { loadAll } from 'js-yaml'; + +import chalk from 'chalk'; +import { + ApiServerError, + ApiServerErrorResponse, + ApiServerVersions, + CommandLineInterface, + GenericResource, + GenericResourceWithoutName, + LanguageTypes, + MAX_FILE_SIZE, + Metadata, + ParsedScopeParam, + ResourceDefinition, + ValidatedDocs, +} from '../types.js'; +import { FindDefsByWordResult } from '../results/DefinitionsManager.js'; +import { lstatSync, Stats } from 'fs'; +import { readFile } from 'fs/promises'; +import { extname } from 'path'; +import { CompositeError } from '../results/compositeerror.js'; + export const writeToFile = (path: string, data: any): void => { try { writeFileSync(path, data); @@ -23,20 +46,6 @@ export const isValidJson = (item: any) => { } return typeof parsedItem === 'object' && item !== null; }; -import chalk from 'chalk'; -import { - ApiServerError, - ApiServerErrorResponse, - ApiServerVersions, - CommandLineInterface, - GenericResource, - GenericResourceWithoutName, - LanguageTypes, - Metadata, - ParsedScopeParam, - ResourceDefinition, -} from '../types.js'; -import { FindDefsByWordResult } from '../results/DefinitionsManager.js'; export function ValueFromKey( stringEnum: { [key: string]: string }, @@ -293,3 +302,102 @@ export function getFieldSetFromDefinitionColumns(def: FindDefsByWordResult): Set }); return fieldSet; } + +export const verifyFile = (specFilePath: string): Error | void => { + let stats: Stats; + let fileExtension: string; + try { + stats = lstatSync(specFilePath); + fileExtension = extname(specFilePath); + } catch (e) { + throw new Error(`Couldn't find the definition file: ${specFilePath}`); + } + + if (!stats.isFile()) { + throw new Error(`Couldn't load the definition file: ${specFilePath}`); + } else if (stats.size >= MAX_FILE_SIZE) { + throw new Error('File size too large'); + } else if (fileExtension !== '.yaml' && fileExtension !== '.yml' && fileExtension !== '.json') { + throw new Error('File extension is invalid, please provide \'.yaml\' or \'.yml\' or \'.json\' file'); + } +}; + +/** + * Loads and parse file from path, accepts JSON and YAML files. Also completing validation on "kind" values. + * @param specFilePath file path + * @param allowedKinds array of allowed "kind" values + */ +export const loadAndVerifySpecs = async ( + specFilePath: string, + allowedKinds: Set, + skipKindCheck?: boolean +): Promise => { + // Load the given JSON or YAML file. + let docs = []; + let isMissingName = false; + try { + docs = loadAll(await readFile(specFilePath, 'utf8')); + } catch (e: any) { + throw new Error( + e.reason && e.reason.includes('null byte') + ? 'File encoding is invalid, please make sure it is using UTF-8' + : 'File content is invalid.' + ); + } + + // if user pass an array of json objects, docs const will have nested array, workaround for this: + if (extname(specFilePath) === '.json' && docs.length === 1 && Array.isArray(docs[0])) { + docs = docs[0]; + } + + // Do not continue if given an empty file. + if (!docs.length) { + throw new Error('File is empty.'); + } + + // Validate all entries in the file. + const errors: Error[] = []; + const createErrorPrefix = (index: number, kind?: string, name?: string) => { + return `Entry ${index + 1}, "${kind}/${name || 'Unknown name'}"`; + }; + for (let index = 0; index < docs.length; index++) { + // Verify document is defined/valid. + const doc = docs[index]; + if (typeof doc !== 'object' || !doc) { + errors.push(new Error(`${createErrorPrefix(index)}: Entry format is invalid.`)); + continue; + } + + // Set a flag if at least 1 name is messing in file. + if (!doc.name) { + isMissingName = true; + } + + if (!skipKindCheck) { + // Validate resource kind. + if (!doc.kind) { + errors.push( + Error( + `${createErrorPrefix(index, doc.kind, doc.name)}: The "kind" field is missing.` + + `\nCurrently supported values are (case sensitive): ${[ ...allowedKinds.values() ].join(', ')}` + ) + ); + } else if (!allowedKinds.has(doc.kind)) { + errors.push( + new Error( + `${createErrorPrefix(index, doc.kind, doc.name)}: Kind "${doc.kind}" is unsupported.` + + `\nCurrently supported values are (case sensitive): ${[ ...allowedKinds.values() ].join(', ')}` + ) + ); + } + } + + // TODO: Validate "metadata.scope.kind" if available. Requires DefinitionManager.getSortedKindsMap() result. + } + if (errors.length > 0) { + throw new CompositeError(errors); + } + + // File's contents appears to be valid. Return loaded info. + return { docs, isMissingName }; +}; diff --git a/temp.yaml b/temp.yaml new file mode 100644 index 00000000..286314ba --- /dev/null +++ b/temp.yaml @@ -0,0 +1,58 @@ +group: management +apiVersion: v1alpha1 +kind: Environment +name: tomato +title: Service Demo 123 +metadata: + id: e4e57c9575008de901750f364cae1320 + audit: + createTimestamp: 2020-10-09T21:13:38.222+0000 + createUserId: 5c7dc2e2-84eb-4a78-9db0-75ceeee9184d + modifyTimestamp: 2026-03-30T23:39:17.107+0000 + modifyUserId: service-account-a29d98dd-788b-43dc-8726-34c4dc1a613f + acl: [] + accessRights: + canChangeOwner: true + canDelete: true + canWrite: true + canRead: true + resourceVersion: '35894' + references: [] + selfLink: /management/v1alpha1/environments/tomato +attributes: + createdBy: yaml + randomNum: '1' +finalizers: [] +tags: + - etstet5 + - teset2 + - teste4 + - test3 + - test9 + - test7 + - test8 + - test12 + - cli + - test11 + - test + - test10 + - axway +spec: + icon: + data: >- + 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 + contentType: image/png + production: false + description: Environment for Demos behind Feature Flag + axwayManaged: false +policies: + credentials: + expiry: + notifications: + daysBefore: + - 1 + - 3 + - 7 + - 14 + - 30 + From 67e90efc6a0f17554cf908f03b83215910f793c8 Mon Sep 17 00:00:00 2001 From: Deepak Kasu Date: Wed, 1 Apr 2026 10:26:20 -0700 Subject: [PATCH 2/2] APIGOV-32330 Updates --- temp.yaml | 58 ------------------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 temp.yaml diff --git a/temp.yaml b/temp.yaml deleted file mode 100644 index 286314ba..00000000 --- a/temp.yaml +++ /dev/null @@ -1,58 +0,0 @@ -group: management -apiVersion: v1alpha1 -kind: Environment -name: tomato -title: Service Demo 123 -metadata: - id: e4e57c9575008de901750f364cae1320 - audit: - createTimestamp: 2020-10-09T21:13:38.222+0000 - createUserId: 5c7dc2e2-84eb-4a78-9db0-75ceeee9184d - modifyTimestamp: 2026-03-30T23:39:17.107+0000 - modifyUserId: service-account-a29d98dd-788b-43dc-8726-34c4dc1a613f - acl: [] - accessRights: - canChangeOwner: true - canDelete: true - canWrite: true - canRead: true - resourceVersion: '35894' - references: [] - selfLink: /management/v1alpha1/environments/tomato -attributes: - createdBy: yaml - randomNum: '1' -finalizers: [] -tags: - - etstet5 - - teset2 - - teste4 - - test3 - - test9 - - test7 - - test8 - - test12 - - cli - - test11 - - test - - test10 - - axway -spec: - icon: - data: >- - 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 - contentType: image/png - production: false - description: Environment for Demos behind Feature Flag - axwayManaged: false -policies: - credentials: - expiry: - notifications: - daysBefore: - - 1 - - 3 - - 7 - - 14 - - 30 -