From 0a2f1f865670bbc558c8ac8576b3fe1a99ba4f0f Mon Sep 17 00:00:00 2001 From: rliechti Date: Fri, 16 Aug 2024 09:38:45 +0200 Subject: [PATCH 01/67] create skeletons for snapshot commands --- src/commands/aem/rde/clean.js | 56 +++++++++++++++++++++++ src/commands/aem/rde/snapshot/apply.js | 58 ++++++++++++++++++++++++ src/commands/aem/rde/snapshot/create.js | 49 ++++++++++++++++++++ src/commands/aem/rde/snapshot/delete.js | 50 ++++++++++++++++++++ src/commands/aem/rde/snapshot/index.js | 50 ++++++++++++++++++++ src/commands/aem/rde/snapshot/restore.js | 41 +++++++++++++++++ 6 files changed, 304 insertions(+) create mode 100644 src/commands/aem/rde/clean.js create mode 100644 src/commands/aem/rde/snapshot/apply.js create mode 100644 src/commands/aem/rde/snapshot/create.js create mode 100644 src/commands/aem/rde/snapshot/delete.js create mode 100644 src/commands/aem/rde/snapshot/index.js create mode 100644 src/commands/aem/rde/snapshot/restore.js diff --git a/src/commands/aem/rde/clean.js b/src/commands/aem/rde/clean.js new file mode 100644 index 0000000..c59cf8e --- /dev/null +++ b/src/commands/aem/rde/clean.js @@ -0,0 +1,56 @@ +/* + * Copyright 2022 Adobe Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +'use strict'; + +const { BaseCommand, Flags } = require('../../../lib/base-command'); +const { CloudSdkAPIBase } = require('../../../lib/cloud-sdk-api-base'); +const { codes: validationCodes } = require('../../../lib/validation-errors'); +const { codes: internalCodes } = require('../../../lib/internal-errors'); +const { throwAioError } = require('../../../lib/error-helpers'); +const chalk = require('chalk'); +const { concatEnvironemntId } = require('../../../lib/utils'); + +class CleanEnvrionment extends BaseCommand { + constructor(argv, config) { + super(argv, config); + this.programsCached = []; + this.environmentsCached = []; + } + + async runCommand(args, flags) { + this.log('Implement clean environmet...'); + } +} + +Object.assign(CleanEnvrionment, { + description: 'Old aliases', + aliases: ['aem:rde:suuber'], + deprecateAliases: true, +}); + +Object.assign(CleanEnvrionment, { + description: + 'Removes the deployment and upgrades to latest AEM version while keeping the content. Gives an option to delete the content.', + args: [], + aliases: [], + flags: { + 'drop-content': Flags.boolean({ + description: 'Also delete the content of the environment.', + char: 'd', + multiple: false, + required: false, + default: false, + }), + }, +}); + +module.exports = CleanEnvrionment; diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js new file mode 100644 index 0000000..6a40c75 --- /dev/null +++ b/src/commands/aem/rde/snapshot/apply.js @@ -0,0 +1,58 @@ +/* + * Copyright 2022 Adobe Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +'use strict'; + +const { BaseCommand, Flags } = require('../../../../lib/base-command'); +const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); +const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { throwAioError } = require('../../../../lib/error-helpers'); +const chalk = require('chalk'); +const { concatEnvironemntId } = require('../../../../lib/utils'); + +class ApplySnapshots extends BaseCommand { + constructor(argv, config) { + super(argv, config); + this.programsCached = []; + this.environmentsCached = []; + } + + async runCommand(args, flags) { + this.log('Implement apply snapshot...'); + } +} + +Object.assign(ApplySnapshots, { + description: 'Applies a snapshot to the environment.', + args: [], + aliases: [], + flags: { + 'keep-deployment': Flags.boolean({ + description: + 'Keeps the deployment of the RDE and applies the content only.', + char: 'k', + multiple: false, + required: false, + default: false, + }), + quiet: Flags.boolean({ + description: + 'Does not ask for user confirmation before applying the snapshot.', + char: 'q', + multiple: false, + required: false, + default: false, + }), + }, +}); + +module.exports = ApplySnapshots; diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js new file mode 100644 index 0000000..22696ed --- /dev/null +++ b/src/commands/aem/rde/snapshot/create.js @@ -0,0 +1,49 @@ +/* + * Copyright 2022 Adobe Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +'use strict'; + +const { BaseCommand, Flags } = require('../../../../lib/base-command'); +const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); +const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { throwAioError } = require('../../../../lib/error-helpers'); +const chalk = require('chalk'); +const { concatEnvironemntId } = require('../../../../lib/utils'); + +class CreateSnapshots extends BaseCommand { + constructor(argv, config) { + super(argv, config); + this.programsCached = []; + this.environmentsCached = []; + } + + async runCommand(args, flags) { + this.log('Implement create snapshot...'); + } +} + +Object.assign(CreateSnapshots, { + description: + 'Creates a snapshot of the current state of the environment, includes content and deployment.', + args: [], + aliases: [], + flags: { + description: Flags.string({ + description: 'A brief description of the snapshot.', + char: 'd', + multiple: false, + required: false, + }), + }, +}); + +module.exports = CreateSnapshots; diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js new file mode 100644 index 0000000..6fbb0b1 --- /dev/null +++ b/src/commands/aem/rde/snapshot/delete.js @@ -0,0 +1,50 @@ +/* + * Copyright 2022 Adobe Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +'use strict'; + +const { BaseCommand, Flags } = require('../../../../lib/base-command'); +const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); +const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { throwAioError } = require('../../../../lib/error-helpers'); +const chalk = require('chalk'); +const { concatEnvironemntId } = require('../../../../lib/utils'); + +class DeleteSnapshots extends BaseCommand { + constructor(argv, config) { + super(argv, config); + this.programsCached = []; + this.environmentsCached = []; + } + + async runCommand(args, flags) { + this.log('Implement delete snapshot...'); + } +} + +Object.assign(DeleteSnapshots, { + description: + 'Marks a snapshot for deletion. The snapshot will be deleted after 7 days. A previously deleted snapshot can be restored.', + args: [], + aliases: [], + flags: { + all: Flags.boolean({ + description: 'Wipe all snapshots from the organization.', + char: 'a', + multiple: false, + required: false, + default: false, + }), + }, +}); + +module.exports = DeleteSnapshots; diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js new file mode 100644 index 0000000..d171d95 --- /dev/null +++ b/src/commands/aem/rde/snapshot/index.js @@ -0,0 +1,50 @@ +/* + * Copyright 2022 Adobe Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +'use strict'; + +const { BaseCommand, Flags } = require('../../../../lib/base-command'); +const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); +const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { throwAioError } = require('../../../../lib/error-helpers'); +const chalk = require('chalk'); +const { concatEnvironemntId } = require('../../../../lib/utils'); + +class ListSnapshots extends BaseCommand { + constructor(argv, config) { + super(argv, config); + this.programsCached = []; + this.environmentsCached = []; + } + + async runCommand(args, flags) { + this.log('Implement list snapshots...'); + } +} + +Object.assign(ListSnapshots, { + description: + 'Lists all content and deployment snapshots in your organization. Use --help for a list of subcommands.', + args: [], + aliases: [], + flags: { + usage: Flags.boolean({ + description: 'Sorts the snapshots by usage, most used first.', + char: 'u', + multiple: false, + required: false, + default: false, + }), + }, +}); + +module.exports = ListSnapshots; diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js new file mode 100644 index 0000000..944d16e --- /dev/null +++ b/src/commands/aem/rde/snapshot/restore.js @@ -0,0 +1,41 @@ +/* + * Copyright 2022 Adobe Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +'use strict'; + +const { BaseCommand, Flags } = require('../../../../lib/base-command'); +const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); +const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { throwAioError } = require('../../../../lib/error-helpers'); +const chalk = require('chalk'); +const { concatEnvironemntId } = require('../../../../lib/utils'); + +class RestoreSnapshots extends BaseCommand { + constructor(argv, config) { + super(argv, config); + this.programsCached = []; + this.environmentsCached = []; + } + + async runCommand(args, flags) { + this.log('Implement restore snapshot...'); + } +} + +Object.assign(RestoreSnapshots, { + description: 'Restores a snapshot so it will not be deleted any longer.', + args: [], + aliases: [], + flags: {}, +}); + +module.exports = RestoreSnapshots; From 6c53d21d35cbe1b4ae735672cb24108d05782b3d Mon Sep 17 00:00:00 2001 From: rliechti Date: Tue, 20 Aug 2024 09:07:14 +0200 Subject: [PATCH 02/67] add support for snapshot requests --- src/commands/aem/rde/snapshot/index.js | 35 +++++++++++++++--- src/lib/cloud-sdk-api.js | 50 ++++++++++++++++++++++++++ src/lib/doRequest.js | 26 ++++++-------- 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index d171d95..b81fd57 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -12,12 +12,8 @@ 'use strict'; const { BaseCommand, Flags } = require('../../../../lib/base-command'); -const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); -const { codes: validationCodes } = require('../../../../lib/validation-errors'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); -const chalk = require('chalk'); -const { concatEnvironemntId } = require('../../../../lib/utils'); class ListSnapshots extends BaseCommand { constructor(argv, config) { @@ -27,7 +23,36 @@ class ListSnapshots extends BaseCommand { } async runCommand(args, flags) { - this.log('Implement list snapshots...'); + try { + const result = this.jsonResult(); + this.spinnerStart('fetching snapshots'); + const response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.getSnapshots() + ); + if (response.status === 200) { + const json = await response.json(); + result.status = json?.status; + this.spinnerStop(); + if (json?.items?.length === 0) { + this.doLog('There are no snapshots yet.'); + } else { + result.items = json?.items; + json?.items.forEach((e) => this.log(e)); + } + } else { + throw new internalCodes.UNEXPECTED_API_ERROR({ + messageValues: [response.status, response.statusText], + }); + } + return result; + } catch (err) { + throwAioError( + err, + new internalCodes.INTERNAL_HISTORY_ERROR({ messageValues: err }) + ); + } finally { + this.spinnerStop(); + } } } diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index b659409..393a123 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -62,6 +62,12 @@ class CloudSdkAPI { `${rdeUrl}/program/${programId}/environment/${environmentId}`, authorizationHeaders ); + this._snapshotClient = new DoRequest( + `${rdeUrl}/snapshots`, + authorizationHeaders + ); + this.programId = programId; + this.environmentId = environmentId; this._cmReleaseId = concatEnvironemntId(programId, environmentId); } @@ -181,6 +187,36 @@ class CloudSdkAPI { ); } + async getSnapshots() { + return await this._snapshotClient.doGet(``); + } + + async deleteAllSnapshots() { + return await this._snapshotClient.doDelete(``); + } + + async deleteSnapshot(name) { + return await this._snapshotClient.doDelete(`/${name}`); + } + + async restoreSnapshot(name) { + return await this._snapshotClient.doPut(`/${name}`); + } + + async createSnapshot(name, params) { + const queryString = this.createUrlQueryStr(params); + return await this._snapshotClient.doPost( + `/program/${this.programId}/environment/${this.environmentId}${queryString}` + ); + } + + async applySnapshot(name, params) { + const queryString = this.createUrlQueryStr(params); + return await this._snapshotClient.doPatch( + `/program/${this.programId}/environment/${this.environmentId}${queryString}` + ); + } + async getLogs(id) { return await this._rdeClient.doGet(`/runtime/updates/${id}/logs`); } @@ -522,6 +558,20 @@ class CloudSdkAPI { await this._cloudManagerClient.doPut(`/reset`); } + async cleanEnv(wait, params) { + await this._checkRDE(); + await this._waitForEnvReady(); + await this._cleanEnv(); + if (wait) { + await this._waitForEnvReady(); + } + } + + async _cleanEnv(params) { + const queryString = this.createUrlQueryStr(params); + await this._rdeClient.doPut(`/clean${queryString}`); + } + async _waitForEnvReady() { await this._waitForJson( (status) => status.status === 'ready', diff --git a/src/lib/doRequest.js b/src/lib/doRequest.js index 042a29b..3316d33 100644 --- a/src/lib/doRequest.js +++ b/src/lib/doRequest.js @@ -46,27 +46,23 @@ class DoRequest { } async doPost(path, body) { - const ret = this.doRequest('post', path, body); - if (ret) { - return ret; - } - throw new internalCodes.NETWORK_ERROR({ - messageValues: this._baseUrl + path, - }); + return this.do('post', path, body); } async doPut(path, body) { - const ret = this.doRequest('put', path, body); - if (ret) { - return ret; - } - throw new internalCodes.NETWORK_ERROR({ - messageValues: this._baseUrl + path, - }); + return this.do('put', path, body); + } + + async doPatch(path, body) { + return this.do('patch', path, body); } async doDelete(path) { - const ret = this.doRequest('delete', path); + return this.do('delete', path); + } + + async do(method, path, body) { + const ret = this.doRequest(method, path, body); if (ret) { return ret; } From 170a559131820dc4376d2197dd2ecbd014176924 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 Aug 2024 13:26:43 +0200 Subject: [PATCH 03/67] skeleton implementation of calling the endpoints, no json ouput yet --- src/commands/aem/rde/clean.js | 40 +++++++++--- src/commands/aem/rde/snapshot/apply.js | 53 ++++++++++++---- src/commands/aem/rde/snapshot/create.js | 56 +++++++++++++---- src/commands/aem/rde/snapshot/delete.js | 79 +++++++++++++++++++++--- src/commands/aem/rde/snapshot/index.js | 37 ++++++----- src/commands/aem/rde/snapshot/restore.js | 47 ++++++++++---- src/lib/cloud-sdk-api.js | 24 ++++--- src/lib/configuration-errors.js | 5 ++ src/lib/internal-errors.js | 9 +++ src/lib/snapshot-errors.js | 57 +++++++++++++++++ 10 files changed, 323 insertions(+), 84 deletions(-) create mode 100644 src/lib/snapshot-errors.js diff --git a/src/commands/aem/rde/clean.js b/src/commands/aem/rde/clean.js index c59cf8e..a7fd3c8 100644 --- a/src/commands/aem/rde/clean.js +++ b/src/commands/aem/rde/clean.js @@ -12,22 +12,42 @@ 'use strict'; const { BaseCommand, Flags } = require('../../../lib/base-command'); -const { CloudSdkAPIBase } = require('../../../lib/cloud-sdk-api-base'); -const { codes: validationCodes } = require('../../../lib/validation-errors'); +const { + codes: configurationCodes, +} = require('../../../lib/configuration-errors'); const { codes: internalCodes } = require('../../../lib/internal-errors'); const { throwAioError } = require('../../../lib/error-helpers'); const chalk = require('chalk'); -const { concatEnvironemntId } = require('../../../lib/utils'); class CleanEnvrionment extends BaseCommand { - constructor(argv, config) { - super(argv, config); - this.programsCached = []; - this.environmentsCached = []; - } - async runCommand(args, flags) { - this.log('Implement clean environmet...'); + let response; + try { + this.spinnerStart(`Cleaning the rde...`); + response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.cleanEnv(args.name, flags['drop-content']) + ); + } catch (err) { + this.spinnerStop(); + throwAioError( + err, + new internalCodes.INTERNAL_CLEAN_ERROR({ messageValues: err }) + ); + } + this.spinnerStop(); + if (response?.status === 200) { + this.doLog( + chalk.green( + `RDE cleaned sucessfully. Use 'aio aem rde status' to view the updated state.` + ) + ); + } else if (response?.status === 400) { + throw new configurationCodes.DIFFERENT_ENV_TYPE(); + } else if (response?.status === 404) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else { + throw new internalCodes.UNKNOWN(); + } } } diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index 6a40c75..d24dfe7 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -12,28 +12,59 @@ 'use strict'; const { BaseCommand, Flags } = require('../../../../lib/base-command'); -const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); -const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { codes: snapshotCodes } = require('../../../../lib/snapshot-errors'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { + codes: configurationCodes, +} = require('../../../../lib/configuration-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); -const { concatEnvironemntId } = require('../../../../lib/utils'); class ApplySnapshots extends BaseCommand { - constructor(argv, config) { - super(argv, config); - this.programsCached = []; - this.environmentsCached = []; - } - async runCommand(args, flags) { - this.log('Implement apply snapshot...'); + let response; + try { + this.spinnerStart(`Applying snapshot ${args.name}...`); + response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.applySnapshot(args.name, { + 'keep-deployment': flags['keep-deployment'], + }) + ); + } catch (err) { + this.spinnerStop(); + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } + this.spinnerStop(); + if (response?.status === 200) { + this.doLog( + chalk.green( + `Snapshot ${args.name} applied successfully. Use 'aio rde status' to view installed artifacts.` + ) + ); + } else if (response?.status === 400) { + throw new configurationCodes.DIFFERENT_ENV_TYPE(); + } else if (response?.status === 404) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (response?.status === 406) { + throw new snapshotCodes.INVALID_STATE(); + } else { + throw new internalCodes.UNKNOWN(); + } } } Object.assign(ApplySnapshots, { description: 'Applies a snapshot to the environment.', - args: [], + args: [ + { + name: 'name', + description: 'The name of the snapshot to apply to the current RDE.', + required: true, + }, + ], aliases: [], flags: { 'keep-deployment': Flags.boolean({ diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 22696ed..4642d06 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -12,29 +12,63 @@ 'use strict'; const { BaseCommand, Flags } = require('../../../../lib/base-command'); -const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); -const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { codes: snapshotCodes } = require('../../../../lib/snapshot-errors'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { + codes: configurationCodes, +} = require('../../../../lib/configuration-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); -const { concatEnvironemntId } = require('../../../../lib/utils'); class CreateSnapshots extends BaseCommand { - constructor(argv, config) { - super(argv, config); - this.programsCached = []; - this.environmentsCached = []; - } - async runCommand(args, flags) { - this.log('Implement create snapshot...'); + let response; + try { + this.spinnerStart(`Creating snapshot ${args.name}...`); + response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.createSnapshot(args.name, { + description: flags.description, + }) + ); + } catch (err) { + this.spinnerStop(); + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } + this.spinnerStop(); + if (response?.status === 200) { + this.doLog( + chalk.green( + `Snapshot ${args.name} created successfully. Use 'aio rde snapshot' to view.` + ) + ); + } else if (response?.status === 400) { + throw new configurationCodes.DIFFERENT_ENV_TYPE(); + } else if (response?.status === 404) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (response?.status === 406) { + throw new snapshotCodes.INVALID_STATE(); + } else if (response?.status === 409) { + throw new snapshotCodes.ALREADY_EXISTS(); + } else { + throw new internalCodes.UNKNOWN(); + } } } Object.assign(CreateSnapshots, { description: 'Creates a snapshot of the current state of the environment, includes content and deployment.', - args: [], + args: [ + { + name: 'name', + description: + 'The name of the new snapshot. The name must be unique within the environment.', + required: true, + }, + ], aliases: [], flags: { description: Flags.string({ diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 6fbb0b1..9d03fda 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -12,29 +12,88 @@ 'use strict'; const { BaseCommand, Flags } = require('../../../../lib/base-command'); -const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); -const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { codes: snapshotCodes } = require('../../../../lib/snapshot-errors'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); -const { concatEnvironemntId } = require('../../../../lib/utils'); class DeleteSnapshots extends BaseCommand { - constructor(argv, config) { - super(argv, config); - this.programsCached = []; - this.environmentsCached = []; + async runCommand(args, flags) { + if (flags.all) { + this.deleteAllSnapshots(); + } else { + this.deleteSnapshot(args.name); + } } - async runCommand(args, flags) { - this.log('Implement delete snapshot...'); + async deleteAllSnapshots() { + let response; + try { + this.spinnerStart('fetching all snapshots'); + response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.getSnapshots() + ); + } catch (err) { + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } finally { + this.spinnerStop(); + } + + if (response.status === 200) { + const json = await response.json(); + this.spinnerStop(); + if (json?.items?.length === 0) { + this.doLog('There are no snapshots yet.'); + } else { + json?.items.forEach((e) => this.deleteSnapshot(e.name)); + } + } else { + throw new internalCodes.UNKNOWN(); + } + } + + async deleteSnapshot(name) { + let response; + try { + this.spinnerStart(`Deleting snapshot ${name}...`); + response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.deleteSnapshot(name) + ); + } catch (err) { + this.spinnerStop(); + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } + this.spinnerStop(); + if (response?.status === 200) { + this.doLog( + chalk.green( + `Snapshot ${name} deleted successfully. Use 'aio rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio rde snapshot restore' to restore it.` + ) + ); + } else if (response?.status === 404) { + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else { + throw new internalCodes.UNKNOWN(); + } } } Object.assign(DeleteSnapshots, { description: 'Marks a snapshot for deletion. The snapshot will be deleted after 7 days. A previously deleted snapshot can be restored.', - args: [], + args: [ + { + name: 'name', + description: 'The name of the snapshot to apply to the current RDE.', + required: true, + }, + ], aliases: [], flags: { all: Flags.boolean({ diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index b81fd57..0e389a4 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -23,36 +23,35 @@ class ListSnapshots extends BaseCommand { } async runCommand(args, flags) { + let response; + const result = this.jsonResult(); try { - const result = this.jsonResult(); this.spinnerStart('fetching snapshots'); - const response = await this.withCloudSdk((cloudSdkAPI) => + response = await this.withCloudSdk((cloudSdkAPI) => cloudSdkAPI.getSnapshots() ); - if (response.status === 200) { - const json = await response.json(); - result.status = json?.status; - this.spinnerStop(); - if (json?.items?.length === 0) { - this.doLog('There are no snapshots yet.'); - } else { - result.items = json?.items; - json?.items.forEach((e) => this.log(e)); - } - } else { - throw new internalCodes.UNEXPECTED_API_ERROR({ - messageValues: [response.status, response.statusText], - }); - } - return result; } catch (err) { throwAioError( err, - new internalCodes.INTERNAL_HISTORY_ERROR({ messageValues: err }) + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) ); } finally { this.spinnerStop(); } + + if (response.status === 200) { + const json = await response.json(); + result.status = json?.status; + this.spinnerStop(); + if (json?.items?.length === 0) { + this.doLog('There are no snapshots yet.'); + } else { + result.items = json?.items; + json?.items.forEach((e) => this.log(e)); + } + } else { + throw new internalCodes.UNKNOWN(); + } } } diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index 944d16e..4777c4e 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -11,29 +11,50 @@ */ 'use strict'; -const { BaseCommand, Flags } = require('../../../../lib/base-command'); -const { CloudSdkAPIBase } = require('../../../../lib/cloud-sdk-api-base'); -const { codes: validationCodes } = require('../../../../lib/validation-errors'); +const { BaseCommand } = require('../../../../lib/base-command'); +const { codes: snapshotCodes } = require('../../../../lib/snapshot-errors'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); -const { concatEnvironemntId } = require('../../../../lib/utils'); - class RestoreSnapshots extends BaseCommand { - constructor(argv, config) { - super(argv, config); - this.programsCached = []; - this.environmentsCached = []; - } - async runCommand(args, flags) { - this.log('Implement restore snapshot...'); + let response; + try { + this.spinnerStart(`Restore snapshot ${args.name}...`); + response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.restoreSnapshot(args.name) + ); + } catch (err) { + this.spinnerStop(); + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } + this.spinnerStop(); + if (response?.status === 200) { + this.doLog( + chalk.green( + `Snapshot ${args.name} restored successfully. Use 'aio rde snapshot' to view its updated state. Use 'aio rde snapshot apply ${args.name}' to apply it on the RDE.` + ) + ); + } else if (response?.status === 404) { + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else { + throw new internalCodes.UNKNOWN(); + } } } Object.assign(RestoreSnapshots, { description: 'Restores a snapshot so it will not be deleted any longer.', - args: [], + args: [ + { + name: 'name', + description: 'The name of the snapshot to apply to the current RDE.', + required: true, + }, + ], aliases: [], flags: {}, }); diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 393a123..5d58edd 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -191,10 +191,6 @@ class CloudSdkAPI { return await this._snapshotClient.doGet(``); } - async deleteAllSnapshots() { - return await this._snapshotClient.doDelete(``); - } - async deleteSnapshot(name) { return await this._snapshotClient.doDelete(`/${name}`); } @@ -204,17 +200,25 @@ class CloudSdkAPI { } async createSnapshot(name, params) { + params = { + ...params, + name, + programId: this.programId, + environmentId: this.environmentId, + }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPost( - `/program/${this.programId}/environment/${this.environmentId}${queryString}` - ); + return await this._snapshotClient.doPost(`${queryString}`); } async applySnapshot(name, params) { + params = { + ...params, + name, + programId: this.programId, + environmentId: this.environmentId, + }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPatch( - `/program/${this.programId}/environment/${this.environmentId}${queryString}` - ); + return await this._snapshotClient.doPatch(`${queryString}`); } async getLogs(id) { diff --git a/src/lib/configuration-errors.js b/src/lib/configuration-errors.js index 8235eab..bd9f048 100644 --- a/src/lib/configuration-errors.js +++ b/src/lib/configuration-errors.js @@ -102,3 +102,8 @@ E( 'MISSING_INSPECT_ACCESS_TOKEN', 'The access token for the inspect commands is missing. Please set one up with the `aio aem rde inspect setup` command.' ); +E('DIFFERENT_ENV_TYPE', 'The given environment is not an RDE'); +E( + 'PROGRAM_OR_ENVIRONMENT_NOT_FOUND', + 'The environment or program does not exist' +); diff --git a/src/lib/internal-errors.js b/src/lib/internal-errors.js index 3f2be66..2c746d0 100644 --- a/src/lib/internal-errors.js +++ b/src/lib/internal-errors.js @@ -112,6 +112,10 @@ E( 'INTERNAL_GET_SLING_REQUESTS_ERROR', 'There was an unexpected error when running get sling requests command. Please, try again later and if the error persists, report it. Error %s' ); +E( + 'INTERNAL_SNAPSHOT_ERROR', + 'There was an unexpected error when running a snapshot command. Please, try again later and if the error persists, report it. Error %s' +); E( 'INTERNAL_DELETE_ERROR', 'There was an unexpected error when running delete command. Please, try again later and if the error persists, report it. Error %s' @@ -128,6 +132,10 @@ E( 'INTERNAL_RESET_ERROR', 'There was an unexpected error when running reset command. Please, try again later and if the error persists, report it. Error %s' ); +E( + 'INTERNAL_CLEAN_ERROR', + 'There was an unexpected error when running clean command. Please, try again later and if the error persists, report it. Error %s' +); E( 'INTERNAL_RESTART_ERROR', 'There was an unexpected error when running restart command. Please, try again later and if the error persists, report it. Error %s' @@ -136,3 +144,4 @@ E( 'INTERNAL_STATUS_ERROR', 'There was an unexpected error when running status command. Please, try again later and if the error persists, report it. Error %s' ); +E('UNKNOWN', 'An unknown error occurred'); diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js new file mode 100644 index 0000000..2ce23bd --- /dev/null +++ b/src/lib/snapshot-errors.js @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Adobe Inc. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +const { ErrorWrapper, createUpdater } = + require('@adobe/aio-lib-core-errors').AioCoreSDKErrorWrapper; + +const codes = {}; +const messages = new Map(); + +/** + * Create an Updater for the Error wrapper + * + * @ignore + */ +const Updater = createUpdater( + // object that stores the error classes (to be exported) + codes, + // Map that stores the error strings (to be exported) + messages +); + +/** + * Provides a wrapper to easily create classes of a certain name, and values + * + * @ignore + */ +const E = ErrorWrapper( + // The class name for your SDK Error. Your Error objects will be these objects + 'RDECLIValidationError', + // The name of your SDK. This will be a property in your Error objects + 'RDECLI', + // the object returned from the CreateUpdater call above + Updater + // the base class that your Error class is extending. AioCoreSDKError is the default + /* AioCoreSDKError, */ +); + +module.exports = { + codes, + messages, +}; + +// Define your error codes with the wrapper +E( + 'INVALID_STATE', + 'The RDE is not in a state where a snapshot can be created from' +); +E('ALREADY_EXISTS', 'A snapshot with the given name already exists'); +E('SNAPSHOT_NOT_FOUND', 'The snapshot does not exist'); From 01b6c876921430e6ba493142c6ea0f95899af041 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 Aug 2024 13:34:12 +0200 Subject: [PATCH 04/67] correct aio calls in messages --- src/commands/aem/rde/clean.js | 2 ++ src/commands/aem/rde/snapshot/apply.js | 2 +- src/commands/aem/rde/snapshot/create.js | 2 +- src/commands/aem/rde/snapshot/delete.js | 2 +- src/commands/aem/rde/snapshot/restore.js | 2 +- src/lib/internal-errors.js | 6 +++++- src/lib/snapshot-errors.js | 2 +- 7 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/commands/aem/rde/clean.js b/src/commands/aem/rde/clean.js index a7fd3c8..376c5d2 100644 --- a/src/commands/aem/rde/clean.js +++ b/src/commands/aem/rde/clean.js @@ -45,6 +45,8 @@ class CleanEnvrionment extends BaseCommand { throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (response?.status === 406) { + throw new internalCodes.INVALID_STATE(); } else { throw new internalCodes.UNKNOWN(); } diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index d24dfe7..6a9ab6e 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -41,7 +41,7 @@ class ApplySnapshots extends BaseCommand { if (response?.status === 200) { this.doLog( chalk.green( - `Snapshot ${args.name} applied successfully. Use 'aio rde status' to view installed artifacts.` + `Snapshot ${args.name} applied successfully. Use 'aio aem rde status' to view installed artifacts.` ) ); } else if (response?.status === 400) { diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 4642d06..83e0f09 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -41,7 +41,7 @@ class CreateSnapshots extends BaseCommand { if (response?.status === 200) { this.doLog( chalk.green( - `Snapshot ${args.name} created successfully. Use 'aio rde snapshot' to view.` + `Snapshot ${args.name} created successfully. Use 'aio aem rde snapshot' to view or 'aio aem rde snapshot apply ${args.name}' to apply it on the RDE.` ) ); } else if (response?.status === 400) { diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 9d03fda..be1e13b 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -73,7 +73,7 @@ class DeleteSnapshots extends BaseCommand { if (response?.status === 200) { this.doLog( chalk.green( - `Snapshot ${name} deleted successfully. Use 'aio rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio rde snapshot restore' to restore it.` + `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio aem rde snapshot restore ${name}' to restore it.` ) ); } else if (response?.status === 404) { diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index 4777c4e..9d1f7f3 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -35,7 +35,7 @@ class RestoreSnapshots extends BaseCommand { if (response?.status === 200) { this.doLog( chalk.green( - `Snapshot ${args.name} restored successfully. Use 'aio rde snapshot' to view its updated state. Use 'aio rde snapshot apply ${args.name}' to apply it on the RDE.` + `Snapshot ${args.name} restored successfully. Use 'aio aem rde snapshot' to view its updated state. Use 'aio aem rde snapshot apply ${args.name}' to apply it on the RDE.` ) ); } else if (response?.status === 404) { diff --git a/src/lib/internal-errors.js b/src/lib/internal-errors.js index 2c746d0..f8fffbd 100644 --- a/src/lib/internal-errors.js +++ b/src/lib/internal-errors.js @@ -144,4 +144,8 @@ E( 'INTERNAL_STATUS_ERROR', 'There was an unexpected error when running status command. Please, try again later and if the error persists, report it. Error %s' ); -E('UNKNOWN', 'An unknown error occurred'); +E('UNKNOWN', 'An unknown error occurred.'); +E( + 'INVALID_STATE', + 'The RDE is not in a state where the command can be executed.' +); diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js index 2ce23bd..8cb5a16 100644 --- a/src/lib/snapshot-errors.js +++ b/src/lib/snapshot-errors.js @@ -51,7 +51,7 @@ module.exports = { // Define your error codes with the wrapper E( 'INVALID_STATE', - 'The RDE is not in a state where a snapshot can be created from' + 'The RDE is not in a state where a snapshot can be created or applied' ); E('ALREADY_EXISTS', 'A snapshot with the given name already exists'); E('SNAPSHOT_NOT_FOUND', 'The snapshot does not exist'); From b8fc0f1b30767fe26ef4874a953f456cfd8b5adb Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 29 Aug 2024 14:33:55 +0200 Subject: [PATCH 05/67] SKYOPS-84421 adapt to latest spec --- src/commands/aem/rde/clean.js | 2 +- src/commands/aem/rde/snapshot/create.js | 6 ++++-- src/commands/aem/rde/snapshot/delete.js | 13 ++++++++++--- src/commands/aem/rde/snapshot/restore.js | 2 ++ src/lib/cloud-sdk-api.js | 13 +++++++++---- src/lib/snapshot-errors.js | 8 ++++++++ 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/commands/aem/rde/clean.js b/src/commands/aem/rde/clean.js index 376c5d2..4252787 100644 --- a/src/commands/aem/rde/clean.js +++ b/src/commands/aem/rde/clean.js @@ -45,7 +45,7 @@ class CleanEnvrionment extends BaseCommand { throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); - } else if (response?.status === 406) { + } else if (response?.status === 503) { throw new internalCodes.INVALID_STATE(); } else { throw new internalCodes.UNKNOWN(); diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 83e0f09..1051e96 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -48,10 +48,12 @@ class CreateSnapshots extends BaseCommand { throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); - } else if (response?.status === 406) { - throw new snapshotCodes.INVALID_STATE(); } else if (response?.status === 409) { throw new snapshotCodes.ALREADY_EXISTS(); + } else if (response?.status === 503) { + throw new snapshotCodes.INVALID_STATE(); + } else if (response?.status === 507) { + throw new snapshotCodes.SNAPSHOT_LIMIT(); } else { throw new internalCodes.UNKNOWN(); } diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index be1e13b..1ddba93 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -22,7 +22,7 @@ class DeleteSnapshots extends BaseCommand { if (flags.all) { this.deleteAllSnapshots(); } else { - this.deleteSnapshot(args.name); + this.deleteSnapshot(args.name, flags.force); } } @@ -55,12 +55,12 @@ class DeleteSnapshots extends BaseCommand { } } - async deleteSnapshot(name) { + async deleteSnapshot(name, force) { let response; try { this.spinnerStart(`Deleting snapshot ${name}...`); response = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.deleteSnapshot(name) + cloudSdkAPI.deleteSnapshot(name, force) ); } catch (err) { this.spinnerStop(); @@ -76,6 +76,8 @@ class DeleteSnapshots extends BaseCommand { `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio aem rde snapshot restore ${name}' to restore it.` ) ); + } else if (response?.status === 400) { + throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); } else if (response?.status === 404) { throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); } else { @@ -103,6 +105,11 @@ Object.assign(DeleteSnapshots, { required: false, default: false, }), + force: Flags.boolean({ + char: 'f', + multiple: false, + required: false, + }), }, }); diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index 9d1f7f3..f7a1675 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -40,6 +40,8 @@ class RestoreSnapshots extends BaseCommand { ); } else if (response?.status === 404) { throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else if (response?.status === 507) { + throw new snapshotCodes.SNAPSHOT_LIMIT(); } else { throw new internalCodes.UNKNOWN(); } diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 5d58edd..2698fa2 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -191,8 +191,12 @@ class CloudSdkAPI { return await this._snapshotClient.doGet(``); } - async deleteSnapshot(name) { - return await this._snapshotClient.doDelete(`/${name}`); + async deleteSnapshot(name, force) { + const params = { + force, + }; + const queryString = this.createUrlQueryStr(params); + return await this._snapshotClient.doDelete(`/${name}${queryString}`); } async restoreSnapshot(name) { @@ -213,12 +217,13 @@ class CloudSdkAPI { async applySnapshot(name, params) { params = { ...params, - name, programId: this.programId, environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPatch(`${queryString}`); + return await this._snapshotClient.doPost( + `/snapshots/${name}/apply${queryString}` + ); } async getLogs(id) { diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js index 8cb5a16..b655889 100644 --- a/src/lib/snapshot-errors.js +++ b/src/lib/snapshot-errors.js @@ -55,3 +55,11 @@ E( ); E('ALREADY_EXISTS', 'A snapshot with the given name already exists'); E('SNAPSHOT_NOT_FOUND', 'The snapshot does not exist'); +E( + 'SNAPSHOT_LIMIT', + 'Reached the maximum number or diskspace of snapshots. Remove some snapshots and try again' +); +E( + 'SNAPSHOT_WRONG_STATE', + 'Snapshot is in wrong state. Must be in state "REMOVED" to be able to wipe.' +); From a51567782e59a016c62f93e9730a7f52e33dbe3c Mon Sep 17 00:00:00 2001 From: rliechti Date: Wed, 11 Sep 2024 14:25:38 +0200 Subject: [PATCH 06/67] adapt to snapshots being stored on environment level instead of organizational level --- src/commands/aem/rde/snapshot/delete.js | 9 ++++++++- src/commands/aem/rde/snapshot/index.js | 7 +++++++ src/commands/aem/rde/snapshot/restore.js | 7 +++++++ src/lib/cloud-sdk-api.js | 16 ++++++++++++++-- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 1ddba93..8664132 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -14,6 +14,9 @@ const { BaseCommand, Flags } = require('../../../../lib/base-command'); const { codes: snapshotCodes } = require('../../../../lib/snapshot-errors'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { + codes: configurationCodes, +} = require('../../../../lib/configuration-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); @@ -77,9 +80,13 @@ class DeleteSnapshots extends BaseCommand { ) ); } else if (response?.status === 400) { - throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); + throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (response?.status === 410) { throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else if (response?.status === 412) { + throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); } else { throw new internalCodes.UNKNOWN(); } diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index 0e389a4..4e16ed1 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -14,6 +14,9 @@ const { BaseCommand, Flags } = require('../../../../lib/base-command'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); +const { + codes: configurationCodes, +} = require('../../../../lib/configuration-errors'); class ListSnapshots extends BaseCommand { constructor(argv, config) { @@ -49,6 +52,10 @@ class ListSnapshots extends BaseCommand { result.items = json?.items; json?.items.forEach((e) => this.log(e)); } + } else if (response?.status === 400) { + throw new configurationCodes.DIFFERENT_ENV_TYPE(); + } else if (response?.status === 404) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); } else { throw new internalCodes.UNKNOWN(); } diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index f7a1675..25e9231 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -14,6 +14,9 @@ const { BaseCommand } = require('../../../../lib/base-command'); const { codes: snapshotCodes } = require('../../../../lib/snapshot-errors'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { + codes: configurationCodes, +} = require('../../../../lib/configuration-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); class RestoreSnapshots extends BaseCommand { @@ -38,7 +41,11 @@ class RestoreSnapshots extends BaseCommand { `Snapshot ${args.name} restored successfully. Use 'aio aem rde snapshot' to view its updated state. Use 'aio aem rde snapshot apply ${args.name}' to apply it on the RDE.` ) ); + } else if (response?.status === 400) { + throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (response?.status === 410) { throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); } else if (response?.status === 507) { throw new snapshotCodes.SNAPSHOT_LIMIT(); diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 2698fa2..0629c15 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -188,19 +188,31 @@ class CloudSdkAPI { } async getSnapshots() { - return await this._snapshotClient.doGet(``); + const params = { + programId: this.programId, + environmentId: this.environmentId, + }; + const queryString = this.createUrlQueryStr(params); + return await this._snapshotClient.doGet(`${queryString}`); } async deleteSnapshot(name, force) { const params = { force, + programId: this.programId, + environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); return await this._snapshotClient.doDelete(`/${name}${queryString}`); } async restoreSnapshot(name) { - return await this._snapshotClient.doPut(`/${name}`); + const params = { + programId: this.programId, + environmentId: this.environmentId, + }; + const queryString = this.createUrlQueryStr(params); + return await this._snapshotClient.doPut(`/${name}${queryString}`); } async createSnapshot(name, params) { From e6bb7cf66c2557c7288b6ca2a3901eb8d8352f17 Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 23 Sep 2024 14:11:47 +0200 Subject: [PATCH 07/67] initial snapshot api implemented --- src/commands/aem/rde/snapshot/apply.js | 9 +++++++- src/commands/aem/rde/snapshot/delete.js | 22 +++++++++++++------ src/commands/aem/rde/snapshot/index.js | 27 +++++++++++++++++++++--- src/commands/aem/rde/snapshot/restore.js | 13 ++++++++---- src/lib/cloud-sdk-api.js | 12 +++++------ 5 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index 6a9ab6e..9dd6f80 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -47,7 +47,14 @@ class ApplySnapshots extends BaseCommand { } else if (response?.status === 400) { throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { - throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + const json = await response.json(); + if ( + json.details === 'The requested environment or program does not exist.' + ) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (json.details === 'The requested snapshot does not exist.') { + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } } else if (response?.status === 406) { throw new snapshotCodes.INVALID_STATE(); } else { diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 8664132..da7030d 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -82,11 +82,21 @@ class DeleteSnapshots extends BaseCommand { } else if (response?.status === 400) { throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { - throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); - } else if (response?.status === 410) { - throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); - } else if (response?.status === 412) { - throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); + const json = await response.json(); + if ( + json.details === 'The requested environment or program does not exist.' + ) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (json.details === 'The requested snapshot does not exist.') { + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } + } else if (response?.status === 403) { + const json = await response.json(); + if ( + json.details === "The snapshot to be wiped is not in state 'removed'." + ) { + throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); + } } else { throw new internalCodes.UNKNOWN(); } @@ -99,7 +109,7 @@ Object.assign(DeleteSnapshots, { args: [ { name: 'name', - description: 'The name of the snapshot to apply to the current RDE.', + description: 'The name of the snapshot to delete.', required: true, }, ], diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index 4e16ed1..0a0e21e 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -11,7 +11,7 @@ */ 'use strict'; -const { BaseCommand, Flags } = require('../../../../lib/base-command'); +const { BaseCommand, Flags, cli } = require('../../../../lib/base-command'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const { @@ -49,8 +49,8 @@ class ListSnapshots extends BaseCommand { if (json?.items?.length === 0) { this.doLog('There are no snapshots yet.'); } else { - result.items = json?.items; - json?.items.forEach((e) => this.log(e)); + result.snapshots = json; + this.logInTableFormat(json); } } else if (response?.status === 400) { throw new configurationCodes.DIFFERENT_ENV_TYPE(); @@ -59,6 +59,27 @@ class ListSnapshots extends BaseCommand { } else { throw new internalCodes.UNKNOWN(); } + return result; + } + + logInTableFormat(items) { + cli.table( + items, + { + name: { + minWidth: 20, + }, + description: { + minWidth: 20, + }, + usage: {}, + size: {}, + state: {}, + created: {}, + lastUsed: { header: 'Last Used' }, + }, + { printLine: (s) => this.doLog(s, true) } + ); } } diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index 25e9231..7c63817 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -44,9 +44,14 @@ class RestoreSnapshots extends BaseCommand { } else if (response?.status === 400) { throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { - throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); - } else if (response?.status === 410) { - throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + const json = await response.json(); + if ( + json.details === 'The requested environment or program does not exist.' + ) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (json.details === 'The requested snapshot does not exist.') { + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } } else if (response?.status === 507) { throw new snapshotCodes.SNAPSHOT_LIMIT(); } else { @@ -60,7 +65,7 @@ Object.assign(RestoreSnapshots, { args: [ { name: 'name', - description: 'The name of the snapshot to apply to the current RDE.', + description: 'The name of the snapshot to apply to the restore.', required: true, }, ], diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 0629c15..9cacae8 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -193,7 +193,7 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doGet(`${queryString}`); + return await this._rdeClient.doGet(`/snapshots${queryString}`); } async deleteSnapshot(name, force) { @@ -203,7 +203,7 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doDelete(`/${name}${queryString}`); + return await this._rdeClient.doDelete(`/snapshots/${name}${queryString}`); } async restoreSnapshot(name) { @@ -212,18 +212,18 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPut(`/${name}${queryString}`); + return await this._rdeClient.doPut(`/snapshots/${name}${queryString}`); } async createSnapshot(name, params) { params = { ...params, - name, + 'snapshot-name': name, programId: this.programId, environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPost(`${queryString}`); + return await this._rdeClient.doPost(`/snapshots${queryString}`); } async applySnapshot(name, params) { @@ -233,7 +233,7 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPost( + return await this._rdeClient.doPost( `/snapshots/${name}/apply${queryString}` ); } From d2338206063201bb36902a106544245eed171779 Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 23 Sep 2024 15:18:57 +0200 Subject: [PATCH 08/67] use snapshot client --- src/lib/cloud-sdk-api.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 9cacae8..70b4372 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -193,7 +193,7 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._rdeClient.doGet(`/snapshots${queryString}`); + return await this._snapshotClient.doGet(`${queryString}`); } async deleteSnapshot(name, force) { @@ -203,7 +203,7 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._rdeClient.doDelete(`/snapshots/${name}${queryString}`); + return await this._snapshotClient.doDelete(`/${name}${queryString}`); } async restoreSnapshot(name) { @@ -212,7 +212,7 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._rdeClient.doPut(`/snapshots/${name}${queryString}`); + return await this._snapshotClient.doPut(`/${name}${queryString}`); } async createSnapshot(name, params) { @@ -223,7 +223,7 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._rdeClient.doPost(`/snapshots${queryString}`); + return await this._snapshotClient.doPost(`${queryString}`); } async applySnapshot(name, params) { @@ -233,8 +233,8 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._rdeClient.doPost( - `/snapshots/${name}/apply${queryString}` + return await this._snapshotClient.doPost( + `/${name}/apply${queryString}` ); } From d4e4bca01192dc0d198b9b0eb8387782f57536a9 Mon Sep 17 00:00:00 2001 From: Julian Sedding Date: Tue, 22 Oct 2024 14:09:58 +0200 Subject: [PATCH 09/67] SKYOPS-89194 - [RDE] aio plugin: support setting IMS context via flag --- src/lib/base-command.js | 14 ++++++-- test/lib/base-command.test.js | 68 ++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/lib/base-command.js b/src/lib/base-command.js index a5b5a62..eecddd2 100644 --- a/src/lib/base-command.js +++ b/src/lib/base-command.js @@ -162,9 +162,10 @@ class BaseCommand extends Command { * */ async getTokenAndKey() { - // TODO - support context flag let contextName = - (await context.getCurrent()) || 'aio-cli-plugin-cloudmanager'; + this.flags?.context || + (await context.getCurrent()) || + 'aio-cli-plugin-cloudmanager'; let contextData = await context.get(contextName); if (!contextData?.data) { @@ -301,6 +302,15 @@ class BaseCommand extends Command { Object.assign(BaseCommand, { description: 'Enable json output for all commands by default.', enableJsonFlag: true, + flags: { + context: Flags.string({ + aliases: ['ctx', 'imsContextName'], + description: 'The IMS context used to retrieve login information', + multiple: false, + required: false, + helpGroup: 'GLOBAL', + }), + }, }); module.exports = { diff --git a/test/lib/base-command.test.js b/test/lib/base-command.test.js index c478d4f..e13e06f 100644 --- a/test/lib/base-command.test.js +++ b/test/lib/base-command.test.js @@ -225,6 +225,31 @@ describe('Authentication tests', function () { }), }, }); + + class TestCommand extends BaseCommandAuthMock.BaseCommand { + constructor(commandLine) { + super(commandLine?.split(/\s+/) || []); + } + + runCommand(args, flags) { + /* do nothing */ + } + } + + /** + * Utillity to create and run a test command. Running the + * command exercises the logic parsing the command-line arguments + * and initializing various fields in the command. + * + * @param commandLine The command line arguments as a string. + * @returns A fully initialized TestCommand instance. + */ + async function createCommand(commandLine) { + const command = new TestCommand(commandLine); + await command.run(); + return command; + } + beforeEach(function () { getOrganizationsStub.returns( Promise.resolve([ @@ -247,6 +272,13 @@ describe('Authentication tests', function () { }, local: true, }; + case 'context-via-flag': + return { + data: { + client_id: 'context-via-flag-id', + }, + local: true, + }; case 'cli': return { data: {}, @@ -265,14 +297,32 @@ describe('Authentication tests', function () { }); it('should be able to fetch token and api key', async function () { contextGetCurrentStub.returns('my-context'); - const command = new BaseCommandAuthMock.BaseCommand(); + const command = await createCommand(); const result = await command.getTokenAndKey(); assert.equal(result.accessToken, accessToken); assert.equal(result.apiKey, 'my-context-client_id'); assert.equal(result.local, true); }); + it('should use the --context flag', async function () { + const command = await createCommand('--context context-via-flag'); + const { apiKey, local } = await command.getTokenAndKey(); + assert.equal(apiKey, 'context-via-flag-id'); + assert.equal(local, true); + }); + it('should use the --ctx flag (alias of --context)', async function () { + const command = await createCommand('--ctx context-via-flag'); + const { apiKey, local } = await command.getTokenAndKey(); + assert.equal(apiKey, 'context-via-flag-id'); + assert.equal(local, true); + }); + it('should use the --imsContextName flag (alias of --context)', async function () { + const command = await createCommand('--imsContextName context-via-flag'); + const { apiKey, local } = await command.getTokenAndKey(); + assert.equal(apiKey, 'context-via-flag-id'); + assert.equal(local, true); + }); it('should be able to fetch cli token and api key in case of api error', async function () { - const command = new BaseCommandAuthMock.BaseCommand(); + const command = await createCommand(); const result = await command.getTokenAndKey(); assert.equal(result.accessToken, accessToken); assert.equal(result.apiKey, 'jwt_client_id'); @@ -282,7 +332,7 @@ describe('Authentication tests', function () { getTokenStub.returns(undefined); let err; try { - const command = new BaseCommandAuthMock.BaseCommand(); + const command = await createCommand(); await command.getTokenAndKey(); } catch (e) { err = e; @@ -294,7 +344,7 @@ describe('Authentication tests', function () { getTokenStub.returns(jwt.sign({}, 'pKey', {})); let err; try { - const command = new BaseCommandAuthMock.BaseCommand(); + const command = await createCommand(); sinon.stub(command, 'doLog'); await command.getTokenAndKey(); } catch (e) { @@ -315,20 +365,20 @@ describe('Authentication tests', function () { 'You have to implement the method runCommand(args, flags) in the subclass!' ); }); - it('should return default base url', function () { - const command = new BaseCommandAuthMock.BaseCommand(); + it('should return default base url', async function () { + const command = await createCommand(); getConfigStub.returns(undefined); const baseUrl = command.getBaseUrl(false); assert.equal(baseUrl, 'https://cloudmanager.adobe.io'); }); - it('should return stage base url', function () { - const command = new BaseCommandAuthMock.BaseCommand(); + it('should return stage base url', async function () { + const command = await createCommand(); getConfigStub.returns(undefined); const baseUrl = command.getBaseUrl(true); assert.equal(baseUrl, 'https://cloudmanager-stage.adobe.io'); }); it('cloud sdk should be initialized properly', async function () { - const command = new BaseCommandAuthMock.BaseCommand(); + const command = await createCommand(); const flags = { organizationId: 'orgId', programId: 'progId', From defe450f8f881a0d7c2655c27fbd3708276d45d8 Mon Sep 17 00:00:00 2001 From: angulito Date: Wed, 11 Dec 2024 14:40:36 +0100 Subject: [PATCH 10/67] Bump up npm publish workflow version --- .github/workflows/on-push-publish-to-npm.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/on-push-publish-to-npm.yml b/.github/workflows/on-push-publish-to-npm.yml index c857d39..89370ae 100644 --- a/.github/workflows/on-push-publish-to-npm.yml +++ b/.github/workflows/on-push-publish-to-npm.yml @@ -16,7 +16,12 @@ jobs: node-version: 20 - run: npm install - run: npm test - - uses: JS-DevTools/npm-publish@v1 + - name: Publish to NPM + uses: JS-DevTools/npm-publish@v3 + id: publish with: token: ${{ secrets.ADOBE_BOT_NPM_TOKEN }} access: public + - name: Check if version changed + if: ${{ steps.publish.outputs.type }} + run: echo "Version changed!" From cd41f51ff58c1f93ab3e9e3e42a0f81113a8c259 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Thu, 13 Feb 2025 16:56:40 +0100 Subject: [PATCH 11/67] Add 201 created response code --- src/commands/aem/rde/snapshot/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 1051e96..8196101 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -38,7 +38,7 @@ class CreateSnapshots extends BaseCommand { ); } this.spinnerStop(); - if (response?.status === 200) { + if (response?.status === 200 || response?.status === 201) { this.doLog( chalk.green( `Snapshot ${args.name} created successfully. Use 'aio aem rde snapshot' to view or 'aio aem rde snapshot apply ${args.name}' to apply it on the RDE.` From 847f5ce1b041c3d521934578d35cb943207327a2 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Thu, 13 Feb 2025 16:56:58 +0100 Subject: [PATCH 12/67] Tweak arguments --- src/lib/cloud-sdk-api.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index b7872bb..6bd262b 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -218,12 +218,16 @@ class CloudSdkAPI { async createSnapshot(name, params) { params = { ...params, - 'snapshot-name': name, programId: this.programId, environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPost(`${queryString}`); + return this._snapshotClient.doPost(queryString, { + metadata: { + 'snapshot-name': name, + 'snapshot-description': params.description + } + }); } async applySnapshot(name, params) { @@ -599,7 +603,7 @@ class CloudSdkAPI { const queryString = this.createUrlQueryStr(params); await this._rdeClient.doPut(`/clean${queryString}`); } - + async _waitForCMStatus() { const json = await this._waitForJson( (status) => status.status === 'ready' || status.status === 'reset_failed', From 07f990917c6876975e3b16c840f55cbdd74027c2 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Tue, 18 Feb 2025 12:19:02 +0100 Subject: [PATCH 13/67] Revert "Tweak arguments" This reverts commit 847f5ce1b041c3d521934578d35cb943207327a2. --- src/lib/cloud-sdk-api.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 6bd262b..b7872bb 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -218,16 +218,12 @@ class CloudSdkAPI { async createSnapshot(name, params) { params = { ...params, + 'snapshot-name': name, programId: this.programId, environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return this._snapshotClient.doPost(queryString, { - metadata: { - 'snapshot-name': name, - 'snapshot-description': params.description - } - }); + return await this._snapshotClient.doPost(`${queryString}`); } async applySnapshot(name, params) { @@ -603,7 +599,7 @@ class CloudSdkAPI { const queryString = this.createUrlQueryStr(params); await this._rdeClient.doPut(`/clean${queryString}`); } - + async _waitForCMStatus() { const json = await this._waitForJson( (status) => status.status === 'ready' || status.status === 'reset_failed', From 90c27f5c227aa1c720cb17344d20b6b68b9a01c4 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Tue, 18 Feb 2025 14:18:42 +0100 Subject: [PATCH 14/67] Linting --- src/lib/cloud-sdk-api.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index b7872bb..c212ec8 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -233,9 +233,7 @@ class CloudSdkAPI { environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPost( - `/${name}/apply${queryString}` - ); + return await this._snapshotClient.doPost(`/${name}/apply${queryString}`); } async getLogs(id) { @@ -599,7 +597,7 @@ class CloudSdkAPI { const queryString = this.createUrlQueryStr(params); await this._rdeClient.doPut(`/clean${queryString}`); } - + async _waitForCMStatus() { const json = await this._waitForJson( (status) => status.status === 'ready' || status.status === 'reset_failed', From 18203448d5e110c5e3a1527174a64ee564149b52 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Wed, 26 Feb 2025 12:46:37 +0100 Subject: [PATCH 15/67] Update delete.js Add status code 201 --- src/commands/aem/rde/snapshot/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index da7030d..180a83b 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -73,7 +73,7 @@ class DeleteSnapshots extends BaseCommand { ); } this.spinnerStop(); - if (response?.status === 200) { + if (response?.status === 200 || response?.status === 201) { this.doLog( chalk.green( `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio aem rde snapshot restore ${name}' to restore it.` From 9d6e118b67761bed6471fd9cad946a9177d24afd Mon Sep 17 00:00:00 2001 From: rliechti Date: Wed, 26 Mar 2025 09:52:50 +0100 Subject: [PATCH 16/67] rename restore to undelete --- src/commands/aem/rde/snapshot/delete.js | 4 ++-- .../aem/rde/snapshot/{restore.js => undelete.js} | 16 ++++++++-------- src/lib/cloud-sdk-api.js | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) rename src/commands/aem/rde/snapshot/{restore.js => undelete.js} (79%) diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 180a83b..42d909e 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -76,7 +76,7 @@ class DeleteSnapshots extends BaseCommand { if (response?.status === 200 || response?.status === 201) { this.doLog( chalk.green( - `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio aem rde snapshot restore ${name}' to restore it.` + `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio aem rde snapshot undelete ${name}' to undelete it.` ) ); } else if (response?.status === 400) { @@ -105,7 +105,7 @@ class DeleteSnapshots extends BaseCommand { Object.assign(DeleteSnapshots, { description: - 'Marks a snapshot for deletion. The snapshot will be deleted after 7 days. A previously deleted snapshot can be restored.', + 'Marks a snapshot for deletion. The snapshot will be deleted after 7 days. A previously deleted snapshot can be undeleted.', args: [ { name: 'name', diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/undelete.js similarity index 79% rename from src/commands/aem/rde/snapshot/restore.js rename to src/commands/aem/rde/snapshot/undelete.js index 7c63817..ae850e2 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/undelete.js @@ -19,13 +19,13 @@ const { } = require('../../../../lib/configuration-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); -class RestoreSnapshots extends BaseCommand { +class UndeleteSnapshots extends BaseCommand { async runCommand(args, flags) { let response; try { - this.spinnerStart(`Restore snapshot ${args.name}...`); + this.spinnerStart(`Undelete snapshot ${args.name}...`); response = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.restoreSnapshot(args.name) + cloudSdkAPI.undeleteSnapshot(args.name) ); } catch (err) { this.spinnerStop(); @@ -38,7 +38,7 @@ class RestoreSnapshots extends BaseCommand { if (response?.status === 200) { this.doLog( chalk.green( - `Snapshot ${args.name} restored successfully. Use 'aio aem rde snapshot' to view its updated state. Use 'aio aem rde snapshot apply ${args.name}' to apply it on the RDE.` + `Snapshot ${args.name} undeleted successfully. Use 'aio aem rde snapshot' to view its updated state. Use 'aio aem rde snapshot apply ${args.name}' to apply it on the RDE.` ) ); } else if (response?.status === 400) { @@ -60,12 +60,12 @@ class RestoreSnapshots extends BaseCommand { } } -Object.assign(RestoreSnapshots, { - description: 'Restores a snapshot so it will not be deleted any longer.', +Object.assign(UndeleteSnapshots, { + description: 'Undeletes a snapshot so it will not be deleted any longer.', args: [ { name: 'name', - description: 'The name of the snapshot to apply to the restore.', + description: 'The name of the snapshot to apply to the undeleted.', required: true, }, ], @@ -73,4 +73,4 @@ Object.assign(RestoreSnapshots, { flags: {}, }); -module.exports = RestoreSnapshots; +module.exports = UndeleteSnapshots; diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index c212ec8..5dcf883 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -206,7 +206,7 @@ class CloudSdkAPI { return await this._snapshotClient.doDelete(`/${name}${queryString}`); } - async restoreSnapshot(name) { + async undeleteSnapshot(name) { const params = { programId: this.programId, environmentId: this.environmentId, @@ -383,7 +383,7 @@ class CloudSdkAPI { let errMessage = response.statusText; try { errMessage = await response.text(); - } catch (err) {} + } catch (err) { } if (errMessage) { switch (errMessage) { From 83144ed474eb239cacb76768731415108abddb38 Mon Sep 17 00:00:00 2001 From: rliechti Date: Wed, 26 Mar 2025 09:54:34 +0100 Subject: [PATCH 17/67] linting --- src/lib/cloud-sdk-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 5dcf883..8af6ebd 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -383,7 +383,7 @@ class CloudSdkAPI { let errMessage = response.statusText; try { errMessage = await response.text(); - } catch (err) { } + } catch (err) {} if (errMessage) { switch (errMessage) { From 15e1fa304b2cde3a6862c15553300f76d967d472 Mon Sep 17 00:00:00 2001 From: rliechti Date: Wed, 9 Apr 2025 13:04:46 +0200 Subject: [PATCH 18/67] rename keep-deployment to only-mutable-content --- src/commands/aem/rde/snapshot/apply.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index 9dd6f80..6481737 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -27,7 +27,7 @@ class ApplySnapshots extends BaseCommand { this.spinnerStart(`Applying snapshot ${args.name}...`); response = await this.withCloudSdk((cloudSdkAPI) => cloudSdkAPI.applySnapshot(args.name, { - 'keep-deployment': flags['keep-deployment'], + 'only-mutable-content': flags['only-mutable-content'], }) ); } catch (err) { @@ -74,10 +74,8 @@ Object.assign(ApplySnapshots, { ], aliases: [], flags: { - 'keep-deployment': Flags.boolean({ - description: - 'Keeps the deployment of the RDE and applies the content only.', - char: 'k', + 'only-mutable-content': Flags.boolean({ + description: 'Applies the mutable content only.', multiple: false, required: false, default: false, From 6574720d0fede0a5f677cbd818012034129d68ae Mon Sep 17 00:00:00 2001 From: rliechti Date: Wed, 9 Apr 2025 13:49:18 +0200 Subject: [PATCH 19/67] added handling for 503 on delete snapshot --- src/commands/aem/rde/snapshot/delete.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 42d909e..d371007 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -97,6 +97,8 @@ class DeleteSnapshots extends BaseCommand { ) { throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); } + } else if (response?.status === 503) { + throw new snapshotCodes.INVALID_STATE(); } else { throw new internalCodes.UNKNOWN(); } @@ -116,7 +118,7 @@ Object.assign(DeleteSnapshots, { aliases: [], flags: { all: Flags.boolean({ - description: 'Wipe all snapshots from the organization.', + description: 'Mark all snapshots as deleted.', char: 'a', multiple: false, required: false, From 4d09aebbc8774a3684926b7e2e5eb75476503857 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 8 May 2025 09:04:41 +0200 Subject: [PATCH 20/67] support node 22.15+ --- package-lock.json | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4bb760a..6ace822 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "source-map-support": "^0.5.21" }, "engines": { - "node": "^16.13 || ^18 || ^20", + "node": "^16.13 || ^18 || ^20 || >= 22.15 <23", "npm": ">= 8.0.0" } }, diff --git a/package.json b/package.json index 46106d2..38452c6 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Adobe Inc.", "engines": { "npm": ">= 8.0.0", - "node": "^16.13 || ^18 || ^20" + "node": "^16.13 || ^18 || ^20 || >= 22.15 <23" }, "dependencies": { "@adobe/aio-lib-cloudmanager": "^3.1.0", @@ -85,4 +85,4 @@ } } } -} +} \ No newline at end of file From a77a1b29e469e66b4f184c2aad28c0d1a07d24d1 Mon Sep 17 00:00:00 2001 From: Abhishek Garg Date: Fri, 16 May 2025 11:52:30 +0530 Subject: [PATCH 21/67] SKYOPS-84421 reading from metadata files --- src/commands/aem/rde/snapshot/apply.js | 2 ++ src/commands/aem/rde/snapshot/delete.js | 11 +++-------- src/lib/snapshot-errors.js | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index 6481737..10b0464 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -46,6 +46,8 @@ class ApplySnapshots extends BaseCommand { ); } else if (response?.status === 400) { throw new configurationCodes.DIFFERENT_ENV_TYPE(); + } else if (response?.status === 403) { + throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); } else if (response?.status === 404) { const json = await response.json(); if ( diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index d371007..56e467f 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -23,9 +23,9 @@ const chalk = require('chalk'); class DeleteSnapshots extends BaseCommand { async runCommand(args, flags) { if (flags.all) { - this.deleteAllSnapshots(); + await this.deleteAllSnapshots(); } else { - this.deleteSnapshot(args.name, flags.force); + await this.deleteSnapshot(args.name, flags.force); } } @@ -91,12 +91,7 @@ class DeleteSnapshots extends BaseCommand { throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); } } else if (response?.status === 403) { - const json = await response.json(); - if ( - json.details === "The snapshot to be wiped is not in state 'removed'." - ) { - throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); - } + throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); } else if (response?.status === 503) { throw new snapshotCodes.INVALID_STATE(); } else { diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js index b655889..7062733 100644 --- a/src/lib/snapshot-errors.js +++ b/src/lib/snapshot-errors.js @@ -61,5 +61,5 @@ E( ); E( 'SNAPSHOT_WRONG_STATE', - 'Snapshot is in wrong state. Must be in state "REMOVED" to be able to wipe.' + 'The snapshot is in deleted state, change the state to available before applying or deleting.' ); From 5bdced0cb68955e7930c20995d8d7130c7f6fe03 Mon Sep 17 00:00:00 2001 From: Abhishek Garg Date: Fri, 16 May 2025 14:39:11 +0530 Subject: [PATCH 22/67] SKYOPS-84421 reading from metadata files --- src/commands/aem/rde/snapshot/apply.js | 4 ++-- src/commands/aem/rde/snapshot/delete.js | 11 ++++++++--- src/lib/snapshot-errors.js | 3 ++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index 10b0464..baa2009 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -46,8 +46,6 @@ class ApplySnapshots extends BaseCommand { ); } else if (response?.status === 400) { throw new configurationCodes.DIFFERENT_ENV_TYPE(); - } else if (response?.status === 403) { - throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); } else if (response?.status === 404) { const json = await response.json(); if ( @@ -56,6 +54,8 @@ class ApplySnapshots extends BaseCommand { throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); } else if (json.details === 'The requested snapshot does not exist.') { throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else if (json.details === 'The snapshot is in deleted state.') { + throw new snapshotCodes.SNAPSHOT_DELETED(); } } else if (response?.status === 406) { throw new snapshotCodes.INVALID_STATE(); diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 56e467f..d371007 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -23,9 +23,9 @@ const chalk = require('chalk'); class DeleteSnapshots extends BaseCommand { async runCommand(args, flags) { if (flags.all) { - await this.deleteAllSnapshots(); + this.deleteAllSnapshots(); } else { - await this.deleteSnapshot(args.name, flags.force); + this.deleteSnapshot(args.name, flags.force); } } @@ -91,7 +91,12 @@ class DeleteSnapshots extends BaseCommand { throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); } } else if (response?.status === 403) { - throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); + const json = await response.json(); + if ( + json.details === "The snapshot to be wiped is not in state 'removed'." + ) { + throw new snapshotCodes.SNAPSHOT_WRONG_STATE(); + } } else if (response?.status === 503) { throw new snapshotCodes.INVALID_STATE(); } else { diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js index 7062733..ac59a0e 100644 --- a/src/lib/snapshot-errors.js +++ b/src/lib/snapshot-errors.js @@ -61,5 +61,6 @@ E( ); E( 'SNAPSHOT_WRONG_STATE', - 'The snapshot is in deleted state, change the state to available before applying or deleting.' + 'Snapshot is in wrong state. Must be in state "REMOVED" to be able to wipe.' ); +E('SNAPSHOT_DELETED', 'The snapshot is in deleted state, change the state to available before applying.'); From 73a8e79e0696a6f76304da3c39a7c56c9dcf4d10 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 08:00:07 +0200 Subject: [PATCH 23/67] format snapshot size on table --- src/commands/aem/rde/snapshot/index.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index 0a0e21e..25d8351 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -63,8 +63,29 @@ class ListSnapshots extends BaseCommand { } logInTableFormat(items) { + // Helper to format bytes to MB or GB + function formatSize(bytes) { + if (typeof bytes !== 'number' || isNaN(bytes)) return ''; + const gb = 1024 * 1024 * 1024; + const mb = 1024 * 1024; + if (bytes >= gb) { + return (bytes / gb).toFixed(2) + ' GB'; + } else if (bytes >= mb) { + return (bytes / mb).toFixed(2) + ' MB'; + } + return bytes + ' B'; + } + + const mappedItems = items.map((item) => { + const sizeBytes = item.size?.total_size ?? item.size; + return { + ...item, + size: formatSize(sizeBytes), + }; + }); + cli.table( - items, + mappedItems, { name: { minWidth: 20, From eb077b7d62c741e2242bac7b735dae1f4c43fdd2 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 08:00:25 +0200 Subject: [PATCH 24/67] add multi spinners plugin --- package-lock.json | 106 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 ++- 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ace822..6bea035 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,8 @@ "inquirer-autocomplete-prompt": "^2.0.1", "jsonwebtoken": "^9.0.2", "open": "^7.0.0", - "ora": "^5.4.1" + "ora": "^5.4.1", + "spinnies": "^0.5.1" }, "bin": { "adobe-aem-rde-cli": "bin/run" @@ -10441,6 +10442,109 @@ "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, + "node_modules/spinnies": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/spinnies/-/spinnies-0.5.1.tgz", + "integrity": "sha512-WpjSXv9NQz0nU3yCT9TFEOfpFrXADY9C5fG6eAJqixLhvTX1jP3w92Y8IE5oafIe42nlF9otjhllnXN/QCaB3A==", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "cli-cursor": "^3.0.0", + "strip-ansi": "^5.2.0" + } + }, + "node_modules/spinnies/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/spinnies/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/spinnies/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/spinnies/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/spinnies/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/spinnies/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/spinnies/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/spinnies/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/spinnies/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", diff --git a/package.json b/package.json index 38452c6..102188d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "inquirer-autocomplete-prompt": "^2.0.1", "jsonwebtoken": "^9.0.2", "open": "^7.0.0", - "ora": "^5.4.1" + "ora": "^5.4.1", + "spinnies": "^0.5.1" }, "devDependencies": { "@adobe/eslint-config-aio-lib-config": "^4.0.0", @@ -85,4 +86,4 @@ } } } -} \ No newline at end of file +} From 17bcaa5c30594e023405d48248923ca53fba1275 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 08:00:38 +0200 Subject: [PATCH 25/67] add format elapsed time function --- src/lib/base-command.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib/base-command.js b/src/lib/base-command.js index a5b5a62..6e76233 100644 --- a/src/lib/base-command.js +++ b/src/lib/base-command.js @@ -296,6 +296,17 @@ class BaseCommand extends Command { }; return result; } + + formatElapsedTime(startTime, endTime) { + const ms = endTime - startTime; + if (ms < 1000) { + return `${ms}ms`; + } else if (ms < 60000) { + return `${(ms / 1000).toFixed(2)}s`; + } else { + return `${(ms / 60000).toFixed(2)}m`; + } + } } Object.assign(BaseCommand, { From 774ec5b823540e7a9663634f07b8b86e5c3fc9d9 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 08:13:36 +0200 Subject: [PATCH 26/67] initial version of apply snapshot with user feedback --- src/commands/aem/rde/snapshot/apply.js | 199 ++++++++++++++++++++----- src/lib/cloud-sdk-api.js | 13 +- 2 files changed, 176 insertions(+), 36 deletions(-) diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index baa2009..dfae234 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -14,54 +14,175 @@ const { BaseCommand, Flags } = require('../../../../lib/base-command'); const { codes: snapshotCodes } = require('../../../../lib/snapshot-errors'); const { codes: internalCodes } = require('../../../../lib/internal-errors'); +const { codes: validationCodes } = require('../../../../lib/validation-errors'); const { codes: configurationCodes, } = require('../../../../lib/configuration-errors'); const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); +const { sleepMillis } = require('../../../../lib/utils'); +const { loadAllArtifacts } = require('../../../../lib/rde-utils'); + +const Spinnies = require('spinnies'); + class ApplySnapshots extends BaseCommand { async runCommand(args, flags) { - let response; - try { - this.spinnerStart(`Applying snapshot ${args.name}...`); - response = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.applySnapshot(args.name, { - 'only-mutable-content': flags['only-mutable-content'], - }) - ); - } catch (err) { - this.spinnerStop(); - throwAioError( - err, - new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + const spinnies = new Spinnies(); + + if (!flags.status) { + spinnies.add('spinner-requesting', { + text: `Requesting to apply snapshot ${args.name} (<1m) ...`, + }); + } + spinnies.add('spinner-backend', { + text: 'Waiting for backend to pick up the job to apply the snapshot (<1min) ...', + }); + spinnies.add('spinner-apply', { + text: 'Applying snapshot to RDE (2-5m) ...', + }); + spinnies.add('spinner-restart', { + text: 'Wait for the RDE to restart (5-10m)...', + }); + + const result = this.jsonResult(); + const startTime = Date.now(); + result.startTime = startTime; + if (!flags.status) { + let response; + try { + response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.applySnapshot(args.name, { + 'only-mutable-content': flags['only-mutable-content'], + }) + ); + } catch (err) { + result.error = err; + spinnies.stopAll('fail'); + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } + + if (response?.status === 200) { + const took = this.formatElapsedTime(startTime, Date.now()); + spinnies.succeed('spinner-requesting', { + text: `Requested to apply snapshot successfully. (${took})`, + successColor: 'greenBright', + }); + } else if (response?.status === 400) { + throw new configurationCodes.DIFFERENT_ENV_TYPE(); + } else if (response?.status === 404) { + const json = await response.json(); + if ( + json.details === + 'The requested environment or program does not exist.' + ) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (json.details === 'The requested snapshot does not exist.') { + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else if (json.details === 'The snapshot is in deleted state.') { + throw new snapshotCodes.SNAPSHOT_DELETED(); + } + } else if (response?.status === 406) { + throw new snapshotCodes.INVALID_STATE(); + } else if (response?.status === 503) { + throw new validationCodes.DEPLOYMENT_IN_PROGRESS(); + } else { + spinnies.stopAll('fail'); + throw new internalCodes.UNKNOWN(); + } + } + + let lastProgress = -1; + result.waitingforbackend = new Date(); + + while (lastProgress < 100) { + let progressResponse; + try { + progressResponse = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.getSnapshotProgress('apply', args.name) + ); + } catch (err) { + result.error = err; + spinnies.stopAll('fail'); + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } + + if (progressResponse.status === 200) { + const json = await progressResponse.json(); + lastProgress = json?.progressPercentage; + } else if (progressResponse.status === 404) { + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else { + spinnies.stopAll('fail'); + this.doLog('Could not get the progress of the snapshot application.'); + throw new internalCodes.UNKNOWN(); + } + + if (lastProgress > 0) { + const took = this.formatElapsedTime( + result.waitingforbackend, + Date.now() + ); + spinnies.succeed('spinner-backend', { + text: `Backend picked up the job to apply the snapshot. (${took})`, + successColor: 'greenBright', + }); + result.processnigsnapshotstarted = new Date(); + } + await sleepMillis(5000); + } + + if (lastProgress === 100) { + const took = this.formatElapsedTime( + result.processnigsnapshotstarted, + Date.now() ); + spinnies.succeed('spinner-apply', { + text: `Applied snapshot to RDE successfully. (${took})`, + successColor: 'greenBright', + }); } - this.spinnerStop(); - if (response?.status === 200) { - this.doLog( - chalk.green( - `Snapshot ${args.name} applied successfully. Use 'aio aem rde status' to view installed artifacts.` - ) + result.processnigsnapshotended = new Date(); + + while (true) { + const status = await this.withCloudSdk((cloudSdkAPI) => + loadAllArtifacts(cloudSdkAPI) ); - } else if (response?.status === 400) { - throw new configurationCodes.DIFFERENT_ENV_TYPE(); - } else if (response?.status === 404) { - const json = await response.json(); - if ( - json.details === 'The requested environment or program does not exist.' - ) { - throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); - } else if (json.details === 'The requested snapshot does not exist.') { - throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); - } else if (json.details === 'The snapshot is in deleted state.') { - throw new snapshotCodes.SNAPSHOT_DELETED(); + if (status.status === 'Ready') { + break; } - } else if (response?.status === 406) { - throw new snapshotCodes.INVALID_STATE(); - } else { - throw new internalCodes.UNKNOWN(); + await sleepMillis(10000); } + + const took = this.formatElapsedTime( + result.processnigsnapshotended, + Date.now() + ); + spinnies.succeed('spinner-restart', { + text: `RDE restarted successfully. (${took})`, + successColor: 'greenBright', + }); + + this.doLog( + chalk.green( + `Snapshot ${args.name} applied successfully. Check the deployment using the command: 'aio aem rde status'` + ) + ); + + result.endTime = Date.now(); + this.doLog( + chalk.yellow( + `Total time to rebase on snapshot: ${this.formatElapsedTime(startTime, result.endTime)}` + ) + ); + result.totalseconds = (result.endTime - startTime) / 1000; + return result; } } @@ -90,6 +211,14 @@ Object.assign(ApplySnapshots, { required: false, default: false, }), + status: Flags.boolean({ + description: + 'Checks the progress of the snapshot application. If the snapshot is already applied, it will return the progress.', + char: 's', + multiple: false, + required: false, + default: false, + }), }, }); diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 8af6ebd..fc5bf1b 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -187,6 +187,17 @@ class CloudSdkAPI { ); } + async getSnapshotProgress(action, snapshotName) { + const params = { + programId: this.programId, + environmentId: this.environmentId, + 'snapshot-name': snapshotName, + action, + }; + const queryString = this.createUrlQueryStr(params); + return await this._snapshotClient.doPut(`${queryString}`); + } + async getSnapshots() { const params = { programId: this.programId, @@ -383,7 +394,7 @@ class CloudSdkAPI { let errMessage = response.statusText; try { errMessage = await response.text(); - } catch (err) {} + } catch (err) { } if (errMessage) { switch (errMessage) { From cbc4b82b306aa1dfaed239b1864fb01de9459062 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 09:15:58 +0200 Subject: [PATCH 27/67] create snapshot with user feedback --- src/commands/aem/rde/snapshot/apply.js | 37 ++++--- src/commands/aem/rde/snapshot/create.js | 133 ++++++++++++++++++++++-- 2 files changed, 147 insertions(+), 23 deletions(-) diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index dfae234..60b569f 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -28,20 +28,20 @@ const Spinnies = require('spinnies'); class ApplySnapshots extends BaseCommand { async runCommand(args, flags) { - const spinnies = new Spinnies(); + const spinnies = flags.quiet || flags.json ? undefined : new Spinnies(); if (!flags.status) { - spinnies.add('spinner-requesting', { + spinnies?.add('spinner-requesting', { text: `Requesting to apply snapshot ${args.name} (<1m) ...`, }); } - spinnies.add('spinner-backend', { + spinnies?.add('spinner-backend', { text: 'Waiting for backend to pick up the job to apply the snapshot (<1min) ...', }); - spinnies.add('spinner-apply', { + spinnies?.add('spinner-apply', { text: 'Applying snapshot to RDE (2-5m) ...', }); - spinnies.add('spinner-restart', { + spinnies?.add('spinner-restart', { text: 'Wait for the RDE to restart (5-10m)...', }); @@ -58,7 +58,7 @@ class ApplySnapshots extends BaseCommand { ); } catch (err) { result.error = err; - spinnies.stopAll('fail'); + spinnies?.stopAll('fail'); throwAioError( err, new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) @@ -67,11 +67,12 @@ class ApplySnapshots extends BaseCommand { if (response?.status === 200) { const took = this.formatElapsedTime(startTime, Date.now()); - spinnies.succeed('spinner-requesting', { - text: `Requested to apply snapshot successfully. (${took})`, + spinnies?.succeed('spinner-requesting', { + text: `Requested to apply the snapshot successfully. (${took})`, successColor: 'greenBright', }); } else if (response?.status === 400) { + spinnies?.stopAll('fail'); throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { const json = await response.json(); @@ -79,18 +80,23 @@ class ApplySnapshots extends BaseCommand { json.details === 'The requested environment or program does not exist.' ) { + spinnies?.stopAll('fail'); throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); } else if (json.details === 'The requested snapshot does not exist.') { + spinnies?.stopAll('fail'); throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); } else if (json.details === 'The snapshot is in deleted state.') { + spinnies?.stopAll('fail'); throw new snapshotCodes.SNAPSHOT_DELETED(); } } else if (response?.status === 406) { + spinnies?.stopAll('fail'); throw new snapshotCodes.INVALID_STATE(); } else if (response?.status === 503) { + spinnies?.stopAll('fail'); throw new validationCodes.DEPLOYMENT_IN_PROGRESS(); } else { - spinnies.stopAll('fail'); + spinnies?.stopAll('fail'); throw new internalCodes.UNKNOWN(); } } @@ -106,7 +112,7 @@ class ApplySnapshots extends BaseCommand { ); } catch (err) { result.error = err; - spinnies.stopAll('fail'); + spinnies?.stopAll('fail'); throwAioError( err, new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) @@ -117,19 +123,20 @@ class ApplySnapshots extends BaseCommand { const json = await progressResponse.json(); lastProgress = json?.progressPercentage; } else if (progressResponse.status === 404) { + spinnies?.stopAll('fail'); throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); } else { - spinnies.stopAll('fail'); + spinnies?.stopAll('fail'); this.doLog('Could not get the progress of the snapshot application.'); throw new internalCodes.UNKNOWN(); } - if (lastProgress > 0) { + if (lastProgress > 0 && !result.processnigsnapshotstarted) { const took = this.formatElapsedTime( result.waitingforbackend, Date.now() ); - spinnies.succeed('spinner-backend', { + spinnies?.succeed('spinner-backend', { text: `Backend picked up the job to apply the snapshot. (${took})`, successColor: 'greenBright', }); @@ -143,7 +150,7 @@ class ApplySnapshots extends BaseCommand { result.processnigsnapshotstarted, Date.now() ); - spinnies.succeed('spinner-apply', { + spinnies?.succeed('spinner-apply', { text: `Applied snapshot to RDE successfully. (${took})`, successColor: 'greenBright', }); @@ -164,7 +171,7 @@ class ApplySnapshots extends BaseCommand { result.processnigsnapshotended, Date.now() ); - spinnies.succeed('spinner-restart', { + spinnies?.succeed('spinner-restart', { text: `RDE restarted successfully. (${took})`, successColor: 'greenBright', }); diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 8196101..9b69309 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -20,43 +20,160 @@ const { const { throwAioError } = require('../../../../lib/error-helpers'); const chalk = require('chalk'); +const { sleepMillis } = require('../../../../lib/utils'); +const { loadAllArtifacts } = require('../../../../lib/rde-utils'); + +const Spinnies = require('spinnies'); + class CreateSnapshots extends BaseCommand { async runCommand(args, flags) { + const spinnies = flags.quiet || flags.json ? undefined : new Spinnies(); + spinnies?.add('spinner-requesting', { + text: `Requesting to create snapshot ${args.name} (<1m) ...`, + }); + spinnies?.add('spinner-backend', { + text: 'Waiting for backend to pick up the job to create the snapshot (<1min) ...', + }); + spinnies?.add('spinner-create', { + text: 'Locking RDE and create the snapshot (2-5m) ...', + }); + spinnies?.add('spinner-restart', { + text: 'Unlocking the RDE (1-2m)...', + }); + + const result = this.jsonResult(); + const startTime = Date.now(); + result.startTime = startTime; + let response; try { - this.spinnerStart(`Creating snapshot ${args.name}...`); response = await this.withCloudSdk((cloudSdkAPI) => cloudSdkAPI.createSnapshot(args.name, { description: flags.description, }) ); } catch (err) { - this.spinnerStop(); + spinnies?.stopAll('fail'); throwAioError( err, new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) ); } - this.spinnerStop(); if (response?.status === 200 || response?.status === 201) { - this.doLog( - chalk.green( - `Snapshot ${args.name} created successfully. Use 'aio aem rde snapshot' to view or 'aio aem rde snapshot apply ${args.name}' to apply it on the RDE.` - ) - ); + const took = this.formatElapsedTime(startTime, Date.now()); + spinnies?.succeed('spinner-requesting', { + text: `Requested to create the snapshot successfully. (${took})`, + successColor: 'greenBright', + }); } else if (response?.status === 400) { + spinnies?.stopAll('fail'); throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { + spinnies?.stopAll('fail'); throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); } else if (response?.status === 409) { + spinnies?.stopAll('fail'); throw new snapshotCodes.ALREADY_EXISTS(); } else if (response?.status === 503) { + spinnies?.stopAll('fail'); throw new snapshotCodes.INVALID_STATE(); } else if (response?.status === 507) { + spinnies?.stopAll('fail'); throw new snapshotCodes.SNAPSHOT_LIMIT(); } else { + spinnies?.stopAll('fail'); throw new internalCodes.UNKNOWN(); } + + let lastProgress = -1; + result.waitingforbackend = new Date(); + + while (lastProgress < 100) { + let progressResponse; + try { + progressResponse = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.getSnapshotProgress('create', args.name) + ); + } catch (err) { + result.error = err; + spinnies?.stopAll('fail'); + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } + + if (progressResponse.status === 200) { + const json = await progressResponse.json(); + lastProgress = json?.progressPercentage; + } else if (progressResponse.status === 404) { + spinnies?.stopAll('fail'); + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else { + spinnies?.stopAll('fail'); + this.doLog('Could not get the progress of the snapshot creation.'); + spinnies?.stopAll('fail'); + throw new internalCodes.UNKNOWN(); + } + + if (lastProgress > 0 && !result.processnigsnapshotstarted) { + const took = this.formatElapsedTime( + result.waitingforbackend, + Date.now() + ); + spinnies?.succeed('spinner-backend', { + text: `Backend picked up the job to create the snapshot. (${took})`, + successColor: 'greenBright', + }); + result.processnigsnapshotstarted = new Date(); + } + await sleepMillis(5000); + } + + if (lastProgress === 100) { + const took = this.formatElapsedTime( + result.processnigsnapshotstarted, + Date.now() + ); + spinnies?.succeed('spinner-create', { + text: `Created snapshot successfully. (${took})`, + successColor: 'greenBright', + }); + } + result.processnigsnapshotended = new Date(); + + while (true) { + const status = await this.withCloudSdk((cloudSdkAPI) => + loadAllArtifacts(cloudSdkAPI) + ); + if (status.status === 'Ready') { + break; + } + await sleepMillis(10000); + } + + const took = this.formatElapsedTime( + result.processnigsnapshotended, + Date.now() + ); + spinnies?.succeed('spinner-restart', { + text: `RDE unlocked successfully. (${took})`, + successColor: 'greenBright', + }); + + this.doLog( + chalk.green( + `Snapshot ${args.name} created successfully. Check the list of snapshots using the command: 'aio aem rde snapshot'` + ) + ); + + result.endTime = Date.now(); + this.doLog( + chalk.yellow( + `Total time to create the snapshot: ${this.formatElapsedTime(startTime, result.endTime)}` + ) + ); + result.totalseconds = (result.endTime - startTime) / 1000; } } From cdb62328e1ed92ebe14abe3f331f995c0f6ab88c Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 10:09:53 +0200 Subject: [PATCH 28/67] support proper json output --- src/commands/aem/rde/snapshot/apply.js | 5 +++-- src/commands/aem/rde/snapshot/create.js | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/apply.js index 60b569f..cb757cd 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/apply.js @@ -182,12 +182,13 @@ class ApplySnapshots extends BaseCommand { ) ); - result.endTime = Date.now(); + result.endTime = new Date(); this.doLog( chalk.yellow( - `Total time to rebase on snapshot: ${this.formatElapsedTime(startTime, result.endTime)}` + `Total time to rebase on snapshot: ${this.formatElapsedTime(startTime, Date.now())}` ) ); + result.startTime = new Date(startTime); result.totalseconds = (result.endTime - startTime) / 1000; return result; } diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 9b69309..9d79c01 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -167,13 +167,15 @@ class CreateSnapshots extends BaseCommand { ) ); - result.endTime = Date.now(); + result.endTime = new Date(); this.doLog( chalk.yellow( - `Total time to create the snapshot: ${this.formatElapsedTime(startTime, result.endTime)}` + `Total time to create the snapshot: ${this.formatElapsedTime(startTime, Date.now())}` ) ); result.totalseconds = (result.endTime - startTime) / 1000; + result.startTime = new Date(startTime); + return result; } } From ead9c23e7519d5d3110546492449c85fed4407b9 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 11:10:51 +0200 Subject: [PATCH 29/67] rename apply to restore snapshot --- src/commands/aem/rde/snapshot/delete.js | 20 ++++++--- .../aem/rde/snapshot/{apply.js => restore.js} | 45 ++++++++----------- src/commands/aem/rde/snapshot/undelete.js | 4 +- src/lib/cloud-sdk-api.js | 6 +-- src/lib/doRequest.js | 4 ++ src/lib/snapshot-errors.js | 7 ++- 6 files changed, 46 insertions(+), 40 deletions(-) rename src/commands/aem/rde/snapshot/{apply.js => restore.js} (81%) diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index d371007..346ee57 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -51,7 +51,7 @@ class DeleteSnapshots extends BaseCommand { if (json?.items?.length === 0) { this.doLog('There are no snapshots yet.'); } else { - json?.items.forEach((e) => this.deleteSnapshot(e.name)); + json?.forEach((e) => this.deleteSnapshot(e.name, this.flags.force)); } } else { throw new internalCodes.UNKNOWN(); @@ -74,11 +74,19 @@ class DeleteSnapshots extends BaseCommand { } this.spinnerStop(); if (response?.status === 200 || response?.status === 201) { - this.doLog( - chalk.green( - `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio aem rde snapshot undelete ${name}' to undelete it.` - ) - ); + if (this.flags.force) { + this.doLog( + chalk.green( + `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to vlaidate its removal.` + ) + ); + } else { + this.doLog( + chalk.green( + `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to view its updated state, it will be removed once the retention time has passed. Use 'aio aem rde snapshot undelete ${name}' to undelete it.` + ) + ); + } } else if (response?.status === 400) { throw new configurationCodes.DIFFERENT_ENV_TYPE(); } else if (response?.status === 404) { diff --git a/src/commands/aem/rde/snapshot/apply.js b/src/commands/aem/rde/snapshot/restore.js similarity index 81% rename from src/commands/aem/rde/snapshot/apply.js rename to src/commands/aem/rde/snapshot/restore.js index cb757cd..fdd7f13 100644 --- a/src/commands/aem/rde/snapshot/apply.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -26,20 +26,20 @@ const { loadAllArtifacts } = require('../../../../lib/rde-utils'); const Spinnies = require('spinnies'); -class ApplySnapshots extends BaseCommand { +class RestoreSnapshot extends BaseCommand { async runCommand(args, flags) { const spinnies = flags.quiet || flags.json ? undefined : new Spinnies(); if (!flags.status) { spinnies?.add('spinner-requesting', { - text: `Requesting to apply snapshot ${args.name} (<1m) ...`, + text: `Requesting to restore snapshot ${args.name} (<1m) ...`, }); } spinnies?.add('spinner-backend', { - text: 'Waiting for backend to pick up the job to apply the snapshot (<1min) ...', + text: 'Waiting for backend to pick up the job to restore the snapshot (<1min) ...', }); - spinnies?.add('spinner-apply', { - text: 'Applying snapshot to RDE (2-5m) ...', + spinnies?.add('spinner-restore', { + text: 'Restoring snapshot to RDE (2-5m) ...', }); spinnies?.add('spinner-restart', { text: 'Wait for the RDE to restart (5-10m)...', @@ -52,7 +52,7 @@ class ApplySnapshots extends BaseCommand { let response; try { response = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.applySnapshot(args.name, { + cloudSdkAPI.restoreSnapshot(args.name, { 'only-mutable-content': flags['only-mutable-content'], }) ); @@ -68,7 +68,7 @@ class ApplySnapshots extends BaseCommand { if (response?.status === 200) { const took = this.formatElapsedTime(startTime, Date.now()); spinnies?.succeed('spinner-requesting', { - text: `Requested to apply the snapshot successfully. (${took})`, + text: `Requested to restore the snapshot successfully. (${took})`, successColor: 'greenBright', }); } else if (response?.status === 400) { @@ -108,7 +108,7 @@ class ApplySnapshots extends BaseCommand { let progressResponse; try { progressResponse = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.getSnapshotProgress('apply', args.name) + cloudSdkAPI.getSnapshotProgress('restore', args.name) ); } catch (err) { result.error = err; @@ -137,7 +137,7 @@ class ApplySnapshots extends BaseCommand { Date.now() ); spinnies?.succeed('spinner-backend', { - text: `Backend picked up the job to apply the snapshot. (${took})`, + text: `Backend picked up the job to restore the snapshot. (${took})`, successColor: 'greenBright', }); result.processnigsnapshotstarted = new Date(); @@ -150,8 +150,8 @@ class ApplySnapshots extends BaseCommand { result.processnigsnapshotstarted, Date.now() ); - spinnies?.succeed('spinner-apply', { - text: `Applied snapshot to RDE successfully. (${took})`, + spinnies?.succeed('spinner-restore', { + text: `Restored snapshot to RDE successfully. (${took})`, successColor: 'greenBright', }); } @@ -178,7 +178,7 @@ class ApplySnapshots extends BaseCommand { this.doLog( chalk.green( - `Snapshot ${args.name} applied successfully. Check the deployment using the command: 'aio aem rde status'` + `Snapshot ${args.name} restored successfully. Check the deployment using the command: 'aio aem rde status'` ) ); @@ -194,34 +194,25 @@ class ApplySnapshots extends BaseCommand { } } -Object.assign(ApplySnapshots, { - description: 'Applies a snapshot to the environment.', +Object.assign(RestoreSnapshot, { + description: 'Restores a snapshot to the environment.', args: [ { name: 'name', - description: 'The name of the snapshot to apply to the current RDE.', + description: 'The name of the snapshot to restore to the current RDE.', required: true, }, ], aliases: [], flags: { 'only-mutable-content': Flags.boolean({ - description: 'Applies the mutable content only.', - multiple: false, - required: false, - default: false, - }), - quiet: Flags.boolean({ - description: - 'Does not ask for user confirmation before applying the snapshot.', - char: 'q', + description: 'Restores the mutable content only.', multiple: false, required: false, default: false, }), status: Flags.boolean({ - description: - 'Checks the progress of the snapshot application. If the snapshot is already applied, it will return the progress.', + description: 'Checks the progress of the snapshot restore.', char: 's', multiple: false, required: false, @@ -230,4 +221,4 @@ Object.assign(ApplySnapshots, { }, }); -module.exports = ApplySnapshots; +module.exports = RestoreSnapshot; diff --git a/src/commands/aem/rde/snapshot/undelete.js b/src/commands/aem/rde/snapshot/undelete.js index ae850e2..e513c6a 100644 --- a/src/commands/aem/rde/snapshot/undelete.js +++ b/src/commands/aem/rde/snapshot/undelete.js @@ -38,7 +38,7 @@ class UndeleteSnapshots extends BaseCommand { if (response?.status === 200) { this.doLog( chalk.green( - `Snapshot ${args.name} undeleted successfully. Use 'aio aem rde snapshot' to view its updated state. Use 'aio aem rde snapshot apply ${args.name}' to apply it on the RDE.` + `Snapshot ${args.name} undeleted successfully. Use 'aio aem rde snapshot' to view its updated state. Use 'aio aem rde snapshot restore ${args.name}' to restore it on the RDE.` ) ); } else if (response?.status === 400) { @@ -65,7 +65,7 @@ Object.assign(UndeleteSnapshots, { args: [ { name: 'name', - description: 'The name of the snapshot to apply to the undeleted.', + description: 'The name of the snapshot to undelete.', required: true, }, ], diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index fc5bf1b..22b8717 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -195,7 +195,7 @@ class CloudSdkAPI { action, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPut(`${queryString}`); + return await this._snapshotClient.doOptions(`${queryString}`); } async getSnapshots() { @@ -237,14 +237,14 @@ class CloudSdkAPI { return await this._snapshotClient.doPost(`${queryString}`); } - async applySnapshot(name, params) { + async restoreSnapshot(name, params) { params = { ...params, programId: this.programId, environmentId: this.environmentId, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPost(`/${name}/apply${queryString}`); + return await this._snapshotClient.doPost(`/${name}/restore${queryString}`); } async getLogs(id) { diff --git a/src/lib/doRequest.js b/src/lib/doRequest.js index 3316d33..4aac391 100644 --- a/src/lib/doRequest.js +++ b/src/lib/doRequest.js @@ -53,6 +53,10 @@ class DoRequest { return this.do('put', path, body); } + async doOptions(path, body) { + return this.do('options', path, body); + } + async doPatch(path, body) { return this.do('patch', path, body); } diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js index ac59a0e..8994309 100644 --- a/src/lib/snapshot-errors.js +++ b/src/lib/snapshot-errors.js @@ -51,7 +51,7 @@ module.exports = { // Define your error codes with the wrapper E( 'INVALID_STATE', - 'The RDE is not in a state where a snapshot can be created or applied' + 'The RDE is not in a state where a snapshot can be created or restored.' ); E('ALREADY_EXISTS', 'A snapshot with the given name already exists'); E('SNAPSHOT_NOT_FOUND', 'The snapshot does not exist'); @@ -63,4 +63,7 @@ E( 'SNAPSHOT_WRONG_STATE', 'Snapshot is in wrong state. Must be in state "REMOVED" to be able to wipe.' ); -E('SNAPSHOT_DELETED', 'The snapshot is in deleted state, change the state to available before applying.'); +E( + 'SNAPSHOT_DELETED', + 'The snapshot is in deleted state, change the state to available before restoring.' +); From 4761467fa28568515a264bd8e15ab1d71daa9b89 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 13:52:26 +0200 Subject: [PATCH 30/67] add wait for read option on status command --- src/commands/aem/rde/status.js | 42 ++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/commands/aem/rde/status.js b/src/commands/aem/rde/status.js index 36c19b3..007a404 100644 --- a/src/commands/aem/rde/status.js +++ b/src/commands/aem/rde/status.js @@ -11,18 +11,42 @@ */ 'use strict'; -const { BaseCommand, commonFlags } = require('../../../lib/base-command'); +const { + BaseCommand, + commonFlags, + Flags, +} = require('../../../lib/base-command'); const { loadAllArtifacts, groupArtifacts } = require('../../../lib/rde-utils'); const { codes: internalCodes } = require('../../../lib/internal-errors'); const { throwAioError } = require('../../../lib/error-helpers'); +const { sleepMillis } = require('../../../lib/utils'); + class StatusCommand extends BaseCommand { async runCommand(args, flags) { try { this.doLog(`Info for cm-p${this._programId}-e${this._environmentId}`); - this.spinnerStart('retrieving environment status information'); - const status = await this.withCloudSdk((cloudSdkAPI) => - loadAllArtifacts(cloudSdkAPI) - ); + + let status; + if (flags?.wait) { + this.spinnerStart( + 'retrieving environment status information - waiting for ready state' + ); + while (true) { + status = await this.withCloudSdk((cloudSdkAPI) => + loadAllArtifacts(cloudSdkAPI) + ); + if (status.status === 'Ready') { + break; + } + await sleepMillis(10000); + } + } else { + this.spinnerStart('retrieving environment status information'); + status = await this.withCloudSdk((cloudSdkAPI) => + loadAllArtifacts(cloudSdkAPI) + ); + } + this.spinnerStop(); this.doLog(`Environment: ${status.status}`, true); const result = this.jsonResult(status.status); @@ -87,10 +111,18 @@ Object.assign(StatusCommand, { programId: commonFlags.programId, environmentId: commonFlags.environmentId, quiet: commonFlags.quiet, + wait: Flags.boolean({ + description: 'Wait for the environment to be ready', + char: 'w', + multiple: false, + required: false, + default: false, + }), }, usage: [ 'status # output as textual content', 'status --json # output as json object', + 'status --wait # wait for the environment to be ready', ], aliases: [], }); From 4e1cf55acd396c71a239370d82151ee4a244a1fb Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 22 May 2025 14:49:02 +0200 Subject: [PATCH 31/67] support desktop notifications for long running tasks --- package-lock.json | 27 ++++++++++++++++ package.json | 1 + src/commands/aem/rde/reset.js | 2 ++ src/commands/aem/rde/restart.js | 1 + src/commands/aem/rde/setup.js | 41 +++++++++++++++++++++++- src/commands/aem/rde/snapshot/create.js | 8 +++++ src/commands/aem/rde/snapshot/restore.js | 7 ++++ src/commands/aem/rde/status.js | 1 + src/lib/base-command.js | 14 +++++++- src/lib/snapshot-errors.js | 8 +++++ 10 files changed, 108 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6bea035..e67f7cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "inquirer": "^8.2.6", "inquirer-autocomplete-prompt": "^2.0.1", "jsonwebtoken": "^9.0.2", + "node-notifier": "^10.0.1", "open": "^7.0.0", "ora": "^5.4.1", "spinnies": "^0.5.1" @@ -6093,6 +6094,12 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", + "license": "MIT" + }, "node_modules/halfred": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/halfred/-/halfred-2.0.0.tgz", @@ -8506,6 +8513,20 @@ "optional": true, "peer": true }, + "node_modules/node-notifier": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", + "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==", + "license": "MIT", + "dependencies": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.5", + "shellwords": "^0.1.1", + "uuid": "^8.3.2", + "which": "^2.0.2" + } + }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -10223,6 +10244,12 @@ "node": ">=8" } }, + "node_modules/shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "license": "MIT" + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", diff --git a/package.json b/package.json index 102188d..83b1a3b 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "inquirer": "^8.2.6", "inquirer-autocomplete-prompt": "^2.0.1", "jsonwebtoken": "^9.0.2", + "node-notifier": "^10.0.1", "open": "^7.0.0", "ora": "^5.4.1", "spinnies": "^0.5.1" diff --git a/src/commands/aem/rde/reset.js b/src/commands/aem/rde/reset.js index 80ec661..98a819c 100644 --- a/src/commands/aem/rde/reset.js +++ b/src/commands/aem/rde/reset.js @@ -33,9 +33,11 @@ class ResetCommand extends BaseCommand { if (status === 'ready') { result.status = 'reset'; this.doLog(`Environment reset.`); + this.notify('reset', 'RDE environment is reset.'); } else if (status === 'reset_failed') { result.status = 'reset_failed'; this.doLog(`Failed to reset the environment.`); + this.notify('reset failed', 'RDE environment failed to reset.'); } } else { result.status = 'resetting'; diff --git a/src/commands/aem/rde/restart.js b/src/commands/aem/rde/restart.js index 0b95c55..9b4d6d2 100644 --- a/src/commands/aem/rde/restart.js +++ b/src/commands/aem/rde/restart.js @@ -25,6 +25,7 @@ class RestartCommand extends BaseCommand { this.spinnerStop(); result.status = 'restarted'; this.doLog(`Environment restarted.`); + this.notify('restarted', 'RDE environment is restarted.'); return result; } catch (err) { this.spinnerStop(); diff --git a/src/commands/aem/rde/setup.js b/src/commands/aem/rde/setup.js index f21db4f..833bca4 100644 --- a/src/commands/aem/rde/setup.js +++ b/src/commands/aem/rde/setup.js @@ -265,7 +265,21 @@ class SetupCommand extends BaseCommand { } async runCommand(args, flags) { - if (flags.show) { + if (flags['enable-notifications']) { + Config.set('rde_enableNotifications', true); + this.notify( + 'RDE notifictions enabled', + 'Notifications enabled for long running tasks.' + ); + return; + } else if (flags['disable-notifications']) { + Config.set('rde_enableNotifications', false); + this.notify( + 'RDE notifictions disabled', + 'You will no longer receive notifications for long running tasks.' + ); + return; + } else if (flags.show) { const orgId = Config.get(CONFIG_ORG); const programId = Config.get(CONFIG_PROGRAM); const programName = Config.get(CONFIG_PROGRAM_NAME); @@ -307,6 +321,17 @@ class SetupCommand extends BaseCommand { }, ]); + const { enableNotifications } = await inquirer.prompt([ + { + type: 'confirm', + name: 'enableNotifications', + message: + 'Do you want to enable desktop notifications for long running tasks, such as reset, snapshot handling and more?', + default: true, + }, + ]); + Config.set('rde_enableNotifications', enableNotifications, storeLocal); + const orgId = await this.getOrgId(); const prevOrgId = Config.get(CONFIG_ORG); Config.set(CONFIG_ORG, orgId, storeLocal); @@ -436,6 +461,20 @@ Object.assign(SetupCommand, { required: false, default: false, }), + 'enable-notifications': Flags.boolean({ + description: 'Enables desktop notifications for long-running tasks.', + char: 'e', + multiple: false, + required: false, + default: false, + }), + 'disable-notifications': Flags.boolean({ + description: 'Disables desktop notifications for long-running tasks.', + char: 'd', + multiple: false, + required: false, + default: false, + }), }, }); diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 9d79c01..380e9e9 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -127,6 +127,13 @@ class CreateSnapshots extends BaseCommand { }); result.processnigsnapshotstarted = new Date(); } + if (lastProgress === -2) { + spinnies?.stopAll('fail'); + this.doLog(chalk.red('Snapshot creation failed.')); + this.notify('failed', 'Snapshot creation failed.'); + throw new snapshotCodes.SNAPSHOT_CREATION_FAILED(); + } + await sleepMillis(5000); } @@ -175,6 +182,7 @@ class CreateSnapshots extends BaseCommand { ); result.totalseconds = (result.endTime - startTime) / 1000; result.startTime = new Date(startTime); + this.notify('restored', 'Snapshot created.'); return result; } } diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index fdd7f13..d2da515 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -142,6 +142,12 @@ class RestoreSnapshot extends BaseCommand { }); result.processnigsnapshotstarted = new Date(); } + if (lastProgress === -2) { + spinnies?.stopAll('fail'); + this.doLog(chalk.red('Snapshot creation failed.')); + this.notify('failed', 'Snapshot creation failed.'); + throw new snapshotCodes.SNAPSHOT_RESTORE_FAILED(); + } await sleepMillis(5000); } @@ -190,6 +196,7 @@ class RestoreSnapshot extends BaseCommand { ); result.startTime = new Date(startTime); result.totalseconds = (result.endTime - startTime) / 1000; + this.notify('restored', 'Snapshot restored.'); return result; } } diff --git a/src/commands/aem/rde/status.js b/src/commands/aem/rde/status.js index 007a404..68bff39 100644 --- a/src/commands/aem/rde/status.js +++ b/src/commands/aem/rde/status.js @@ -40,6 +40,7 @@ class StatusCommand extends BaseCommand { } await sleepMillis(10000); } + this.notify('ready', 'RDE environment is ready'); } else { this.spinnerStart('retrieving environment status information'); status = await this.withCloudSdk((cloudSdkAPI) => diff --git a/src/lib/base-command.js b/src/lib/base-command.js index 6e76233..47655fb 100644 --- a/src/lib/base-command.js +++ b/src/lib/base-command.js @@ -16,6 +16,7 @@ const jwt = require('jsonwebtoken'); const inquirer = require('inquirer'); const spinner = require('ora')(); const chalk = require('chalk'); +const notifier = require('node-notifier'); // Adobe dependencies const { getToken, context } = require('@adobe/aio-lib-ims'); @@ -110,8 +111,12 @@ class BaseCommand extends Command { handleError(err, this.error); } + rdeIdentification() { + return `${concatEnvironemntId(this._programId, this._environmentId)}${this.printNamesWhenAvailable()}`; + } + getLogHeader() { - return `Running ${!this.id ? this.constructor.name : this.id} on ${concatEnvironemntId(this._programId, this._environmentId)}${this.printNamesWhenAvailable()}`; + return `Running ${!this.id ? this.constructor.name : this.id} on ${this.rdeIdentification()}`; } printNamesWhenAvailable() { @@ -141,6 +146,13 @@ class BaseCommand extends Command { spinner.stop(); } + notify(title, message) { + if (Config.get('rde_enableNotifications')) { + title = `${this.rdeIdentification()} - ${title}`; + notifier.notify({ title, message }); + } + } + /** * */ diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js index 8994309..4d73075 100644 --- a/src/lib/snapshot-errors.js +++ b/src/lib/snapshot-errors.js @@ -67,3 +67,11 @@ E( 'SNAPSHOT_DELETED', 'The snapshot is in deleted state, change the state to available before restoring.' ); +E( + 'SNAPSHOT_CREATION_FAILED', + 'The snapshot failed to be created. Please contact support.' +); +E( + 'SNAPSHOT_RESTORE_FAILED', + 'The snapshot failed to be restored. Please try again. When this still happens, reset the RDE and try again. Otherwise contact support.' +); \ No newline at end of file From f3cb47c2a2300b9c558398ec4c9a582d7bf39de9 Mon Sep 17 00:00:00 2001 From: rliechti Date: Fri, 23 May 2025 09:37:16 +0200 Subject: [PATCH 32/67] notification improvements --- src/commands/aem/rde/setup.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/commands/aem/rde/setup.js b/src/commands/aem/rde/setup.js index 833bca4..02d5d23 100644 --- a/src/commands/aem/rde/setup.js +++ b/src/commands/aem/rde/setup.js @@ -273,11 +273,11 @@ class SetupCommand extends BaseCommand { ); return; } else if (flags['disable-notifications']) { - Config.set('rde_enableNotifications', false); this.notify( 'RDE notifictions disabled', 'You will no longer receive notifications for long running tasks.' ); + Config.set('rde_enableNotifications', false); return; } else if (flags.show) { const orgId = Config.get(CONFIG_ORG); @@ -462,14 +462,16 @@ Object.assign(SetupCommand, { default: false, }), 'enable-notifications': Flags.boolean({ - description: 'Enables desktop notifications for long-running tasks.', + description: + 'Enables desktop notifications for long-running tasks (global aio config).', char: 'e', multiple: false, required: false, default: false, }), 'disable-notifications': Flags.boolean({ - description: 'Disables desktop notifications for long-running tasks.', + description: + 'Disables desktop notifications for long-running tasks (global aio config).', char: 'd', multiple: false, required: false, From 8a83adb1bdfe8558450fde4314e998094a8c6a0c Mon Sep 17 00:00:00 2001 From: rliechti Date: Fri, 23 May 2025 15:07:14 +0200 Subject: [PATCH 33/67] relocate progress to env status --- src/commands/aem/rde/snapshot/create.js | 2 +- src/commands/aem/rde/snapshot/restore.js | 2 +- src/lib/cloud-sdk-api.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 380e9e9..d3db251 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -92,7 +92,7 @@ class CreateSnapshots extends BaseCommand { let progressResponse; try { progressResponse = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.getSnapshotProgress('create', args.name) + cloudSdkAPI.getSnapshotProgress('snapshot_create', args.name) ); } catch (err) { result.error = err; diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index d2da515..c534847 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -108,7 +108,7 @@ class RestoreSnapshot extends BaseCommand { let progressResponse; try { progressResponse = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.getSnapshotProgress('restore', args.name) + cloudSdkAPI.getSnapshotProgress('snapshot_restore', args.name) ); } catch (err) { result.error = err; diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 22b8717..8647f1d 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -195,7 +195,7 @@ class CloudSdkAPI { action, }; const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doOptions(`${queryString}`); + return await this._rdeClient.doGet(`/runtime/status${queryString}`); } async getSnapshots() { From 2ba9e6c002a68772f0a54c9d17fc6fa0e867eed0 Mon Sep 17 00:00:00 2001 From: rliechti Date: Wed, 28 May 2025 08:07:25 +0200 Subject: [PATCH 34/67] linting --- src/lib/cloud-sdk-api.js | 2 +- src/lib/snapshot-errors.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 8647f1d..4c6ef7d 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -394,7 +394,7 @@ class CloudSdkAPI { let errMessage = response.statusText; try { errMessage = await response.text(); - } catch (err) { } + } catch (err) {} if (errMessage) { switch (errMessage) { diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js index 4d73075..9a9bd42 100644 --- a/src/lib/snapshot-errors.js +++ b/src/lib/snapshot-errors.js @@ -74,4 +74,4 @@ E( E( 'SNAPSHOT_RESTORE_FAILED', 'The snapshot failed to be restored. Please try again. When this still happens, reset the RDE and try again. Otherwise contact support.' -); \ No newline at end of file +); From ff7d7d732dce48efdb2058e0283490c21c3edae8 Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 2 Jun 2025 11:17:37 +0200 Subject: [PATCH 35/67] add action id and EAP for snapshotting --- src/commands/aem/rde/snapshot/create.js | 13 ++- src/commands/aem/rde/snapshot/delete.js | 8 +- src/commands/aem/rde/snapshot/index.js | 4 +- src/commands/aem/rde/snapshot/restore.js | 108 +++++++++++----------- src/commands/aem/rde/snapshot/undelete.js | 4 +- src/lib/cloud-sdk-api.js | 5 +- src/lib/configuration-errors.js | 4 + src/lib/doRequest.js | 3 +- 8 files changed, 85 insertions(+), 64 deletions(-) diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index d3db251..a233f47 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -59,7 +59,12 @@ class CreateSnapshots extends BaseCommand { new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) ); } - if (response?.status === 200 || response?.status === 201) { + let actionid; + if (response?.status === 451) { + throw new configurationCodes.NON_EAP(); + } else if (response?.status === 200 || response?.status === 201) { + const json = await response.json(); + actionid = json?.actionid; const took = this.formatElapsedTime(startTime, Date.now()); spinnies?.succeed('spinner-requesting', { text: `Requested to create the snapshot successfully. (${took})`, @@ -92,7 +97,11 @@ class CreateSnapshots extends BaseCommand { let progressResponse; try { progressResponse = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.getSnapshotProgress('snapshot_create', args.name) + cloudSdkAPI.getSnapshotProgress( + 'snapshot_create', + args.name, + actionid + ) ); } catch (err) { result.error = err; diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 346ee57..1b917c3 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -45,7 +45,9 @@ class DeleteSnapshots extends BaseCommand { this.spinnerStop(); } - if (response.status === 200) { + if (response?.status === 451) { + throw new configurationCodes.NON_EAP(); + } else if (response.status === 200) { const json = await response.json(); this.spinnerStop(); if (json?.items?.length === 0) { @@ -73,7 +75,9 @@ class DeleteSnapshots extends BaseCommand { ); } this.spinnerStop(); - if (response?.status === 200 || response?.status === 201) { + if (response?.status === 451) { + throw new configurationCodes.NON_EAP(); + } else if (response?.status === 200 || response?.status === 201) { if (this.flags.force) { this.doLog( chalk.green( diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index 25d8351..4b1d185 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -42,7 +42,9 @@ class ListSnapshots extends BaseCommand { this.spinnerStop(); } - if (response.status === 200) { + if (response?.status === 451) { + throw new configurationCodes.NON_EAP(); + } else if (response.status === 200) { const json = await response.json(); result.status = json?.status; this.spinnerStop(); diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index c534847..4541927 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -48,57 +48,58 @@ class RestoreSnapshot extends BaseCommand { const result = this.jsonResult(); const startTime = Date.now(); result.startTime = startTime; - if (!flags.status) { - let response; - try { - response = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.restoreSnapshot(args.name, { - 'only-mutable-content': flags['only-mutable-content'], - }) - ); - } catch (err) { - result.error = err; - spinnies?.stopAll('fail'); - throwAioError( - err, - new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) - ); - } - - if (response?.status === 200) { - const took = this.formatElapsedTime(startTime, Date.now()); - spinnies?.succeed('spinner-requesting', { - text: `Requested to restore the snapshot successfully. (${took})`, - successColor: 'greenBright', - }); - } else if (response?.status === 400) { - spinnies?.stopAll('fail'); - throw new configurationCodes.DIFFERENT_ENV_TYPE(); - } else if (response?.status === 404) { - const json = await response.json(); - if ( - json.details === - 'The requested environment or program does not exist.' - ) { - spinnies?.stopAll('fail'); - throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); - } else if (json.details === 'The requested snapshot does not exist.') { - spinnies?.stopAll('fail'); - throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); - } else if (json.details === 'The snapshot is in deleted state.') { - spinnies?.stopAll('fail'); - throw new snapshotCodes.SNAPSHOT_DELETED(); - } - } else if (response?.status === 406) { + let response; + try { + response = await this.withCloudSdk((cloudSdkAPI) => + cloudSdkAPI.restoreSnapshot(args.name, { + 'only-mutable-content': flags['only-mutable-content'], + }) + ); + } catch (err) { + result.error = err; + spinnies?.stopAll('fail'); + throwAioError( + err, + new internalCodes.INTERNAL_SNAPSHOT_ERROR({ messageValues: err }) + ); + } + let actionid; + if (response?.status === 451) { + throw new configurationCodes.NON_EAP(); + } else if (response?.status === 200) { + const json = await response.json(); + actionid = json?.actionid; + const took = this.formatElapsedTime(startTime, Date.now()); + spinnies?.succeed('spinner-requesting', { + text: `Requested to restore the snapshot successfully. (${took})`, + successColor: 'greenBright', + }); + } else if (response?.status === 400) { + spinnies?.stopAll('fail'); + throw new configurationCodes.DIFFERENT_ENV_TYPE(); + } else if (response?.status === 404) { + const json = await response.json(); + if ( + json.details === 'The requested environment or program does not exist.' + ) { spinnies?.stopAll('fail'); - throw new snapshotCodes.INVALID_STATE(); - } else if (response?.status === 503) { + throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); + } else if (json.details === 'The requested snapshot does not exist.') { spinnies?.stopAll('fail'); - throw new validationCodes.DEPLOYMENT_IN_PROGRESS(); - } else { + throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); + } else if (json.details === 'The snapshot is in deleted state.') { spinnies?.stopAll('fail'); - throw new internalCodes.UNKNOWN(); + throw new snapshotCodes.SNAPSHOT_DELETED(); } + } else if (response?.status === 406) { + spinnies?.stopAll('fail'); + throw new snapshotCodes.INVALID_STATE(); + } else if (response?.status === 503) { + spinnies?.stopAll('fail'); + throw new validationCodes.DEPLOYMENT_IN_PROGRESS(); + } else { + spinnies?.stopAll('fail'); + throw new internalCodes.UNKNOWN(); } let lastProgress = -1; @@ -108,7 +109,11 @@ class RestoreSnapshot extends BaseCommand { let progressResponse; try { progressResponse = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.getSnapshotProgress('snapshot_restore', args.name) + cloudSdkAPI.getSnapshotProgress( + 'snapshot_restore', + args.name, + actionid + ) ); } catch (err) { result.error = err; @@ -218,13 +223,6 @@ Object.assign(RestoreSnapshot, { required: false, default: false, }), - status: Flags.boolean({ - description: 'Checks the progress of the snapshot restore.', - char: 's', - multiple: false, - required: false, - default: false, - }), }, }); diff --git a/src/commands/aem/rde/snapshot/undelete.js b/src/commands/aem/rde/snapshot/undelete.js index e513c6a..b29df1b 100644 --- a/src/commands/aem/rde/snapshot/undelete.js +++ b/src/commands/aem/rde/snapshot/undelete.js @@ -35,7 +35,9 @@ class UndeleteSnapshots extends BaseCommand { ); } this.spinnerStop(); - if (response?.status === 200) { + if (response?.status === 451) { + throw new configurationCodes.NON_EAP(); + } else if (response?.status === 200) { this.doLog( chalk.green( `Snapshot ${args.name} undeleted successfully. Use 'aio aem rde snapshot' to view its updated state. Use 'aio aem rde snapshot restore ${args.name}' to restore it on the RDE.` diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 4c6ef7d..18e26f6 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -187,12 +187,13 @@ class CloudSdkAPI { ); } - async getSnapshotProgress(action, snapshotName) { + async getSnapshotProgress(action, snapshotName, actionid) { const params = { programId: this.programId, environmentId: this.environmentId, 'snapshot-name': snapshotName, action, + actionid, }; const queryString = this.createUrlQueryStr(params); return await this._rdeClient.doGet(`/runtime/status${queryString}`); @@ -394,7 +395,7 @@ class CloudSdkAPI { let errMessage = response.statusText; try { errMessage = await response.text(); - } catch (err) {} + } catch (err) { } if (errMessage) { switch (errMessage) { diff --git a/src/lib/configuration-errors.js b/src/lib/configuration-errors.js index bd9f048..9fef050 100644 --- a/src/lib/configuration-errors.js +++ b/src/lib/configuration-errors.js @@ -107,3 +107,7 @@ E( 'PROGRAM_OR_ENVIRONMENT_NOT_FOUND', 'The environment or program does not exist' ); +E( + 'NON_EAP', + 'The feature is part of the EAP program and not available for general use. Please contact your Adobe representative for more information on how to join the early access program.' +); diff --git a/src/lib/doRequest.js b/src/lib/doRequest.js index 4aac391..da12589 100644 --- a/src/lib/doRequest.js +++ b/src/lib/doRequest.js @@ -33,7 +33,8 @@ class DoRequest { (response) => response && ((response.status >= 200 && response.status < 300) || - response.status === 404), + response.status === 404 || + response.status === 451), // 451 Unavailable For Legal Reasons, EAP early access), 1, 5 ); From 5fe44436e74107ca3452b9f76342deac78067158 Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 2 Jun 2025 11:57:25 +0200 Subject: [PATCH 36/67] lint --- src/lib/cloud-sdk-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 18e26f6..e60d8f0 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -395,7 +395,7 @@ class CloudSdkAPI { let errMessage = response.statusText; try { errMessage = await response.text(); - } catch (err) { } + } catch (err) {} if (errMessage) { switch (errMessage) { From ecfcbd06a50cf4e32bab77a43b3e97b6d7ebe335 Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 2 Jun 2025 15:49:22 +0200 Subject: [PATCH 37/67] unittests --- src/commands/aem/rde/snapshot/delete.js | 2 +- src/commands/aem/rde/snapshot/index.js | 4 +- test/commands/aem/rde/snapshot/index.test.js | 157 +++++++++++++++++++ 3 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 test/commands/aem/rde/snapshot/index.test.js diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 1b917c3..903bef8 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -81,7 +81,7 @@ class DeleteSnapshots extends BaseCommand { if (this.flags.force) { this.doLog( chalk.green( - `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to vlaidate its removal.` + `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to validate its removal.` ) ); } else { diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index 4b1d185..6ba7e84 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -46,9 +46,9 @@ class ListSnapshots extends BaseCommand { throw new configurationCodes.NON_EAP(); } else if (response.status === 200) { const json = await response.json(); - result.status = json?.status; + result.status = response.status; this.spinnerStop(); - if (json?.items?.length === 0) { + if (json?.length === 0) { this.doLog('There are no snapshots yet.'); } else { result.snapshots = json; diff --git a/test/commands/aem/rde/snapshot/index.test.js b/test/commands/aem/rde/snapshot/index.test.js new file mode 100644 index 0000000..0d51811 --- /dev/null +++ b/test/commands/aem/rde/snapshot/index.test.js @@ -0,0 +1,157 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const ListSnapshots = require('../../../../../src/commands/aem/rde/snapshot'); +const internalErrors = require('../../../../../src/lib/internal-errors'); +const configErrors = require('../../../../../src/lib/configuration-errors'); +const errorHelpers = require('../../../../../src/lib/error-helpers'); + +/** + * + * @param sinon + * @param command + * @param methods + */ +function createCloudSdkAPIStub(sinon, command, methods) { + const cloudSdkApiStub = {}; + Object.keys(methods).forEach((k) => { + cloudSdkApiStub[k] = methods[k]; + }); + sinon + .stub(command, 'withCloudSdk') + .callsFake(async (fn) => fn(cloudSdkApiStub)); + return [command, cloudSdkApiStub]; +} + +/** + * + * @param sinon + * @param command + */ +function setupLogCapturing(sinon, command) { + const logs = []; + sinon.stub(command, 'doLog').callsFake((msg) => logs.push(msg)); + command.log = { getCapturedLogOutput: () => logs.join('\n') }; +} + +describe('ListSnapshots', function () { + describe('#runCommand', function () { + let command, cloudSdkApiStub; + + const stubbedSnapshotsResponse = { + status: 200, + json: async () => [ + { + name: 'snap1', + description: 'desc1', + usage: 1, + size: { total_size: 1048576 }, + state: 'AVAILABLE', + created: '2024-06-01T12:00:00Z', + lastUsed: '2024-06-02T12:00:00Z', + }, + { + name: 'snap2', + description: 'desc2', + usage: 2, + size: { total_size: 1073741824 }, + state: 'DELETED', + created: '2024-06-03T12:00:00Z', + lastUsed: '2024-06-04T12:00:00Z', + }, + ], + }; + + const stubbedEmptySnapshotsResponse = { + status: 200, + json: async () => [], + }; + + beforeEach(() => { + command = new ListSnapshots([], {}); + [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { + getSnapshots: sinon.stub().resolves(stubbedSnapshotsResponse), + }); + sinon.stub(command, 'spinnerStart'); + sinon.stub(command, 'spinnerStop'); + setupLogCapturing(sinon, command); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('calls getSnapshots and logs table output for non-empty items', async function () { + await command.runCommand([], {}); + expect(cloudSdkApiStub.getSnapshots.calledOnce).to.be.true; + const output = command.log.getCapturedLogOutput(); + expect(output).to.include('snap1'); + expect(output).to.include('snap2'); + expect(output).to.include('1.00 MB'); + expect(output).to.include('1.00 GB'); + }); + + it('logs "There are no snapshots yet." for empty items', async function () { + cloudSdkApiStub.getSnapshots.resolves(stubbedEmptySnapshotsResponse); + await command.runCommand([], {}); + expect(command.log.getCapturedLogOutput()).to.include( + 'There are no snapshots yet.' + ); + }); + + it('throws DIFFERENT_ENV_TYPE error for 400 status', async function () { + cloudSdkApiStub.getSnapshots.resolves({ status: 400 }); + try { + await command.runCommand([], {}); + expect.fail('Should throw DIFFERENT_ENV_TYPE'); + } catch (e) { + expect(e).to.be.instanceOf(configErrors.codes.DIFFERENT_ENV_TYPE); + } + }); + + it('throws PROGRAM_OR_ENVIRONMENT_NOT_FOUND error for 404 status', async function () { + cloudSdkApiStub.getSnapshots.resolves({ status: 404 }); + try { + await command.runCommand([], {}); + expect.fail('Should throw PROGRAM_OR_ENVIRONMENT_NOT_FOUND'); + } catch (e) { + expect(e).to.be.instanceOf( + configErrors.codes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND + ); + } + }); + + it('throws UNKNOWN error for unexpected status', async function () { + cloudSdkApiStub.getSnapshots.resolves({ status: 500 }); + try { + await command.runCommand([], {}); + expect.fail('Should throw UNKNOWN'); + } catch (e) { + expect(e).to.be.instanceOf(internalErrors.codes.UNKNOWN); + } + }); + + it('throws INTERNAL_SNAPSHOT_ERROR if getSnapshots throws', async function () { + cloudSdkApiStub.getSnapshots.rejects(new Error('fail')); + sinon.stub(errorHelpers, 'throwAioError').throws( + new internalErrors.codes.INTERNAL_SNAPSHOT_ERROR({ + messageValues: 'fail', + }) + ); + try { + await command.runCommand([], {}); + expect.fail('Should throw INTERNAL_SNAPSHOT_ERROR'); + } catch (e) { + expect(e).to.be.instanceOf( + internalErrors.codes.INTERNAL_SNAPSHOT_ERROR + ); + } + }); + + it('returns result object with status and snapshots', async function () { + const result = await command.runCommand([], {}); + expect(result.status).to.equal(200); + expect(result.snapshots).to.exist; + expect(result.snapshots).to.be.an('array'); + }); + }); +}); From 0c6d71579fe665a6566f3ee2fa5cf7acb5794091 Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 12 Jun 2025 08:57:35 +0200 Subject: [PATCH 38/67] add sorting for usage --- src/commands/aem/rde/snapshot/index.js | 14 +++++++++++--- test/commands/aem/rde/snapshot/create.test.js | 0 test/commands/aem/rde/snapshot/delete.test.js | 0 test/commands/aem/rde/snapshot/restore.test.js | 0 test/commands/aem/rde/snapshot/undelete.test.js | 0 5 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 test/commands/aem/rde/snapshot/create.test.js create mode 100644 test/commands/aem/rde/snapshot/delete.test.js create mode 100644 test/commands/aem/rde/snapshot/restore.test.js create mode 100644 test/commands/aem/rde/snapshot/undelete.test.js diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index 6ba7e84..b0fb8a6 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -52,7 +52,7 @@ class ListSnapshots extends BaseCommand { this.doLog('There are no snapshots yet.'); } else { result.snapshots = json; - this.logInTableFormat(json); + this.logInTableFormat(json, flags); } } else if (response?.status === 400) { throw new configurationCodes.DIFFERENT_ENV_TYPE(); @@ -64,7 +64,7 @@ class ListSnapshots extends BaseCommand { return result; } - logInTableFormat(items) { + logInTableFormat(items, flags) { // Helper to format bytes to MB or GB function formatSize(bytes) { if (typeof bytes !== 'number' || isNaN(bytes)) return ''; @@ -78,7 +78,7 @@ class ListSnapshots extends BaseCommand { return bytes + ' B'; } - const mappedItems = items.map((item) => { + let mappedItems = items.map((item) => { const sizeBytes = item.size?.total_size ?? item.size; return { ...item, @@ -86,6 +86,14 @@ class ListSnapshots extends BaseCommand { }; }); + if (flags.usage) { + mappedItems = mappedItems.sort((a, b) => { + const usageA = a.usage ?? 0; + const usageB = b.usage ?? 0; + return usageB - usageA; // Sort by usage, most used first + }); + } + cli.table( mappedItems, { diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js new file mode 100644 index 0000000..e69de29 diff --git a/test/commands/aem/rde/snapshot/delete.test.js b/test/commands/aem/rde/snapshot/delete.test.js new file mode 100644 index 0000000..e69de29 diff --git a/test/commands/aem/rde/snapshot/restore.test.js b/test/commands/aem/rde/snapshot/restore.test.js new file mode 100644 index 0000000..e69de29 diff --git a/test/commands/aem/rde/snapshot/undelete.test.js b/test/commands/aem/rde/snapshot/undelete.test.js new file mode 100644 index 0000000..e69de29 From 782c50a23d36bb1d3650b7eb788b8cf416b5e2a2 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 12:14:19 +0200 Subject: [PATCH 39/67] Add basic create test --- src/commands/aem/rde/snapshot/create.js | 15 +- test/commands/aem/rde/snapshot/create.test.js | 181 ++++++++++++++++++ 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index a233f47..2453102 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -26,8 +26,13 @@ const { loadAllArtifacts } = require('../../../../lib/rde-utils'); const Spinnies = require('spinnies'); class CreateSnapshots extends BaseCommand { + constructor(argv, config, sleepTime = 5000) { + super(argv, config); + this.sleepTime = sleepTime; + } + async runCommand(args, flags) { - const spinnies = flags.quiet || flags.json ? undefined : new Spinnies(); + const spinnies = this.getSpinnies(flags); spinnies?.add('spinner-requesting', { text: `Requesting to create snapshot ${args.name} (<1m) ...`, }); @@ -143,7 +148,7 @@ class CreateSnapshots extends BaseCommand { throw new snapshotCodes.SNAPSHOT_CREATION_FAILED(); } - await sleepMillis(5000); + await sleepMillis(this.sleepTime); } if (lastProgress === 100) { @@ -165,7 +170,7 @@ class CreateSnapshots extends BaseCommand { if (status.status === 'Ready') { break; } - await sleepMillis(10000); + await sleepMillis(this.sleepTime * 2); } const took = this.formatElapsedTime( @@ -194,6 +199,10 @@ class CreateSnapshots extends BaseCommand { this.notify('restored', 'Snapshot created.'); return result; } + + getSpinnies(flags) { + return flags.quiet || flags.json ? undefined : new Spinnies(); + } } Object.assign(CreateSnapshots, { diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index e69de29..49f184f 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -0,0 +1,181 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const CreateSnapshot = require('../../../../../src/commands/aem/rde/snapshot/create'); +const Spinnies = require('spinnies'); +const assert = require('assert'); + +/** + * + * @param sinon + * @param command + * @param methods + */ +function createCloudSdkAPIStub(sinon, command, methods) { + const cloudSdkApiStub = {}; + Object.keys(methods).forEach((k) => { + cloudSdkApiStub[k] = methods[k]; + }); + sinon + .stub(command, 'withCloudSdk') + .callsFake(async (fn) => fn(cloudSdkApiStub)); + return [command, cloudSdkApiStub]; +} + +/** + * + * @param sinon + * @param command + */ +function createSpinniesStub(sinon, command) { + const spinnies = new Spinnies(); + const addSpy = sinon.spy(spinnies, 'add'); + const stopAllSpy = sinon.spy(spinnies, 'stopAll'); + const succeedSpy = sinon.spy(spinnies, 'succeed'); + sinon.stub(command, 'getSpinnies').callsFake(() => spinnies); + + return { + addSpy, + stopAllSpy, + succeedSpy, + }; +} + +/** + * + * @param sinon + * @param command + */ +function setupLogCapturing(sinon, command) { + const logs = []; + sinon.stub(command, 'doLog').callsFake((msg) => logs.push(msg)); + command.log = { getCapturedLogOutput: () => logs.join('\n') }; +} + +describe('CreateSnapshot', function () { + describe('#runCommand', function () { + let command, + cloudSdkApiStub, + addSpinniesSpy, + stopAllSpinniesSpy, + succeedSpinniesSpy; + + const stubbedCreateResponse = { + status: 200, + json: async () => ({ + actionid: 1234123, + success: true, + }), + }; + + let counter = 0; + const getSnapshotProgressResponse = { + status: 200, + json: async () => { + if (counter === 0) { + counter++; + return { + action: 'create-snapshot', + progressPercentage: 20, + snapshotName: 'snapshot-001', + }; + } else { + return { + action: 'create-snapshot', + progressPercentage: 100, + snapshotName: 'snapshot-001', + }; + } + }, + }; + + const getArtifactsResponse = { + status: 200, + json: async () => ({ + status: 'Ready', + items: [], + }), + }; + + const stub = (response) => sinon.stub().resolves(response); + + beforeEach(() => { + command = new CreateSnapshot([], {}, 10); + [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { + createSnapshot: stub(stubbedCreateResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse), + getArtifacts: stub(getArtifactsResponse), + }); + const { addSpy, stopAllSpy, succeedSpy } = createSpinniesStub( + sinon, + command + ); + addSpinniesSpy = addSpy; + stopAllSpinniesSpy = stopAllSpy; + succeedSpinniesSpy = succeedSpy; + setupLogCapturing(sinon, command); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('returns result object with status and snapshots', async function () { + const result = await command.runCommand([], {}); + + expect(result.totalseconds).to.be.a('number'); + expect(result.waitingforbackend).to.be.a('date'); + expect(result.startTime).to.be.a('date'); + expect(result.processnigsnapshotstarted).to.be.a('date'); + expect(result.processnigsnapshotended).to.be.a('date'); + + assert.equal(cloudSdkApiStub.createSnapshot.calledOnce, true); + assert.equal(cloudSdkApiStub.getSnapshotProgress.called, true); + assert.equal(cloudSdkApiStub.getArtifacts.called, true); + + expect(addSpinniesSpy.callCount).to.equal(4); + + expect(addSpinniesSpy.getCall(0).args[0]).to.equal('spinner-requesting'); + expect(addSpinniesSpy.getCall(1).args[0]).to.equal('spinner-backend'); + expect(addSpinniesSpy.getCall(2).args[0]).to.equal('spinner-create'); + expect(addSpinniesSpy.getCall(3).args[0]).to.equal('spinner-restart'); + + expect(succeedSpinniesSpy.callCount).to.equal(4); + + const verifySpinnySucceeded = ( + callIndex, + expectedSpinnerName, + compareObjectFn + ) => { + const [spinnerName, obj] = succeedSpinniesSpy.getCall(callIndex).args; + expect(spinnerName).to.equal(expectedSpinnerName); + + assert.equal(compareObjectFn(obj), true); + }; + + verifySpinnySucceeded( + 0, + 'spinner-requesting', + (obj) => + obj.text.startsWith( + 'Requested to create the snapshot successfully.' + ) && obj.successColor === 'greenBright' + ); + + verifySpinnySucceeded( + 1, + 'spinner-backend', + (obj) => + obj.text.startsWith( + 'Backend picked up the job to create the snapshot.' + ) && obj.successColor === 'greenBright' + ); + verifySpinnySucceeded( + 2, + 'spinner-create', + (obj) => + obj.text.startsWith('Created snapshot successfully.') && + obj.successColor === 'greenBright' + ); + }); + }); +}); From 4c8bee988ffd78d6efe58128d78244fdc81f9144 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 13:19:12 +0200 Subject: [PATCH 40/67] wip unit tests --- test/commands/aem/rde/snapshot/create.test.js | 64 ++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index 49f184f..7b83584 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -59,7 +59,7 @@ describe('CreateSnapshot', function () { stopAllSpinniesSpy, succeedSpinniesSpy; - const stubbedCreateResponse = { + const stubbedCreateResponseSuccess = { status: 200, json: async () => ({ actionid: 1234123, @@ -67,6 +67,15 @@ describe('CreateSnapshot', function () { }), }; + const stubbedCreateResponseFailure = (status = 400) => ({ + status, + json: async () => ({ + actionid: 1234123, + success: false, + error: 'Failed to create snapshot', + }), + }); + let counter = 0; const getSnapshotProgressResponse = { status: 200, @@ -98,13 +107,13 @@ describe('CreateSnapshot', function () { const stub = (response) => sinon.stub().resolves(response); - beforeEach(() => { + const prepareStubs = (cloudSdkMethods) => { command = new CreateSnapshot([], {}, 10); - [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { - createSnapshot: stub(stubbedCreateResponse), - getSnapshotProgress: stub(getSnapshotProgressResponse), - getArtifacts: stub(getArtifactsResponse), - }); + [command, cloudSdkApiStub] = createCloudSdkAPIStub( + sinon, + command, + cloudSdkMethods + ); const { addSpy, stopAllSpy, succeedSpy } = createSpinniesStub( sinon, command @@ -113,13 +122,19 @@ describe('CreateSnapshot', function () { stopAllSpinniesSpy = stopAllSpy; succeedSpinniesSpy = succeedSpy; setupLogCapturing(sinon, command); - }); + }; afterEach(() => { sinon.restore(); }); - it('returns result object with status and snapshots', async function () { + it('calls the appropriate api and shows success spinners if the result is good.', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getSnapshotProgress: stub(getSnapshotProgressResponse), + getArtifacts: stub(getArtifactsResponse), + }); + const result = await command.runCommand([], {}); expect(result.totalseconds).to.be.a('number'); @@ -177,5 +192,36 @@ describe('CreateSnapshot', function () { obj.successColor === 'greenBright' ); }); + + it('calls the appropriate api and shows failures', async function () { + const checkError = async (statusCode, expectedMessage) => { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseFailure(statusCode)), + getSnapshotProgress: stub(getSnapshotProgressResponse), + getArtifacts: stub(getArtifactsResponse), + }); + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.include(expectedMessage); + } + expect(stopAllSpinniesSpy.callCount).to.equal(1); + }; + + await checkError(400, 'The given environment is not an RDE'); + await checkError(404, 'The environment or program does not exist'); + await checkError(409, 'A snapshot with the given name already exists'); + await checkError( + 503, + 'The RDE is not in a state where a snapshot can be created or restored.' + ); + await checkError( + 507, + 'Reached the maximum number or diskspace of snapshots. Remove some snapshots and try again' + ); + await checkError(500, 'An unknown error occurred.'); + }); }); }); From 3a7f3a0e1eea617637063de9205a1645c30aa32d Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 13:36:50 +0200 Subject: [PATCH 41/67] Add coverage --- test/commands/aem/rde/snapshot/create.test.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index 7b83584..e285499 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -106,6 +106,7 @@ describe('CreateSnapshot', function () { }; const stub = (response) => sinon.stub().resolves(response); + const stubReject = (message) => sinon.stub().rejects(message); const prepareStubs = (cloudSdkMethods) => { command = new CreateSnapshot([], {}, 10); @@ -193,6 +194,40 @@ describe('CreateSnapshot', function () { ); }); + it('catches a withCloudSdk exception - createSnapshot', async function () { + prepareStubs({ + createSnapshot: stubReject('Internal error'), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain( + 'There was an unexpected error when running a snapshot command.' + ); + } + }); + + it('catches a withCloudSdk exception - getSnapshotProgress', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getSnapshotProgress: stubReject('Internal error'), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain( + 'There was an unexpected error when running a snapshot command.' + ); + } + }); + + it('calls the appropriate api and shows failures', async function () { const checkError = async (statusCode, expectedMessage) => { prepareStubs({ From df548b0702ebe9fe15b8badfe95e9a553eaaa050 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 14:02:13 +0200 Subject: [PATCH 42/67] More coverage --- test/commands/aem/rde/snapshot/create.test.js | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index e285499..80b4c63 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -54,11 +54,16 @@ function setupLogCapturing(sinon, command) { describe('CreateSnapshot', function () { describe('#runCommand', function () { let command, + progressCounter, cloudSdkApiStub, addSpinniesSpy, stopAllSpinniesSpy, succeedSpinniesSpy; + beforeEach(function () { + progressCounter = 0; + }); + const stubbedCreateResponseSuccess = { status: 200, json: async () => ({ @@ -76,25 +81,17 @@ describe('CreateSnapshot', function () { }), }); - let counter = 0; - const getSnapshotProgressResponse = { - status: 200, - json: async () => { - if (counter === 0) { - counter++; - return { - action: 'create-snapshot', - progressPercentage: 20, - snapshotName: 'snapshot-001', - }; - } else { - return { - action: 'create-snapshot', - progressPercentage: 100, - snapshotName: 'snapshot-001', - }; - } - }, + const getSnapshotProgressResponse = (percentages = [20, 100]) => { + const result = { + status: 200, + json: async () => ({ + action: 'create-snapshot', + progressPercentage: percentages[progressCounter], + snapshotName: 'snapshot-001', + }), + }; + progressCounter++; + return result; }; const getArtifactsResponse = { @@ -132,7 +129,7 @@ describe('CreateSnapshot', function () { it('calls the appropriate api and shows success spinners if the result is good.', async function () { prepareStubs({ createSnapshot: stub(stubbedCreateResponseSuccess), - getSnapshotProgress: stub(getSnapshotProgressResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse()), getArtifacts: stub(getArtifactsResponse), }); @@ -194,6 +191,24 @@ describe('CreateSnapshot', function () { ); }); + it('catches a bad progress result (-2)', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getArtifacts: stub(getArtifactsResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, -2])), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain( + 'The snapshot failed to be created. Please contact support.' + ); + } + }); + it('catches a withCloudSdk exception - createSnapshot', async function () { prepareStubs({ createSnapshot: stubReject('Internal error'), @@ -227,12 +242,11 @@ describe('CreateSnapshot', function () { } }); - it('calls the appropriate api and shows failures', async function () { const checkError = async (statusCode, expectedMessage) => { prepareStubs({ createSnapshot: stub(stubbedCreateResponseFailure(statusCode)), - getSnapshotProgress: stub(getSnapshotProgressResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse()), getArtifacts: stub(getArtifactsResponse), }); try { From 7368f623b04a879021720966de9fbe3527d31175 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 14:06:29 +0200 Subject: [PATCH 43/67] More coverage --- test/commands/aem/rde/snapshot/create.test.js | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index 80b4c63..e723426 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -81,9 +81,12 @@ describe('CreateSnapshot', function () { }), }); - const getSnapshotProgressResponse = (percentages = [20, 100]) => { + const getSnapshotProgressResponse = ( + percentages = [20, 100], + status = 200 + ) => { const result = { - status: 200, + status, json: async () => ({ action: 'create-snapshot', progressPercentage: percentages[progressCounter], @@ -209,6 +212,38 @@ describe('CreateSnapshot', function () { } }); + it('catches 404 on progress response', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getArtifacts: stub(getArtifactsResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, 50], 404)), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain('The snapshot does not exist'); + } + }); + + it('catches unknown on progress response', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getArtifacts: stub(getArtifactsResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, 50], 500)), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain('An unknown error occurred.'); + } + }); + it('catches a withCloudSdk exception - createSnapshot', async function () { prepareStubs({ createSnapshot: stubReject('Internal error'), From 856c41cdac954b3aa0913d6676e96aac1ec316c1 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Mon, 7 Jul 2025 10:51:47 +0200 Subject: [PATCH 44/67] add coverage of delete.js --- package-lock.json | 82 ++++-- package.json | 2 +- src/commands/aem/rde/snapshot/delete.js | 12 +- test/commands/aem/rde/snapshot/delete.test.js | 246 ++++++++++++++++++ test/commands/aem/rde/snapshot/index.test.js | 27 +- .../aem/rde/snapshot/snapshots.mocks.js | 30 +++ 6 files changed, 344 insertions(+), 55 deletions(-) create mode 100644 test/commands/aem/rde/snapshot/snapshots.mocks.js diff --git a/package-lock.json b/package-lock.json index e67f7cb..26d814b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "@adobe/eslint-config-aio-lib-config": "^4.0.0", "@inquirer/testing": "^2.1.19", "@oclif/dev-cli": "^1.26.10", - "chai": "^5.1.1", + "chai": "^4.5.0", "chai-as-promised": "^8.0.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", @@ -3258,12 +3258,13 @@ } }, "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": "*" } }, "node_modules/async": { @@ -3740,19 +3741,22 @@ } }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, + "license": "MIT", "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" }, "engines": { - "node": ">=12" + "node": ">=4" } }, "node_modules/chai-as-promised": { @@ -3767,6 +3771,29 @@ "chai": ">= 2.1.2 < 6" } }, + "node_modules/chai/node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chai/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4366,10 +4393,14 @@ } }, "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, "engines": { "node": ">=6" } @@ -5889,6 +5920,7 @@ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -8199,10 +8231,11 @@ } }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, + "license": "MIT", "dependencies": { "get-func-name": "^2.0.1" } @@ -9167,12 +9200,13 @@ } }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 14.16" + "node": "*" } }, "node_modules/picocolors": { diff --git a/package.json b/package.json index 83b1a3b..a58cfe7 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@adobe/eslint-config-aio-lib-config": "^4.0.0", "@inquirer/testing": "^2.1.19", "@oclif/dev-cli": "^1.26.10", - "chai": "^5.1.1", + "chai": "^4.5.0", "chai-as-promised": "^8.0.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 903bef8..6c84ace 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -23,13 +23,13 @@ const chalk = require('chalk'); class DeleteSnapshots extends BaseCommand { async runCommand(args, flags) { if (flags.all) { - this.deleteAllSnapshots(); + await this.deleteAllSnapshots(flags.force); } else { - this.deleteSnapshot(args.name, flags.force); + await this.deleteSnapshot(args.name, flags.force); } } - async deleteAllSnapshots() { + async deleteAllSnapshots(force) { let response; try { this.spinnerStart('fetching all snapshots'); @@ -53,7 +53,9 @@ class DeleteSnapshots extends BaseCommand { if (json?.items?.length === 0) { this.doLog('There are no snapshots yet.'); } else { - json?.forEach((e) => this.deleteSnapshot(e.name, this.flags.force)); + const promises = + json?.map((e) => this.deleteSnapshot(e.name, force)) || []; + await Promise.all(promises); } } else { throw new internalCodes.UNKNOWN(); @@ -78,7 +80,7 @@ class DeleteSnapshots extends BaseCommand { if (response?.status === 451) { throw new configurationCodes.NON_EAP(); } else if (response?.status === 200 || response?.status === 201) { - if (this.flags.force) { + if (force) { this.doLog( chalk.green( `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to validate its removal.` diff --git a/test/commands/aem/rde/snapshot/delete.test.js b/test/commands/aem/rde/snapshot/delete.test.js index e69de29..f4efdd3 100644 --- a/test/commands/aem/rde/snapshot/delete.test.js +++ b/test/commands/aem/rde/snapshot/delete.test.js @@ -0,0 +1,246 @@ +const assert = require('assert'); +const { expect } = require('chai'); +const sinon = require('sinon'); +const DeleteSnapshot = require('../../../../../src/commands/aem/rde/snapshot/delete'); +const { snapshotsResponse, snapshots } = require('./snapshots.mocks'); + +/** + * + * @param sinon + * @param command + * @param methods + */ +function createCloudSdkAPIStub(sinon, command, methods) { + const cloudSdkApiStub = {}; + Object.keys(methods).forEach((k) => { + cloudSdkApiStub[k] = methods[k]; + }); + sinon + .stub(command, 'withCloudSdk') + .callsFake(async (fn) => fn(cloudSdkApiStub)); + return [command, cloudSdkApiStub]; +} + +/** + * + * @param sinon + * @param command + */ +function setupLogCapturing(sinon, command) { + const logs = []; + sinon.stub(command, 'doLog').callsFake((msg) => logs.push(msg)); + command.log = { getCapturedLogOutput: () => logs.join('\n') }; +} + +describe('DeleteSnapshots', function () { + describe('#run commands', function () { + let command, cloudSdkApiStub; + + const stubbedDeleteResponse = (status, details) => ({ + status, + json: async () => ({ + details, + }), + }); + + const stubCommandResponse = (status, details) => { + [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { + getSnapshots: sinon.stub().resolves(snapshotsResponse), + deleteSnapshot: sinon + .stub() + .resolves(stubbedDeleteResponse(status, details)), + }); + }; + + beforeEach(() => { + command = new DeleteSnapshot([], {}); + sinon.stub(command, 'spinnerStart'); + sinon.stub(command, 'spinnerStop'); + setupLogCapturing(sinon, command); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('calls deleteSnapshots successfully', async () => { + stubCommandResponse(200); + + await command.runCommand( + { + name: 'snap1', + }, + { + force: false, + } + ); + expect(cloudSdkApiStub.deleteSnapshot.calledOnce).to.be.true; + const output = command.log.getCapturedLogOutput(); + expect(output).to.include('snap1 deleted successfully'); + + expect(command.spinnerStart.calledOnce).to.be.true; + expect(command.spinnerStop.calledOnce).to.be.true; + expect(cloudSdkApiStub.deleteSnapshot.calledWith('snap1', false)).to.be + .true; + }); + + const executeWithErrorExpected = async ( + snapshotName, + force, + status, + statusMessage, + expectedMessage + ) => { + const isSingle = snapshotName && snapshotName !== 'all'; + stubCommandResponse(status, statusMessage); + try { + const args = {}; + const flags = { + force, + }; + if (isSingle) { + args.name = snapshotName; + } else { + flags.all = true; + } + await command.runCommand(args, flags); + assert.fail('Expected an error to be thrown'); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.include(expectedMessage); + } + + if (isSingle) { + assert.equal(cloudSdkApiStub.deleteSnapshot.callCount, 1); + }else{ + assert.equal(cloudSdkApiStub.deleteSnapshot.callCount, snapshots.length); + } + + const output = command.log.getCapturedLogOutput(); + expect(command.spinnerStart.called).to.be.true; + expect(command.spinnerStop.called).to.be.true; + if (isSingle) { + expect(cloudSdkApiStub.deleteSnapshot.calledWith('snap1', false)).to.be + .true; + } else { + for (const snapshot of snapshots) { + expect( + cloudSdkApiStub.deleteSnapshot.calledWith(snapshot.name, false) + ).to.be.true; + } + } + }; + + describe('single snapshot deletion', function () { + it('reacts to error code 404 appropriately - 1', async () => + executeWithErrorExpected( + 'snap1', + false, + 404, + 'The requested environment or program does not exist.', + 'The environment or program does not exist' + )); + + it('reacts to error code 404 appropriately - 2', async () => + executeWithErrorExpected( + 'snap1', + false, + 404, + 'The requested snapshot does not exist.', + 'The snapshot does not exist' + )); + + it('reacts to error code 403 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 403, + "The snapshot to be wiped is not in state 'removed'.", + 'Snapshot is in wrong state. Must be in state "REMOVED" to be able to wipe.' + )); + + it('reacts to error code 400 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 400, + null, + 'The given environment is not an RDE' + )); + + it('reacts to error code 500 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 451, + null, + 'The feature is part of the EAP program and not available for general use.' + )); + + it('reacts to error code 503 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 503, + null, + 'The RDE is not in a state where a snapshot can be created or restored.' + )); + }); + + describe('all snapshot deletion', function () { + it('reacts to error code 404 appropriately - 1', async () => + executeWithErrorExpected( + 'all', + false, + 404, + 'The requested environment or program does not exist.', + 'The environment or program does not exist' + )); + + it('reacts to error code 404 appropriately - 2', async () => + executeWithErrorExpected( + 'all', + false, + 404, + 'The requested snapshot does not exist.', + 'The snapshot does not exist' + )); + + it('reacts to error code 403 appropriately', async () => + executeWithErrorExpected( + 'all', + false, + 403, + "The snapshot to be wiped is not in state 'removed'.", + 'Snapshot is in wrong state. Must be in state "REMOVED" to be able to wipe.' + )); + + it('reacts to error code 400 appropriately', async () => + executeWithErrorExpected( + 'all', + false, + 400, + null, + 'The given environment is not an RDE' + )); + + it('reacts to error code 500 appropriately', async () => + executeWithErrorExpected( + 'all', + false, + 451, + null, + 'The feature is part of the EAP program and not available for general use.' + )); + + it('reacts to error code 503 appropriately', async () => + executeWithErrorExpected( + 'all', + false, + 503, + null, + 'The RDE is not in a state where a snapshot can be created or restored.' + )); + }); + }); +}); diff --git a/test/commands/aem/rde/snapshot/index.test.js b/test/commands/aem/rde/snapshot/index.test.js index 0d51811..e67114c 100644 --- a/test/commands/aem/rde/snapshot/index.test.js +++ b/test/commands/aem/rde/snapshot/index.test.js @@ -4,6 +4,7 @@ const ListSnapshots = require('../../../../../src/commands/aem/rde/snapshot'); const internalErrors = require('../../../../../src/lib/internal-errors'); const configErrors = require('../../../../../src/lib/configuration-errors'); const errorHelpers = require('../../../../../src/lib/error-helpers'); +const { snapshots, response } = require('./snapshots.mocks'); /** * @@ -37,30 +38,6 @@ describe('ListSnapshots', function () { describe('#runCommand', function () { let command, cloudSdkApiStub; - const stubbedSnapshotsResponse = { - status: 200, - json: async () => [ - { - name: 'snap1', - description: 'desc1', - usage: 1, - size: { total_size: 1048576 }, - state: 'AVAILABLE', - created: '2024-06-01T12:00:00Z', - lastUsed: '2024-06-02T12:00:00Z', - }, - { - name: 'snap2', - description: 'desc2', - usage: 2, - size: { total_size: 1073741824 }, - state: 'DELETED', - created: '2024-06-03T12:00:00Z', - lastUsed: '2024-06-04T12:00:00Z', - }, - ], - }; - const stubbedEmptySnapshotsResponse = { status: 200, json: async () => [], @@ -69,7 +46,7 @@ describe('ListSnapshots', function () { beforeEach(() => { command = new ListSnapshots([], {}); [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { - getSnapshots: sinon.stub().resolves(stubbedSnapshotsResponse), + getSnapshots: sinon.stub().resolves(snapshots), }); sinon.stub(command, 'spinnerStart'); sinon.stub(command, 'spinnerStop'); diff --git a/test/commands/aem/rde/snapshot/snapshots.mocks.js b/test/commands/aem/rde/snapshot/snapshots.mocks.js new file mode 100644 index 0000000..49815f1 --- /dev/null +++ b/test/commands/aem/rde/snapshot/snapshots.mocks.js @@ -0,0 +1,30 @@ +const snapshots = [ + { + name: 'snap1', + description: 'desc1', + usage: 1, + size: { total_size: 1048576 }, + state: 'AVAILABLE', + created: '2024-06-01T12:00:00Z', + lastUsed: '2024-06-02T12:00:00Z', + }, + { + name: 'snap2', + description: 'desc2', + usage: 2, + size: { total_size: 1073741824 }, + state: 'DELETED', + created: '2024-06-03T12:00:00Z', + lastUsed: '2024-06-04T12:00:00Z', + }, +]; + +const snapshotsResponse = { + status: 200, + json: async () => snapshots, +}; + +module.exports = { + snapshotsResponse, + snapshots, +}; From 96119b4670114f700ba7dca889680a81f2b12f86 Mon Sep 17 00:00:00 2001 From: Julian Sedding Date: Mon, 7 Jul 2025 11:24:24 +0200 Subject: [PATCH 45/67] generalize transformation of table columns + use sorting of table api --- src/commands/aem/rde/snapshot/index.js | 90 ++++++++++++-------- test/commands/aem/rde/snapshot/index.test.js | 11 +++ 2 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/commands/aem/rde/snapshot/index.js b/src/commands/aem/rde/snapshot/index.js index b0fb8a6..a99e226 100644 --- a/src/commands/aem/rde/snapshot/index.js +++ b/src/commands/aem/rde/snapshot/index.js @@ -18,6 +18,51 @@ const { codes: configurationCodes, } = require('../../../../lib/configuration-errors'); +const DATE_FORMATTER = (val) => + new Date(val).toLocaleString(undefined, { + dateStyle: 'medium', + timeStyle: 'short', + }); + +// Helper to format bytes to MB or GB +const BYTE_FORMATTER = (bytes) => { + if (typeof bytes !== 'number' || isNaN(bytes)) return ''; + const kb = 1024; + const mb = 1024 * kb; + const gb = 1024 * mb; + if (bytes >= gb) { + return (bytes / gb).toFixed(2) + ' GB'; + } else if (bytes >= mb) { + return (bytes / mb).toFixed(2) + ' MB'; + } else if (bytes >= kb) { + return (bytes / kb).toFixed(2) + ' KB'; + } + return bytes + ' B'; +}; + +const SIZE_FORMATTER = (size) => { + const bytes = size.total_size ?? size; + return BYTE_FORMATTER(bytes); +}; + +const FORMATTERS = { + created: DATE_FORMATTER, + lastUsed: DATE_FORMATTER, + size: SIZE_FORMATTER, +}; + +const formatItem = (row) => { + const copy = Object.assign({}, row); + const keys = Object.keys(copy); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (Object.hasOwn(FORMATTERS, key)) { + copy[key] = FORMATTERS[key].call(null, copy[key]); + } + } + return copy; +}; + class ListSnapshots extends BaseCommand { constructor(argv, config) { super(argv, config); @@ -65,37 +110,8 @@ class ListSnapshots extends BaseCommand { } logInTableFormat(items, flags) { - // Helper to format bytes to MB or GB - function formatSize(bytes) { - if (typeof bytes !== 'number' || isNaN(bytes)) return ''; - const gb = 1024 * 1024 * 1024; - const mb = 1024 * 1024; - if (bytes >= gb) { - return (bytes / gb).toFixed(2) + ' GB'; - } else if (bytes >= mb) { - return (bytes / mb).toFixed(2) + ' MB'; - } - return bytes + ' B'; - } - - let mappedItems = items.map((item) => { - const sizeBytes = item.size?.total_size ?? item.size; - return { - ...item, - size: formatSize(sizeBytes), - }; - }); - - if (flags.usage) { - mappedItems = mappedItems.sort((a, b) => { - const usageA = a.usage ?? 0; - const usageB = b.usage ?? 0; - return usageB - usageA; // Sort by usage, most used first - }); - } - cli.table( - mappedItems, + items.map(formatItem), { name: { minWidth: 20, @@ -109,7 +125,10 @@ class ListSnapshots extends BaseCommand { created: {}, lastUsed: { header: 'Last Used' }, }, - { printLine: (s) => this.doLog(s, true) } + { + sort: flags.sort, + printLine: (s) => this.doLog(s, true), + } ); } } @@ -120,12 +139,13 @@ Object.assign(ListSnapshots, { args: [], aliases: [], flags: { - usage: Flags.boolean({ - description: 'Sorts the snapshots by usage, most used first.', - char: 'u', + sort: Flags.string({ + description: + 'Sort the table by a table header, prefixed by a minus symbol for reverse sorting', + char: 's', multiple: false, required: false, - default: false, + default: '-Last Used', }), }, }); diff --git a/test/commands/aem/rde/snapshot/index.test.js b/test/commands/aem/rde/snapshot/index.test.js index 0d51811..ab9ecd9 100644 --- a/test/commands/aem/rde/snapshot/index.test.js +++ b/test/commands/aem/rde/snapshot/index.test.js @@ -58,6 +58,15 @@ describe('ListSnapshots', function () { created: '2024-06-03T12:00:00Z', lastUsed: '2024-06-04T12:00:00Z', }, + { + name: 'snap3', + description: 'desc3', + usage: 2, + size: { total_size: 5012 }, + state: 'AVAILABLE', + created: '2024-06-03T12:00:00Z', + lastUsed: '2024-06-04T12:00:00Z', + }, ], }; @@ -86,8 +95,10 @@ describe('ListSnapshots', function () { const output = command.log.getCapturedLogOutput(); expect(output).to.include('snap1'); expect(output).to.include('snap2'); + expect(output).to.include('snap3'); expect(output).to.include('1.00 MB'); expect(output).to.include('1.00 GB'); + expect(output).to.include('4.89 KB'); }); it('logs "There are no snapshots yet." for empty items', async function () { From d19d014d86d6d469e98b65d03dcd151cacf7d5c5 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 12:14:19 +0200 Subject: [PATCH 46/67] Add basic create test --- src/commands/aem/rde/snapshot/create.js | 15 +- test/commands/aem/rde/snapshot/create.test.js | 181 ++++++++++++++++++ 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index a233f47..2453102 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -26,8 +26,13 @@ const { loadAllArtifacts } = require('../../../../lib/rde-utils'); const Spinnies = require('spinnies'); class CreateSnapshots extends BaseCommand { + constructor(argv, config, sleepTime = 5000) { + super(argv, config); + this.sleepTime = sleepTime; + } + async runCommand(args, flags) { - const spinnies = flags.quiet || flags.json ? undefined : new Spinnies(); + const spinnies = this.getSpinnies(flags); spinnies?.add('spinner-requesting', { text: `Requesting to create snapshot ${args.name} (<1m) ...`, }); @@ -143,7 +148,7 @@ class CreateSnapshots extends BaseCommand { throw new snapshotCodes.SNAPSHOT_CREATION_FAILED(); } - await sleepMillis(5000); + await sleepMillis(this.sleepTime); } if (lastProgress === 100) { @@ -165,7 +170,7 @@ class CreateSnapshots extends BaseCommand { if (status.status === 'Ready') { break; } - await sleepMillis(10000); + await sleepMillis(this.sleepTime * 2); } const took = this.formatElapsedTime( @@ -194,6 +199,10 @@ class CreateSnapshots extends BaseCommand { this.notify('restored', 'Snapshot created.'); return result; } + + getSpinnies(flags) { + return flags.quiet || flags.json ? undefined : new Spinnies(); + } } Object.assign(CreateSnapshots, { diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index e69de29..49f184f 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -0,0 +1,181 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const CreateSnapshot = require('../../../../../src/commands/aem/rde/snapshot/create'); +const Spinnies = require('spinnies'); +const assert = require('assert'); + +/** + * + * @param sinon + * @param command + * @param methods + */ +function createCloudSdkAPIStub(sinon, command, methods) { + const cloudSdkApiStub = {}; + Object.keys(methods).forEach((k) => { + cloudSdkApiStub[k] = methods[k]; + }); + sinon + .stub(command, 'withCloudSdk') + .callsFake(async (fn) => fn(cloudSdkApiStub)); + return [command, cloudSdkApiStub]; +} + +/** + * + * @param sinon + * @param command + */ +function createSpinniesStub(sinon, command) { + const spinnies = new Spinnies(); + const addSpy = sinon.spy(spinnies, 'add'); + const stopAllSpy = sinon.spy(spinnies, 'stopAll'); + const succeedSpy = sinon.spy(spinnies, 'succeed'); + sinon.stub(command, 'getSpinnies').callsFake(() => spinnies); + + return { + addSpy, + stopAllSpy, + succeedSpy, + }; +} + +/** + * + * @param sinon + * @param command + */ +function setupLogCapturing(sinon, command) { + const logs = []; + sinon.stub(command, 'doLog').callsFake((msg) => logs.push(msg)); + command.log = { getCapturedLogOutput: () => logs.join('\n') }; +} + +describe('CreateSnapshot', function () { + describe('#runCommand', function () { + let command, + cloudSdkApiStub, + addSpinniesSpy, + stopAllSpinniesSpy, + succeedSpinniesSpy; + + const stubbedCreateResponse = { + status: 200, + json: async () => ({ + actionid: 1234123, + success: true, + }), + }; + + let counter = 0; + const getSnapshotProgressResponse = { + status: 200, + json: async () => { + if (counter === 0) { + counter++; + return { + action: 'create-snapshot', + progressPercentage: 20, + snapshotName: 'snapshot-001', + }; + } else { + return { + action: 'create-snapshot', + progressPercentage: 100, + snapshotName: 'snapshot-001', + }; + } + }, + }; + + const getArtifactsResponse = { + status: 200, + json: async () => ({ + status: 'Ready', + items: [], + }), + }; + + const stub = (response) => sinon.stub().resolves(response); + + beforeEach(() => { + command = new CreateSnapshot([], {}, 10); + [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { + createSnapshot: stub(stubbedCreateResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse), + getArtifacts: stub(getArtifactsResponse), + }); + const { addSpy, stopAllSpy, succeedSpy } = createSpinniesStub( + sinon, + command + ); + addSpinniesSpy = addSpy; + stopAllSpinniesSpy = stopAllSpy; + succeedSpinniesSpy = succeedSpy; + setupLogCapturing(sinon, command); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('returns result object with status and snapshots', async function () { + const result = await command.runCommand([], {}); + + expect(result.totalseconds).to.be.a('number'); + expect(result.waitingforbackend).to.be.a('date'); + expect(result.startTime).to.be.a('date'); + expect(result.processnigsnapshotstarted).to.be.a('date'); + expect(result.processnigsnapshotended).to.be.a('date'); + + assert.equal(cloudSdkApiStub.createSnapshot.calledOnce, true); + assert.equal(cloudSdkApiStub.getSnapshotProgress.called, true); + assert.equal(cloudSdkApiStub.getArtifacts.called, true); + + expect(addSpinniesSpy.callCount).to.equal(4); + + expect(addSpinniesSpy.getCall(0).args[0]).to.equal('spinner-requesting'); + expect(addSpinniesSpy.getCall(1).args[0]).to.equal('spinner-backend'); + expect(addSpinniesSpy.getCall(2).args[0]).to.equal('spinner-create'); + expect(addSpinniesSpy.getCall(3).args[0]).to.equal('spinner-restart'); + + expect(succeedSpinniesSpy.callCount).to.equal(4); + + const verifySpinnySucceeded = ( + callIndex, + expectedSpinnerName, + compareObjectFn + ) => { + const [spinnerName, obj] = succeedSpinniesSpy.getCall(callIndex).args; + expect(spinnerName).to.equal(expectedSpinnerName); + + assert.equal(compareObjectFn(obj), true); + }; + + verifySpinnySucceeded( + 0, + 'spinner-requesting', + (obj) => + obj.text.startsWith( + 'Requested to create the snapshot successfully.' + ) && obj.successColor === 'greenBright' + ); + + verifySpinnySucceeded( + 1, + 'spinner-backend', + (obj) => + obj.text.startsWith( + 'Backend picked up the job to create the snapshot.' + ) && obj.successColor === 'greenBright' + ); + verifySpinnySucceeded( + 2, + 'spinner-create', + (obj) => + obj.text.startsWith('Created snapshot successfully.') && + obj.successColor === 'greenBright' + ); + }); + }); +}); From e9a5a738bdb55d188bed74bd6aa03da3af78c007 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 13:19:12 +0200 Subject: [PATCH 47/67] wip unit tests --- test/commands/aem/rde/snapshot/create.test.js | 64 ++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index 49f184f..7b83584 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -59,7 +59,7 @@ describe('CreateSnapshot', function () { stopAllSpinniesSpy, succeedSpinniesSpy; - const stubbedCreateResponse = { + const stubbedCreateResponseSuccess = { status: 200, json: async () => ({ actionid: 1234123, @@ -67,6 +67,15 @@ describe('CreateSnapshot', function () { }), }; + const stubbedCreateResponseFailure = (status = 400) => ({ + status, + json: async () => ({ + actionid: 1234123, + success: false, + error: 'Failed to create snapshot', + }), + }); + let counter = 0; const getSnapshotProgressResponse = { status: 200, @@ -98,13 +107,13 @@ describe('CreateSnapshot', function () { const stub = (response) => sinon.stub().resolves(response); - beforeEach(() => { + const prepareStubs = (cloudSdkMethods) => { command = new CreateSnapshot([], {}, 10); - [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { - createSnapshot: stub(stubbedCreateResponse), - getSnapshotProgress: stub(getSnapshotProgressResponse), - getArtifacts: stub(getArtifactsResponse), - }); + [command, cloudSdkApiStub] = createCloudSdkAPIStub( + sinon, + command, + cloudSdkMethods + ); const { addSpy, stopAllSpy, succeedSpy } = createSpinniesStub( sinon, command @@ -113,13 +122,19 @@ describe('CreateSnapshot', function () { stopAllSpinniesSpy = stopAllSpy; succeedSpinniesSpy = succeedSpy; setupLogCapturing(sinon, command); - }); + }; afterEach(() => { sinon.restore(); }); - it('returns result object with status and snapshots', async function () { + it('calls the appropriate api and shows success spinners if the result is good.', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getSnapshotProgress: stub(getSnapshotProgressResponse), + getArtifacts: stub(getArtifactsResponse), + }); + const result = await command.runCommand([], {}); expect(result.totalseconds).to.be.a('number'); @@ -177,5 +192,36 @@ describe('CreateSnapshot', function () { obj.successColor === 'greenBright' ); }); + + it('calls the appropriate api and shows failures', async function () { + const checkError = async (statusCode, expectedMessage) => { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseFailure(statusCode)), + getSnapshotProgress: stub(getSnapshotProgressResponse), + getArtifacts: stub(getArtifactsResponse), + }); + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.include(expectedMessage); + } + expect(stopAllSpinniesSpy.callCount).to.equal(1); + }; + + await checkError(400, 'The given environment is not an RDE'); + await checkError(404, 'The environment or program does not exist'); + await checkError(409, 'A snapshot with the given name already exists'); + await checkError( + 503, + 'The RDE is not in a state where a snapshot can be created or restored.' + ); + await checkError( + 507, + 'Reached the maximum number or diskspace of snapshots. Remove some snapshots and try again' + ); + await checkError(500, 'An unknown error occurred.'); + }); }); }); From bbb36da3a4f6d8bec1c7b2dba516c155b27638cf Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 13:36:50 +0200 Subject: [PATCH 48/67] Add coverage --- test/commands/aem/rde/snapshot/create.test.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index 7b83584..e285499 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -106,6 +106,7 @@ describe('CreateSnapshot', function () { }; const stub = (response) => sinon.stub().resolves(response); + const stubReject = (message) => sinon.stub().rejects(message); const prepareStubs = (cloudSdkMethods) => { command = new CreateSnapshot([], {}, 10); @@ -193,6 +194,40 @@ describe('CreateSnapshot', function () { ); }); + it('catches a withCloudSdk exception - createSnapshot', async function () { + prepareStubs({ + createSnapshot: stubReject('Internal error'), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain( + 'There was an unexpected error when running a snapshot command.' + ); + } + }); + + it('catches a withCloudSdk exception - getSnapshotProgress', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getSnapshotProgress: stubReject('Internal error'), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain( + 'There was an unexpected error when running a snapshot command.' + ); + } + }); + + it('calls the appropriate api and shows failures', async function () { const checkError = async (statusCode, expectedMessage) => { prepareStubs({ From 7e7b8ab758f398ee97b0567956a633a4e170681a Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 14:02:13 +0200 Subject: [PATCH 49/67] More coverage --- test/commands/aem/rde/snapshot/create.test.js | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index e285499..80b4c63 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -54,11 +54,16 @@ function setupLogCapturing(sinon, command) { describe('CreateSnapshot', function () { describe('#runCommand', function () { let command, + progressCounter, cloudSdkApiStub, addSpinniesSpy, stopAllSpinniesSpy, succeedSpinniesSpy; + beforeEach(function () { + progressCounter = 0; + }); + const stubbedCreateResponseSuccess = { status: 200, json: async () => ({ @@ -76,25 +81,17 @@ describe('CreateSnapshot', function () { }), }); - let counter = 0; - const getSnapshotProgressResponse = { - status: 200, - json: async () => { - if (counter === 0) { - counter++; - return { - action: 'create-snapshot', - progressPercentage: 20, - snapshotName: 'snapshot-001', - }; - } else { - return { - action: 'create-snapshot', - progressPercentage: 100, - snapshotName: 'snapshot-001', - }; - } - }, + const getSnapshotProgressResponse = (percentages = [20, 100]) => { + const result = { + status: 200, + json: async () => ({ + action: 'create-snapshot', + progressPercentage: percentages[progressCounter], + snapshotName: 'snapshot-001', + }), + }; + progressCounter++; + return result; }; const getArtifactsResponse = { @@ -132,7 +129,7 @@ describe('CreateSnapshot', function () { it('calls the appropriate api and shows success spinners if the result is good.', async function () { prepareStubs({ createSnapshot: stub(stubbedCreateResponseSuccess), - getSnapshotProgress: stub(getSnapshotProgressResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse()), getArtifacts: stub(getArtifactsResponse), }); @@ -194,6 +191,24 @@ describe('CreateSnapshot', function () { ); }); + it('catches a bad progress result (-2)', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getArtifacts: stub(getArtifactsResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, -2])), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain( + 'The snapshot failed to be created. Please contact support.' + ); + } + }); + it('catches a withCloudSdk exception - createSnapshot', async function () { prepareStubs({ createSnapshot: stubReject('Internal error'), @@ -227,12 +242,11 @@ describe('CreateSnapshot', function () { } }); - it('calls the appropriate api and shows failures', async function () { const checkError = async (statusCode, expectedMessage) => { prepareStubs({ createSnapshot: stub(stubbedCreateResponseFailure(statusCode)), - getSnapshotProgress: stub(getSnapshotProgressResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse()), getArtifacts: stub(getArtifactsResponse), }); try { From dbc9197b69e9be87bc5cefb6902b9012451bb2ff Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Fri, 4 Jul 2025 14:06:29 +0200 Subject: [PATCH 50/67] More coverage --- test/commands/aem/rde/snapshot/create.test.js | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/test/commands/aem/rde/snapshot/create.test.js b/test/commands/aem/rde/snapshot/create.test.js index 80b4c63..e723426 100644 --- a/test/commands/aem/rde/snapshot/create.test.js +++ b/test/commands/aem/rde/snapshot/create.test.js @@ -81,9 +81,12 @@ describe('CreateSnapshot', function () { }), }); - const getSnapshotProgressResponse = (percentages = [20, 100]) => { + const getSnapshotProgressResponse = ( + percentages = [20, 100], + status = 200 + ) => { const result = { - status: 200, + status, json: async () => ({ action: 'create-snapshot', progressPercentage: percentages[progressCounter], @@ -209,6 +212,38 @@ describe('CreateSnapshot', function () { } }); + it('catches 404 on progress response', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getArtifacts: stub(getArtifactsResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, 50], 404)), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain('The snapshot does not exist'); + } + }); + + it('catches unknown on progress response', async function () { + prepareStubs({ + createSnapshot: stub(stubbedCreateResponseSuccess), + getArtifacts: stub(getArtifactsResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, 50], 500)), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain('An unknown error occurred.'); + } + }); + it('catches a withCloudSdk exception - createSnapshot', async function () { prepareStubs({ createSnapshot: stubReject('Internal error'), From de20017b568b64af2352617bfbd4a4501bec84f6 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Mon, 7 Jul 2025 10:51:47 +0200 Subject: [PATCH 51/67] add coverage of delete.js --- package-lock.json | 82 ++++-- package.json | 2 +- src/commands/aem/rde/snapshot/delete.js | 12 +- test/commands/aem/rde/snapshot/delete.test.js | 246 ++++++++++++++++++ test/commands/aem/rde/snapshot/index.test.js | 36 +-- .../aem/rde/snapshot/snapshots.mocks.js | 39 +++ 6 files changed, 353 insertions(+), 64 deletions(-) create mode 100644 test/commands/aem/rde/snapshot/snapshots.mocks.js diff --git a/package-lock.json b/package-lock.json index e67f7cb..26d814b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "@adobe/eslint-config-aio-lib-config": "^4.0.0", "@inquirer/testing": "^2.1.19", "@oclif/dev-cli": "^1.26.10", - "chai": "^5.1.1", + "chai": "^4.5.0", "chai-as-promised": "^8.0.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", @@ -3258,12 +3258,13 @@ } }, "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": "*" } }, "node_modules/async": { @@ -3740,19 +3741,22 @@ } }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, + "license": "MIT", "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" }, "engines": { - "node": ">=12" + "node": ">=4" } }, "node_modules/chai-as-promised": { @@ -3767,6 +3771,29 @@ "chai": ">= 2.1.2 < 6" } }, + "node_modules/chai/node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chai/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4366,10 +4393,14 @@ } }, "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, "engines": { "node": ">=6" } @@ -5889,6 +5920,7 @@ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -8199,10 +8231,11 @@ } }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, + "license": "MIT", "dependencies": { "get-func-name": "^2.0.1" } @@ -9167,12 +9200,13 @@ } }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 14.16" + "node": "*" } }, "node_modules/picocolors": { diff --git a/package.json b/package.json index 83b1a3b..a58cfe7 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@adobe/eslint-config-aio-lib-config": "^4.0.0", "@inquirer/testing": "^2.1.19", "@oclif/dev-cli": "^1.26.10", - "chai": "^5.1.1", + "chai": "^4.5.0", "chai-as-promised": "^8.0.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", diff --git a/src/commands/aem/rde/snapshot/delete.js b/src/commands/aem/rde/snapshot/delete.js index 903bef8..6c84ace 100644 --- a/src/commands/aem/rde/snapshot/delete.js +++ b/src/commands/aem/rde/snapshot/delete.js @@ -23,13 +23,13 @@ const chalk = require('chalk'); class DeleteSnapshots extends BaseCommand { async runCommand(args, flags) { if (flags.all) { - this.deleteAllSnapshots(); + await this.deleteAllSnapshots(flags.force); } else { - this.deleteSnapshot(args.name, flags.force); + await this.deleteSnapshot(args.name, flags.force); } } - async deleteAllSnapshots() { + async deleteAllSnapshots(force) { let response; try { this.spinnerStart('fetching all snapshots'); @@ -53,7 +53,9 @@ class DeleteSnapshots extends BaseCommand { if (json?.items?.length === 0) { this.doLog('There are no snapshots yet.'); } else { - json?.forEach((e) => this.deleteSnapshot(e.name, this.flags.force)); + const promises = + json?.map((e) => this.deleteSnapshot(e.name, force)) || []; + await Promise.all(promises); } } else { throw new internalCodes.UNKNOWN(); @@ -78,7 +80,7 @@ class DeleteSnapshots extends BaseCommand { if (response?.status === 451) { throw new configurationCodes.NON_EAP(); } else if (response?.status === 200 || response?.status === 201) { - if (this.flags.force) { + if (force) { this.doLog( chalk.green( `Snapshot ${name} deleted successfully. Use 'aio aem rde snapshot' to validate its removal.` diff --git a/test/commands/aem/rde/snapshot/delete.test.js b/test/commands/aem/rde/snapshot/delete.test.js index e69de29..f4efdd3 100644 --- a/test/commands/aem/rde/snapshot/delete.test.js +++ b/test/commands/aem/rde/snapshot/delete.test.js @@ -0,0 +1,246 @@ +const assert = require('assert'); +const { expect } = require('chai'); +const sinon = require('sinon'); +const DeleteSnapshot = require('../../../../../src/commands/aem/rde/snapshot/delete'); +const { snapshotsResponse, snapshots } = require('./snapshots.mocks'); + +/** + * + * @param sinon + * @param command + * @param methods + */ +function createCloudSdkAPIStub(sinon, command, methods) { + const cloudSdkApiStub = {}; + Object.keys(methods).forEach((k) => { + cloudSdkApiStub[k] = methods[k]; + }); + sinon + .stub(command, 'withCloudSdk') + .callsFake(async (fn) => fn(cloudSdkApiStub)); + return [command, cloudSdkApiStub]; +} + +/** + * + * @param sinon + * @param command + */ +function setupLogCapturing(sinon, command) { + const logs = []; + sinon.stub(command, 'doLog').callsFake((msg) => logs.push(msg)); + command.log = { getCapturedLogOutput: () => logs.join('\n') }; +} + +describe('DeleteSnapshots', function () { + describe('#run commands', function () { + let command, cloudSdkApiStub; + + const stubbedDeleteResponse = (status, details) => ({ + status, + json: async () => ({ + details, + }), + }); + + const stubCommandResponse = (status, details) => { + [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { + getSnapshots: sinon.stub().resolves(snapshotsResponse), + deleteSnapshot: sinon + .stub() + .resolves(stubbedDeleteResponse(status, details)), + }); + }; + + beforeEach(() => { + command = new DeleteSnapshot([], {}); + sinon.stub(command, 'spinnerStart'); + sinon.stub(command, 'spinnerStop'); + setupLogCapturing(sinon, command); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('calls deleteSnapshots successfully', async () => { + stubCommandResponse(200); + + await command.runCommand( + { + name: 'snap1', + }, + { + force: false, + } + ); + expect(cloudSdkApiStub.deleteSnapshot.calledOnce).to.be.true; + const output = command.log.getCapturedLogOutput(); + expect(output).to.include('snap1 deleted successfully'); + + expect(command.spinnerStart.calledOnce).to.be.true; + expect(command.spinnerStop.calledOnce).to.be.true; + expect(cloudSdkApiStub.deleteSnapshot.calledWith('snap1', false)).to.be + .true; + }); + + const executeWithErrorExpected = async ( + snapshotName, + force, + status, + statusMessage, + expectedMessage + ) => { + const isSingle = snapshotName && snapshotName !== 'all'; + stubCommandResponse(status, statusMessage); + try { + const args = {}; + const flags = { + force, + }; + if (isSingle) { + args.name = snapshotName; + } else { + flags.all = true; + } + await command.runCommand(args, flags); + assert.fail('Expected an error to be thrown'); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.include(expectedMessage); + } + + if (isSingle) { + assert.equal(cloudSdkApiStub.deleteSnapshot.callCount, 1); + }else{ + assert.equal(cloudSdkApiStub.deleteSnapshot.callCount, snapshots.length); + } + + const output = command.log.getCapturedLogOutput(); + expect(command.spinnerStart.called).to.be.true; + expect(command.spinnerStop.called).to.be.true; + if (isSingle) { + expect(cloudSdkApiStub.deleteSnapshot.calledWith('snap1', false)).to.be + .true; + } else { + for (const snapshot of snapshots) { + expect( + cloudSdkApiStub.deleteSnapshot.calledWith(snapshot.name, false) + ).to.be.true; + } + } + }; + + describe('single snapshot deletion', function () { + it('reacts to error code 404 appropriately - 1', async () => + executeWithErrorExpected( + 'snap1', + false, + 404, + 'The requested environment or program does not exist.', + 'The environment or program does not exist' + )); + + it('reacts to error code 404 appropriately - 2', async () => + executeWithErrorExpected( + 'snap1', + false, + 404, + 'The requested snapshot does not exist.', + 'The snapshot does not exist' + )); + + it('reacts to error code 403 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 403, + "The snapshot to be wiped is not in state 'removed'.", + 'Snapshot is in wrong state. Must be in state "REMOVED" to be able to wipe.' + )); + + it('reacts to error code 400 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 400, + null, + 'The given environment is not an RDE' + )); + + it('reacts to error code 500 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 451, + null, + 'The feature is part of the EAP program and not available for general use.' + )); + + it('reacts to error code 503 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 503, + null, + 'The RDE is not in a state where a snapshot can be created or restored.' + )); + }); + + describe('all snapshot deletion', function () { + it('reacts to error code 404 appropriately - 1', async () => + executeWithErrorExpected( + 'all', + false, + 404, + 'The requested environment or program does not exist.', + 'The environment or program does not exist' + )); + + it('reacts to error code 404 appropriately - 2', async () => + executeWithErrorExpected( + 'all', + false, + 404, + 'The requested snapshot does not exist.', + 'The snapshot does not exist' + )); + + it('reacts to error code 403 appropriately', async () => + executeWithErrorExpected( + 'all', + false, + 403, + "The snapshot to be wiped is not in state 'removed'.", + 'Snapshot is in wrong state. Must be in state "REMOVED" to be able to wipe.' + )); + + it('reacts to error code 400 appropriately', async () => + executeWithErrorExpected( + 'all', + false, + 400, + null, + 'The given environment is not an RDE' + )); + + it('reacts to error code 500 appropriately', async () => + executeWithErrorExpected( + 'all', + false, + 451, + null, + 'The feature is part of the EAP program and not available for general use.' + )); + + it('reacts to error code 503 appropriately', async () => + executeWithErrorExpected( + 'all', + false, + 503, + null, + 'The RDE is not in a state where a snapshot can be created or restored.' + )); + }); + }); +}); diff --git a/test/commands/aem/rde/snapshot/index.test.js b/test/commands/aem/rde/snapshot/index.test.js index ab9ecd9..2c5c3da 100644 --- a/test/commands/aem/rde/snapshot/index.test.js +++ b/test/commands/aem/rde/snapshot/index.test.js @@ -4,6 +4,7 @@ const ListSnapshots = require('../../../../../src/commands/aem/rde/snapshot'); const internalErrors = require('../../../../../src/lib/internal-errors'); const configErrors = require('../../../../../src/lib/configuration-errors'); const errorHelpers = require('../../../../../src/lib/error-helpers'); +const { snapshotsResponse } = require('./snapshots.mocks'); /** * @@ -37,39 +38,6 @@ describe('ListSnapshots', function () { describe('#runCommand', function () { let command, cloudSdkApiStub; - const stubbedSnapshotsResponse = { - status: 200, - json: async () => [ - { - name: 'snap1', - description: 'desc1', - usage: 1, - size: { total_size: 1048576 }, - state: 'AVAILABLE', - created: '2024-06-01T12:00:00Z', - lastUsed: '2024-06-02T12:00:00Z', - }, - { - name: 'snap2', - description: 'desc2', - usage: 2, - size: { total_size: 1073741824 }, - state: 'DELETED', - created: '2024-06-03T12:00:00Z', - lastUsed: '2024-06-04T12:00:00Z', - }, - { - name: 'snap3', - description: 'desc3', - usage: 2, - size: { total_size: 5012 }, - state: 'AVAILABLE', - created: '2024-06-03T12:00:00Z', - lastUsed: '2024-06-04T12:00:00Z', - }, - ], - }; - const stubbedEmptySnapshotsResponse = { status: 200, json: async () => [], @@ -78,7 +46,7 @@ describe('ListSnapshots', function () { beforeEach(() => { command = new ListSnapshots([], {}); [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { - getSnapshots: sinon.stub().resolves(stubbedSnapshotsResponse), + getSnapshots: sinon.stub().resolves(snapshotsResponse), }); sinon.stub(command, 'spinnerStart'); sinon.stub(command, 'spinnerStop'); diff --git a/test/commands/aem/rde/snapshot/snapshots.mocks.js b/test/commands/aem/rde/snapshot/snapshots.mocks.js new file mode 100644 index 0000000..7f1e686 --- /dev/null +++ b/test/commands/aem/rde/snapshot/snapshots.mocks.js @@ -0,0 +1,39 @@ +const snapshots = [ + { + name: 'snap1', + description: 'desc1', + usage: 1, + size: { total_size: 1048576 }, + state: 'AVAILABLE', + created: '2024-06-01T12:00:00Z', + lastUsed: '2024-06-02T12:00:00Z', + }, + { + name: 'snap2', + description: 'desc2', + usage: 2, + size: { total_size: 1073741824 }, + state: 'DELETED', + created: '2024-06-03T12:00:00Z', + lastUsed: '2024-06-04T12:00:00Z', + }, + { + name: 'snap3', + description: 'desc3', + usage: 2, + size: { total_size: 5012 }, + state: 'AVAILABLE', + created: '2024-06-03T12:00:00Z', + lastUsed: '2024-06-04T12:00:00Z', + }, +]; + +const snapshotsResponse = { + status: 200, + json: async () => snapshots, +}; + +module.exports = { + snapshotsResponse, + snapshots, +}; From cd6eb164d1a7b8bf2314c43e5e075faf289f2a5c Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Mon, 7 Jul 2025 17:30:36 +0200 Subject: [PATCH 52/67] wip - use dev console api for resetting --- src/commands/aem/rde/reset.js | 8 +++++++- src/lib/cloud-sdk-api.js | 34 +++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/commands/aem/rde/reset.js b/src/commands/aem/rde/reset.js index 98a819c..0a3bd6e 100644 --- a/src/commands/aem/rde/reset.js +++ b/src/commands/aem/rde/reset.js @@ -26,7 +26,7 @@ class ResetCommand extends BaseCommand { this.doLog(`Reset cm-p${this._programId}-e${this._environmentId}`); this.spinnerStart('resetting environment'); const status = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.resetEnv(flags.wait) + cloudSdkAPI.resetEnv(flags.wait, flags['keep-mutable-content']) ); this.spinnerStop(); if (flags.wait) { @@ -63,6 +63,12 @@ Object.assign(ResetCommand, { organizationId: commonFlags.organizationId, programId: commonFlags.programId, environmentId: commonFlags.environmentId, + 'keep-mutable-content': Flags.boolean({ + description: 'Reset the RDE but keep mutable content.', + required: false, + default: false, + multiple: false, + }), wait: Flags.boolean({ description: 'Do or do not wait for completion of the reset operation. Progress can be manually checked using the "status" command.', diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index e60d8f0..1cfcde5 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -583,17 +583,33 @@ class CloudSdkAPI { } } - async resetEnv(wait) { + async resetEnv(wait, keepMutableContent) { await this._checkRDE(); - await this._waitForCMStatus(); - await this._resetEnv(); - if (wait) { - return await this._waitForCMStatus(); - } - } - async _resetEnv() { - await this._cloudManagerClient.doPut(`/reset`); + // later we should fold everything into the RDE API and not use the CM API + if (keepMutableContent) { + const result = await this._rdeClient.doPost(`/runtime/reset`, { + keepMutableContent: 'true', + }); + + if(result.status !== 201) { + throw await this._createError(response); + } + + const namespace = await this._getNamespace(); + const tries = 3; + for (let i = 0; i < tries; i++) { + await sleepSeconds(5); + await this._waitForEnvRunning(namespace); + } + + } else { + await this._waitForCMStatus(); + await this._cloudManagerClient.doPut(`/reset`); + if (wait) { + return await this._waitForCMStatus(); + } + } } async cleanEnv(wait, params) { From c00e54e996b1d57648d091c7b731aabe32b5dbbc Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Tue, 8 Jul 2025 10:31:31 +0200 Subject: [PATCH 53/67] Change param name --- src/lib/cloud-sdk-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 1cfcde5..83e5e49 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -589,7 +589,7 @@ class CloudSdkAPI { // later we should fold everything into the RDE API and not use the CM API if (keepMutableContent) { const result = await this._rdeClient.doPost(`/runtime/reset`, { - keepMutableContent: 'true', + 'keep-mutable-content': 'true', }); if(result.status !== 201) { From 2724d174d991062e9ff0dd30964c9e9c7ab121ea Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Tue, 8 Jul 2025 10:42:14 +0200 Subject: [PATCH 54/67] eslint fixes --- src/lib/cloud-sdk-api.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 83e5e49..9546948 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -592,7 +592,7 @@ class CloudSdkAPI { 'keep-mutable-content': 'true', }); - if(result.status !== 201) { + if (result.status !== 201) { throw await this._createError(response); } @@ -602,7 +602,6 @@ class CloudSdkAPI { await sleepSeconds(5); await this._waitForEnvRunning(namespace); } - } else { await this._waitForCMStatus(); await this._cloudManagerClient.doPut(`/reset`); From 42d2939490a2291ed1288d01303445afcbbb8d1d Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Tue, 8 Jul 2025 10:44:47 +0200 Subject: [PATCH 55/67] linting --- src/commands/aem/rde/reset.js | 2 +- src/lib/cloud-sdk-api.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/aem/rde/reset.js b/src/commands/aem/rde/reset.js index 0a3bd6e..2a9d57b 100644 --- a/src/commands/aem/rde/reset.js +++ b/src/commands/aem/rde/reset.js @@ -26,7 +26,7 @@ class ResetCommand extends BaseCommand { this.doLog(`Reset cm-p${this._programId}-e${this._environmentId}`); this.spinnerStart('resetting environment'); const status = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.resetEnv(flags.wait, flags['keep-mutable-content']) + cloudSdkAPI.resetEnv(flags.wait, flags['keep-mutable-content']) ); this.spinnerStop(); if (flags.wait) { diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 9546948..3ea98cf 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -593,7 +593,7 @@ class CloudSdkAPI { }); if (result.status !== 201) { - throw await this._createError(response); + throw await this._createError(result); } const namespace = await this._getNamespace(); From 5feb9c5a8a3d4fb76f290505ed8a6da1776de125 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Mon, 21 Jul 2025 12:02:12 +0200 Subject: [PATCH 56/67] Add unit tests for restore functionality --- src/commands/aem/rde/snapshot/restore.js | 17 +- .../commands/aem/rde/snapshot/restore.test.js | 330 ++++++++++++++++++ 2 files changed, 343 insertions(+), 4 deletions(-) diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index 4541927..58cbf52 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -27,9 +27,14 @@ const { loadAllArtifacts } = require('../../../../lib/rde-utils'); const Spinnies = require('spinnies'); class RestoreSnapshot extends BaseCommand { - async runCommand(args, flags) { - const spinnies = flags.quiet || flags.json ? undefined : new Spinnies(); + constructor(argv, config, sleepTime = 5000) { + super(argv, config); + this.sleepTime = sleepTime; + } + + async runCommand(args, flags) { + const spinnies = this.getSpinnies(flags); if (!flags.status) { spinnies?.add('spinner-requesting', { text: `Requesting to restore snapshot ${args.name} (<1m) ...`, @@ -153,7 +158,7 @@ class RestoreSnapshot extends BaseCommand { this.notify('failed', 'Snapshot creation failed.'); throw new snapshotCodes.SNAPSHOT_RESTORE_FAILED(); } - await sleepMillis(5000); + await sleepMillis(this.sleepTime); } if (lastProgress === 100) { @@ -175,7 +180,7 @@ class RestoreSnapshot extends BaseCommand { if (status.status === 'Ready') { break; } - await sleepMillis(10000); + await sleepMillis(this.sleepTime * 2); } const took = this.formatElapsedTime( @@ -204,6 +209,10 @@ class RestoreSnapshot extends BaseCommand { this.notify('restored', 'Snapshot restored.'); return result; } + + getSpinnies(flags) { + return flags.quiet || flags.json ? undefined : new Spinnies(); + } } Object.assign(RestoreSnapshot, { diff --git a/test/commands/aem/rde/snapshot/restore.test.js b/test/commands/aem/rde/snapshot/restore.test.js index e69de29..1fe8fea 100644 --- a/test/commands/aem/rde/snapshot/restore.test.js +++ b/test/commands/aem/rde/snapshot/restore.test.js @@ -0,0 +1,330 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const RestoreSnapshot = require('../../../../../src/commands/aem/rde/snapshot/restore'); +const Spinnies = require('spinnies'); +const assert = require('assert'); + +/** + * + * @param sinon + * @param command + * @param methods + */ +function createCloudSdkAPIStub(sinon, command, methods) { + const cloudSdkApiStub = {}; + Object.keys(methods).forEach((k) => { + cloudSdkApiStub[k] = methods[k]; + }); + sinon + .stub(command, 'withCloudSdk') + .callsFake(async (fn) => fn(cloudSdkApiStub)); + return [command, cloudSdkApiStub]; +} + +/** + * + * @param sinon + * @param command + */ +function createSpinniesStub(sinon, command) { + const spinnies = new Spinnies(); + const addSpy = sinon.spy(spinnies, 'add'); + const stopAllSpy = sinon.spy(spinnies, 'stopAll'); + const succeedSpy = sinon.spy(spinnies, 'succeed'); + sinon.stub(command, 'getSpinnies').callsFake(() => spinnies); + + return { + addSpy, + stopAllSpy, + succeedSpy, + }; +} + +/** + * + * @param sinon + * @param command + */ +function setupLogCapturing(sinon, command) { + const logs = []; + sinon.stub(command, 'doLog').callsFake((msg) => logs.push(msg)); + command.log = { getCapturedLogOutput: () => logs.join('\n') }; +} + +describe('RestoreSnapshot', function () { + describe('#runCommand', function () { + let command, + progressCounter, + cloudSdkApiStub, + addSpinniesSpy, + stopAllSpinniesSpy, + succeedSpinniesSpy; + + beforeEach(function () { + progressCounter = 0; + }); + + const stubbedCreateResponseSuccess = { + status: 200, + json: async () => ({ + actionid: 1234123, + success: true, + }), + }; + + const stubbedRestoreResponseFailure = (status = 400, details = '') => ({ + status, + json: async () => ({ + actionid: 1234123, + success: false, + error: 'Failed to restore snapshot', + details, + }), + }); + + const getSnapshotProgressResponse = ( + percentages = [20, 100], + status = 200 + ) => { + const result = { + status, + json: async () => ({ + action: 'restore-snapshot', + progressPercentage: percentages[progressCounter], + snapshotName: 'snapshot-001', + }), + }; + progressCounter++; + return result; + }; + + const getArtifactsResponse = { + status: 200, + json: async () => ({ + status: 'Ready', + items: [], + }), + }; + + const stub = (response) => sinon.stub().resolves(response); + const stubReject = (message) => sinon.stub().rejects(message); + + const prepareStubs = (cloudSdkMethods) => { + command = new RestoreSnapshot([], {}, 10); + [command, cloudSdkApiStub] = createCloudSdkAPIStub( + sinon, + command, + cloudSdkMethods + ); + const { addSpy, stopAllSpy, succeedSpy } = createSpinniesStub( + sinon, + command + ); + addSpinniesSpy = addSpy; + stopAllSpinniesSpy = stopAllSpy; + succeedSpinniesSpy = succeedSpy; + setupLogCapturing(sinon, command); + }; + + afterEach(() => { + sinon.restore(); + }); + + it('works', async function () { + prepareStubs({ + restoreSnapshot: stub(stubbedCreateResponseSuccess), + getSnapshotProgress: stub(getSnapshotProgressResponse()), + getArtifacts: stub(getArtifactsResponse), + }); + + const result = await command.runCommand([], {}); + + expect(result.totalseconds).to.be.a('number'); + expect(result.waitingforbackend).to.be.a('date'); + expect(result.startTime).to.be.a('date'); + expect(result.processnigsnapshotstarted).to.be.a('date'); + expect(result.processnigsnapshotended).to.be.a('date'); + + assert.equal(cloudSdkApiStub.restoreSnapshot.calledOnce, true); + assert.equal(cloudSdkApiStub.getSnapshotProgress.called, true); + assert.equal(cloudSdkApiStub.getArtifacts.called, true); + + expect(addSpinniesSpy.callCount).to.equal(4); + + expect(addSpinniesSpy.getCall(0).args[0]).to.equal('spinner-requesting'); + expect(addSpinniesSpy.getCall(1).args[0]).to.equal('spinner-backend'); + expect(addSpinniesSpy.getCall(2).args[0]).to.equal('spinner-restore'); + expect(addSpinniesSpy.getCall(3).args[0]).to.equal('spinner-restart'); + + expect(succeedSpinniesSpy.callCount).to.equal(4); + + const verifySpinnySucceeded = ( + callIndex, + expectedSpinnerName, + compareObjectFn + ) => { + const [spinnerName, obj] = succeedSpinniesSpy.getCall(callIndex).args; + expect(spinnerName).to.equal(expectedSpinnerName); + + assert.equal(compareObjectFn(obj), true); + }; + + verifySpinnySucceeded( + 0, + 'spinner-requesting', + (obj) => + obj.text.startsWith( + 'Requested to restore the snapshot successfully.' + ) && obj.successColor === 'greenBright' + ); + + verifySpinnySucceeded( + 1, + 'spinner-backend', + (obj) => + obj.text.startsWith( + 'Backend picked up the job to restore the snapshot.' + ) && obj.successColor === 'greenBright' + ); + verifySpinnySucceeded( + 2, + 'spinner-restore', + (obj) => + obj.text.startsWith('Restored snapshot to RDE successfully.') && + obj.successColor === 'greenBright' + ); + }); + + it('catches a bad progress result (-2)', async function () { + prepareStubs({ + restoreSnapshot: stub(stubbedCreateResponseSuccess), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, -2])), + getArtifacts: stub(getArtifactsResponse), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain('The snapshot failed to be restored.'); + } + }); + + it('catches 404 on progress response', async function () { + prepareStubs({ + restoreSnapshot: stub(stubbedCreateResponseSuccess), + getArtifacts: stub(getArtifactsResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, 50], 404)), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain('The snapshot does not exist'); + } + }); + + it('catches unknown on progress response', async function () { + prepareStubs({ + restoreSnapshot: stub(stubbedCreateResponseSuccess), + getArtifacts: stub(getArtifactsResponse), + getSnapshotProgress: stub(getSnapshotProgressResponse([20, 50], 500)), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain('An unknown error occurred.'); + } + }); + + it('catches a withCloudSdk exception - createSnapshot', async function () { + prepareStubs({ + restoreSnapshot: stubReject('Internal error'), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain( + 'There was an unexpected error when running a snapshot command.' + ); + } + }); + + it('catches a withCloudSdk exception - getSnapshotProgress', async function () { + prepareStubs({ + restoreSnapshot: stub(stubbedCreateResponseSuccess), + getSnapshotProgress: stubReject('Internal error'), + }); + + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.contain( + 'There was an unexpected error when running a snapshot command.' + ); + } + }); + + it('calls the appropriate api and shows failures', async function () { + const checkError = async (statusCode, statusDetails, expectedMessage) => { + prepareStubs({ + restoreSnapshot: stub( + stubbedRestoreResponseFailure(statusCode, statusDetails) + ), + getSnapshotProgress: stub(getSnapshotProgressResponse()), + getArtifacts: stub(getArtifactsResponse), + }); + try { + await command.runCommand([], {}); + assert.fail('Expected command to throw an error'); + } catch (err) { + expect(err).to.be.an('error'); + expect(err.message).to.include(expectedMessage); + } + expect(stopAllSpinniesSpy.callCount).to.equal(1); + }; + + await checkError(400, '', 'The given environment is not an RDE'); + await checkError( + 404, + 'The requested environment or program does not exist.', + 'The environment or program does not exist' + ); + await checkError( + 404, + 'The requested snapshot does not exist.', + 'The snapshot does not exist' + ); + await checkError( + 404, + 'The snapshot is in deleted state.', + 'The snapshot is in deleted state, change the state to available before restoring.' + ); + + await checkError( + 406, + '', + 'The RDE is not in a state where a snapshot can be created or restored.' + ); + + + await checkError( + 503, + '', + 'AEM instances are receiving a deployment and new packages are not accepted temporarily until the instances are done.' + ); + await checkError(500, '', 'An unknown error occurred.'); + }); + }); +}); From f414c18a1d207707be2b02ee59de51bb061019f7 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Mon, 21 Jul 2025 12:05:01 +0200 Subject: [PATCH 57/67] Linting fix --- src/commands/aem/rde/snapshot/restore.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/aem/rde/snapshot/restore.js b/src/commands/aem/rde/snapshot/restore.js index 58cbf52..2fb3483 100644 --- a/src/commands/aem/rde/snapshot/restore.js +++ b/src/commands/aem/rde/snapshot/restore.js @@ -27,7 +27,6 @@ const { loadAllArtifacts } = require('../../../../lib/rde-utils'); const Spinnies = require('spinnies'); class RestoreSnapshot extends BaseCommand { - constructor(argv, config, sleepTime = 5000) { super(argv, config); this.sleepTime = sleepTime; From b4ef73f09d9eb302602b49294f705a63304dc933 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Mon, 21 Jul 2025 13:03:09 +0200 Subject: [PATCH 58/67] wip - wrap up test coverage for commands --- test/commands/aem/rde/snapshot/delete.test.js | 7 +- .../aem/rde/snapshot/undelete.test.js | 190 ++++++++++++++++++ 2 files changed, 195 insertions(+), 2 deletions(-) diff --git a/test/commands/aem/rde/snapshot/delete.test.js b/test/commands/aem/rde/snapshot/delete.test.js index f4efdd3..16f974e 100644 --- a/test/commands/aem/rde/snapshot/delete.test.js +++ b/test/commands/aem/rde/snapshot/delete.test.js @@ -112,8 +112,11 @@ describe('DeleteSnapshots', function () { if (isSingle) { assert.equal(cloudSdkApiStub.deleteSnapshot.callCount, 1); - }else{ - assert.equal(cloudSdkApiStub.deleteSnapshot.callCount, snapshots.length); + } else { + assert.equal( + cloudSdkApiStub.deleteSnapshot.callCount, + snapshots.length + ); } const output = command.log.getCapturedLogOutput(); diff --git a/test/commands/aem/rde/snapshot/undelete.test.js b/test/commands/aem/rde/snapshot/undelete.test.js index e69de29..cdeb559 100644 --- a/test/commands/aem/rde/snapshot/undelete.test.js +++ b/test/commands/aem/rde/snapshot/undelete.test.js @@ -0,0 +1,190 @@ +const assert = require('assert'); +const { expect } = require('chai'); +const sinon = require('sinon'); +const UnDeleteSnapshot = require('../../../../../src/commands/aem/rde/snapshot/undelete'); +const { snapshotsResponse, snapshots } = require('./snapshots.mocks'); + +/** + * + * @param sinon + * @param command + * @param methods + */ +function createCloudSdkAPIStub(sinon, command, methods) { + const cloudSdkApiStub = {}; + Object.keys(methods).forEach((k) => { + cloudSdkApiStub[k] = methods[k]; + }); + sinon + .stub(command, 'withCloudSdk') + .callsFake(async (fn) => fn(cloudSdkApiStub)); + return [command, cloudSdkApiStub]; +} + +/** + * + * @param sinon + * @param command + */ +function setupLogCapturing(sinon, command) { + const logs = []; + sinon.stub(command, 'doLog').callsFake((msg) => logs.push(msg)); + command.log = { getCapturedLogOutput: () => logs.join('\n') }; +} + +describe('UnDeleteSnapshots', function () { + describe('#run commands', function () { + let command, cloudSdkApiStub; + + const stubbedUnDeleteResponse = (status, details) => ({ + status, + json: async () => ({ + details, + }), + }); + + const stubCommandResponse = (status, details) => { + [command, cloudSdkApiStub] = createCloudSdkAPIStub(sinon, command, { + getSnapshots: sinon.stub().resolves(snapshotsResponse), + undeleteSnapshot: sinon + .stub() + .resolves(stubbedUnDeleteResponse(status, details)), + }); + }; + + beforeEach(() => { + command = new UnDeleteSnapshot([], {}); + sinon.stub(command, 'spinnerStart'); + sinon.stub(command, 'spinnerStop'); + setupLogCapturing(sinon, command); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('calls undeleteSnapshots successfully', async () => { + stubCommandResponse(200); + + await command.runCommand( + { + name: 'snap1', + }, + { + force: false, + } + ); + expect(cloudSdkApiStub.undeleteSnapshot.calledOnce).to.be.true; + const output = command.log.getCapturedLogOutput(); + expect(output).to.include('Snapshot snap1 undeleted successfully.'); + + expect(command.spinnerStart.calledOnce).to.be.true; + expect(command.spinnerStop.calledOnce).to.be.true; + expect(cloudSdkApiStub.undeleteSnapshot.calledWith('snap1')).to.be.true; + }); + + const executeWithErrorExpected = async ( + snapshotName, + force, + status, + statusMessage, + expectedMessage + ) => { + const isSingle = snapshotName && snapshotName !== 'all'; + stubCommandResponse(status, statusMessage); + try { + const args = {}; + const flags = { + force, + }; + if (isSingle) { + args.name = snapshotName; + } else { + flags.all = true; + } + await command.runCommand(args, flags); + assert.fail('Expected an error to be thrown'); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.include(expectedMessage); + } + + if (isSingle) { + assert.equal(cloudSdkApiStub.undeleteSnapshot.callCount, 1); + } else { + assert.equal( + cloudSdkApiStub.undeleteSnapshot.callCount, + snapshots.length + ); + } + + const output = command.log.getCapturedLogOutput(); + expect(command.spinnerStart.called).to.be.true; + expect(command.spinnerStop.called).to.be.true; + if (isSingle) { + expect(cloudSdkApiStub.undeleteSnapshot.calledWith('snap1')).to.be.true; + } else { + for (const snapshot of snapshots) { + expect(cloudSdkApiStub.undeleteSnapshot.calledWith(snapshot.name)).to + .be.true; + } + } + }; + + describe('single snapshot deletion', function () { + it('reacts to error code 404 appropriately - 1', async () => + executeWithErrorExpected( + 'snap1', + false, + 404, + 'The requested environment or program does not exist.', + 'The environment or program does not exist' + )); + + it('reacts to error code 404 appropriately - 2', async () => + executeWithErrorExpected( + 'snap1', + false, + 404, + 'The requested snapshot does not exist.', + 'The snapshot does not exist' + )); + + it('reacts to error code 400 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 400, + null, + 'The given environment is not an RDE' + )); + + it('reacts to error code 451 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 451, + null, + 'The feature is part of the EAP program and not available for general use.' + )); + + it('reacts to error code 507 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 507, + null, + 'Reached the maximum number or diskspace of snapshots.' + )); + + it('reacts to error code 500 appropriately', async () => + executeWithErrorExpected( + 'snap1', + false, + 500, + null, + 'An unknown error occurred.' + )); + }); + }); +}); From ef4dd7ff3fb13c8f5772c63061b1238f332ef629 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Wed, 23 Jul 2025 13:23:38 +0200 Subject: [PATCH 59/67] Add in the force option --- src/commands/aem/rde/reset.js | 21 +++++++++++++++++-- src/lib/cloud-sdk-api.js | 18 +++++++++------- .../commands/aem/rde/snapshot/restore.test.js | 7 +++---- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/commands/aem/rde/reset.js b/src/commands/aem/rde/reset.js index 2a9d57b..6a63a13 100644 --- a/src/commands/aem/rde/reset.js +++ b/src/commands/aem/rde/reset.js @@ -24,9 +24,18 @@ class ResetCommand extends BaseCommand { try { const result = this.jsonResult(); this.doLog(`Reset cm-p${this._programId}-e${this._environmentId}`); - this.spinnerStart('resetting environment'); + this.spinnerStart( + 'resetting environment ... ' + + flags['keep-mutable-content'] + + ' ' + + flags.force + ); const status = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.resetEnv(flags.wait, flags['keep-mutable-content']) + cloudSdkAPI.resetEnv( + flags.wait, + flags['keep-mutable-content'], + flags.force + ) ); this.spinnerStop(); if (flags.wait) { @@ -69,6 +78,14 @@ Object.assign(ResetCommand, { default: false, multiple: false, }), + force: Flags.boolean({ + char: 'f', + multiple: false, + required: false, + default: false, + description: + 'Force resets the RDE, not re-using a previously generated base repository. Can be used in case of issues but takes longer.', + }), wait: Flags.boolean({ description: 'Do or do not wait for completion of the reset operation. Progress can be manually checked using the "status" command.', diff --git a/src/lib/cloud-sdk-api.js b/src/lib/cloud-sdk-api.js index 3ea98cf..e73c175 100644 --- a/src/lib/cloud-sdk-api.js +++ b/src/lib/cloud-sdk-api.js @@ -239,13 +239,14 @@ class CloudSdkAPI { } async restoreSnapshot(name, params) { - params = { - ...params, + const queryString = this.createUrlQueryStr({ programId: this.programId, environmentId: this.environmentId, - }; - const queryString = this.createUrlQueryStr(params); - return await this._snapshotClient.doPost(`/${name}/restore${queryString}`); + }); + return await this._snapshotClient.doPost( + `/${name}/restore${queryString}`, + params + ); } async getLogs(id) { @@ -583,13 +584,14 @@ class CloudSdkAPI { } } - async resetEnv(wait, keepMutableContent) { + async resetEnv(wait, keepMutableContent, force) { await this._checkRDE(); // later we should fold everything into the RDE API and not use the CM API - if (keepMutableContent) { + if (keepMutableContent || force) { const result = await this._rdeClient.doPost(`/runtime/reset`, { - 'keep-mutable-content': 'true', + 'keep-mutable-content': keepMutableContent, + force, }); if (result.status !== 201) { diff --git a/test/commands/aem/rde/snapshot/restore.test.js b/test/commands/aem/rde/snapshot/restore.test.js index 1fe8fea..1490178 100644 --- a/test/commands/aem/rde/snapshot/restore.test.js +++ b/test/commands/aem/rde/snapshot/restore.test.js @@ -313,12 +313,11 @@ describe('RestoreSnapshot', function () { ); await checkError( - 406, - '', - 'The RDE is not in a state where a snapshot can be created or restored.' + 406, + '', + 'The RDE is not in a state where a snapshot can be created or restored.' ); - await checkError( 503, '', From dea1bf7abbb6dac33294465c1476690496926338 Mon Sep 17 00:00:00 2001 From: Niek Raaijmakers Date: Wed, 23 Jul 2025 13:51:25 +0200 Subject: [PATCH 60/67] Remove the debugging logs --- src/commands/aem/rde/reset.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/commands/aem/rde/reset.js b/src/commands/aem/rde/reset.js index 6a63a13..22d86cc 100644 --- a/src/commands/aem/rde/reset.js +++ b/src/commands/aem/rde/reset.js @@ -24,12 +24,7 @@ class ResetCommand extends BaseCommand { try { const result = this.jsonResult(); this.doLog(`Reset cm-p${this._programId}-e${this._environmentId}`); - this.spinnerStart( - 'resetting environment ... ' + - flags['keep-mutable-content'] + - ' ' + - flags.force - ); + this.spinnerStart('resetting environment ... '); const status = await this.withCloudSdk((cloudSdkAPI) => cloudSdkAPI.resetEnv( flags.wait, From 9973f45921dd65b95b601a25ccf6aced41ff6a7d Mon Sep 17 00:00:00 2001 From: rliechti Date: Tue, 29 Jul 2025 10:30:11 +0200 Subject: [PATCH 61/67] fix tests --- test/commands/aem/rde/snapshot/index.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/commands/aem/rde/snapshot/index.test.js b/test/commands/aem/rde/snapshot/index.test.js index c56a74d..e11ab4e 100644 --- a/test/commands/aem/rde/snapshot/index.test.js +++ b/test/commands/aem/rde/snapshot/index.test.js @@ -4,7 +4,7 @@ const ListSnapshots = require('../../../../../src/commands/aem/rde/snapshot'); const internalErrors = require('../../../../../src/lib/internal-errors'); const configErrors = require('../../../../../src/lib/configuration-errors'); const errorHelpers = require('../../../../../src/lib/error-helpers'); -const { snapshots, response } = require('./snapshots.mocks'); +const { snapshots, snapshotsResponse } = require('./snapshots.mocks'); /** * @@ -58,6 +58,7 @@ describe('ListSnapshots', function () { }); it('calls getSnapshots and logs table output for non-empty items', async function () { + cloudSdkApiStub.getSnapshots.resolves(snapshotsResponse); await command.runCommand([], {}); expect(cloudSdkApiStub.getSnapshots.calledOnce).to.be.true; const output = command.log.getCapturedLogOutput(); @@ -127,6 +128,7 @@ describe('ListSnapshots', function () { }); it('returns result object with status and snapshots', async function () { + cloudSdkApiStub.getSnapshots.resolves(snapshotsResponse); const result = await command.runCommand([], {}); expect(result.status).to.equal(200); expect(result.snapshots).to.exist; From 6e19c6d114ce8fd2cbbba1efaaced01b018fedda Mon Sep 17 00:00:00 2001 From: rliechti Date: Thu, 7 Aug 2025 13:18:59 +0200 Subject: [PATCH 62/67] check for stuck snapshot creation --- src/commands/aem/rde/snapshot/create.js | 25 +++++++++++++++++++++---- src/lib/snapshot-errors.js | 4 ++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/commands/aem/rde/snapshot/create.js b/src/commands/aem/rde/snapshot/create.js index 2453102..a6d0009 100644 --- a/src/commands/aem/rde/snapshot/create.js +++ b/src/commands/aem/rde/snapshot/create.js @@ -95,10 +95,12 @@ class CreateSnapshots extends BaseCommand { throw new internalCodes.UNKNOWN(); } + let currentProgress = -1; + let lastProgressChange = new Date(); let lastProgress = -1; result.waitingforbackend = new Date(); - while (lastProgress < 100) { + while (currentProgress < 100) { let progressResponse; try { progressResponse = await this.withCloudSdk((cloudSdkAPI) => @@ -119,7 +121,8 @@ class CreateSnapshots extends BaseCommand { if (progressResponse.status === 200) { const json = await progressResponse.json(); - lastProgress = json?.progressPercentage; + lastProgress = currentProgress; + currentProgress = json?.progressPercentage; } else if (progressResponse.status === 404) { spinnies?.stopAll('fail'); throw new snapshotCodes.SNAPSHOT_NOT_FOUND(); @@ -130,7 +133,7 @@ class CreateSnapshots extends BaseCommand { throw new internalCodes.UNKNOWN(); } - if (lastProgress > 0 && !result.processnigsnapshotstarted) { + if (currentProgress > 0 && !result.processnigsnapshotstarted) { const took = this.formatElapsedTime( result.waitingforbackend, Date.now() @@ -148,10 +151,24 @@ class CreateSnapshots extends BaseCommand { throw new snapshotCodes.SNAPSHOT_CREATION_FAILED(); } + if (currentProgress > lastProgress) { + lastProgressChange = new Date(); + } else if (lastProgressChange.getTime() + 1000 * 60 * 15 < Date.now()) { + // If the progress hasn't changed in 15 minutes, we assume the process is stuck. + spinnies?.stopAll('fail'); + this.doLog( + chalk.red( + 'The snapshot creation seems stuck. Either the snapshot is huge and takes a long time, or the backend is not responding. Please monitor snapshot creation using the list of snapshots to check on the state. If the snapshot is stuck for more than an hour, please contact support.' + ) + ); + this.notify('failed', 'Snapshot creation is stuck.'); + throw new snapshotCodes.SNAPSHOT_CREATION_STUCK(); + } + await sleepMillis(this.sleepTime); } - if (lastProgress === 100) { + if (currentProgress === 100) { const took = this.formatElapsedTime( result.processnigsnapshotstarted, Date.now() diff --git a/src/lib/snapshot-errors.js b/src/lib/snapshot-errors.js index 9a9bd42..519c8c9 100644 --- a/src/lib/snapshot-errors.js +++ b/src/lib/snapshot-errors.js @@ -71,6 +71,10 @@ E( 'SNAPSHOT_CREATION_FAILED', 'The snapshot failed to be created. Please contact support.' ); +E( + 'SNAPSHOT_CREATION_STUCK', + 'The snapshot creation seems stuck. Either the snapshot is huge and takes a long time, or the backend is not responding. Please monitor snapshot creation using the list of snapshots to check on the state. If the snapshot is stuck for some hours, please contact support.' +); E( 'SNAPSHOT_RESTORE_FAILED', 'The snapshot failed to be restored. Please try again. When this still happens, reset the RDE and try again. Otherwise contact support.' From 66023893443f3abca880648c8c7152b77d70f82f Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 8 Sep 2025 10:26:16 +0200 Subject: [PATCH 63/67] SKYOPS-116391 support AIO-cli 11.x --- package-lock.json | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26d814b..4c0ad2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "source-map-support": "^0.5.21" }, "engines": { - "node": "^16.13 || ^18 || ^20 || >= 22.15 <23", + "node": "^18 || ^20 || >= 22.15 <23", "npm": ">= 8.0.0" } }, diff --git a/package.json b/package.json index a58cfe7..ba562c4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Adobe Inc.", "engines": { "npm": ">= 8.0.0", - "node": "^16.13 || ^18 || ^20 || >= 22.15 <23" + "node": "^18 || ^20 || >= 22.15 <23" }, "dependencies": { "@adobe/aio-lib-cloudmanager": "^3.1.0", @@ -87,4 +87,4 @@ } } } -} +} \ No newline at end of file From c5e037d823a3fdbf4e12e6d76605dc2cce20f20f Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 8 Sep 2025 10:47:32 +0200 Subject: [PATCH 64/67] SKYOPS-116391 auto fix vulnerabilities --- package-lock.json | 669 +++++++++++++++++++--------------------------- 1 file changed, 278 insertions(+), 391 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c0ad2a..ba3b0f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -701,89 +701,20 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/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==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", @@ -973,19 +904,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1000,109 +933,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/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==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.28.4" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -1316,14 +1168,15 @@ } }, "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1360,14 +1213,14 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1516,6 +1369,27 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.1.tgz", + "integrity": "sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.0", + "iconv-lite": "^0.6.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@inquirer/testing": { "version": "2.1.19", "resolved": "https://registry.npmjs.org/@inquirer/testing/-/testing-2.1.19.tgz", @@ -2983,10 +2857,11 @@ } }, "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3486,9 +3361,10 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3690,6 +3566,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3832,9 +3721,10 @@ } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", + "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "license": "MIT" }, "node_modules/check-error": { "version": "2.1.1", @@ -4285,9 +4175,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4352,11 +4243,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -4510,10 +4402,11 @@ } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -4563,6 +4456,20 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -4702,14 +4609,10 @@ "peer": true }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -4718,18 +4621,15 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "peer": true, "engines": { "node": ">= 0.4" } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "peer": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -4738,15 +4638,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "peer": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -5463,30 +5363,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor/node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/extract-stack": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/extract-stack/-/extract-stack-2.0.0.tgz", @@ -5622,9 +5498,10 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5788,12 +5665,15 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -5863,7 +5743,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5926,17 +5805,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "peer": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5953,6 +5836,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -6022,9 +5918,10 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -6103,13 +6000,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6182,11 +6078,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "peer": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6198,8 +6093,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "peer": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -6239,7 +6132,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -6394,11 +6286,12 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -6506,15 +6399,16 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "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", - "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.21", "mute-stream": "0.0.8", @@ -7793,7 +7687,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -8274,6 +8169,15 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -8300,11 +8204,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -8366,31 +8271,32 @@ "dev": true }, "node_modules/mocha": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", - "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -8401,10 +8307,11 @@ } }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -8421,10 +8328,11 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -8432,12 +8340,6 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/mocha/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -8463,9 +8365,10 @@ "dev": true }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/mute-stream": { "version": "0.0.8", @@ -9025,14 +8928,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -9186,10 +9081,11 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -9210,9 +9106,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -9540,10 +9437,11 @@ "dev": true }, "node_modules/qqjs/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, + "license": "MIT", "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -9796,6 +9694,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -9830,9 +9729,10 @@ } }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -10159,7 +10059,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/sax": { "version": "1.3.0", @@ -10205,10 +10106,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -10348,15 +10250,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/sinon/node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/sinon/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10862,10 +10755,11 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "dev": true, + "license": "MIT", "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -11001,15 +10895,6 @@ "optional": true, "peer": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11527,10 +11412,11 @@ } }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -11679,10 +11565,11 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } From 0093c7dc7da43af692fb4702cde55f2ce98fd860 Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 8 Sep 2025 11:03:33 +0200 Subject: [PATCH 65/67] SKYOPS-116391 hide snapshotting commands (experimental features) --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ba562c4..a635c96 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,8 @@ "commands": "./src/commands", "bin": "aio", "experimental-features": [ - "aem:rde:inspect" + "aem:rde:inspect", + "aem:rde:snapshot" ], "hooks": { "init": "./src/lib/hooks/experimental-features-init-hook" @@ -84,6 +85,9 @@ }, "aem:rde:inspect": { "description": "Inspects the RapidDev Environments (experimental)." + }, + "aem:rde:snapshot": { + "description": "Provides functionality for managing the RapidDev Environments snapshots (experimental)." } } } From 869026764e82bc4e967c496559d4679d8e45805b Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 8 Sep 2025 11:19:45 +0200 Subject: [PATCH 66/67] SKYOPS-116391 remove unused clean command --- .aio_localdev | 35 ++++++++++++++++ src/commands/aem/rde/clean.js | 78 ----------------------------------- 2 files changed, 35 insertions(+), 78 deletions(-) create mode 100644 .aio_localdev delete mode 100644 src/commands/aem/rde/clean.js diff --git a/.aio_localdev b/.aio_localdev new file mode 100644 index 0000000..7be5d1b --- /dev/null +++ b/.aio_localdev @@ -0,0 +1,35 @@ +{ + ims: { + contexts: { + fccsstage: { + cli.bare-output: false, + env: "stage", + access_token: { + token: "eyJhbGciOiJSUzI1NiIsIng1dSI6Imltc19uYTEtc3RnMS1rZXktYXQtMS5jZXIiLCJraWQiOiJpbXNfbmExLXN0ZzEta2V5LWF0LTEiLCJpdHQiOiJhdCJ9.eyJpZCI6IjE3NTQ5ODc0Njk2MDhfMWU3YTZmMjMtN2MwZi00MjVkLWJmY2YtY2JlMTMwNjc4NzBhX3V3MiIsInR5cGUiOiJhY2Nlc3NfdG9rZW4iLCJjbGllbnRfaWQiOiJhaW8tY2xpLWNvbnNvbGUtYXV0aC1zdGFnZSIsInVzZXJfaWQiOiIxQTM0MUEzQjY1NUY2MDYzMEE0OTQwMERAZjcxMjYxZjQ2MjY5MjcwNTQ5NDEyOC5lIiwic3RhdGUiOiJ7XCJpZFwiOlwiZDI1MTAxNDBcIixcImNvZGVfdHlwZVwiOlwiYWNjZXNzX3Rva2VuXCIsXCJjbGllbnRfaWRcIjpcImFpby1jbGktY29uc29sZS1hdXRoLXN0YWdlXCIsXCJwb3J0XCI6XCI1MzA4MFwiLFwiZW52XCI6XCJzdGFnZVwifSIsImFzIjoiaW1zLW5hMS1zdGcxIiwiYWFfaWQiOiI1NzY5MUQxNzY0MUM5NjNGMEE0OTQyMkZAYzYyZjI0Y2M1YjViN2UwZTBhNDk0MDA0IiwiY3RwIjowLCJmZyI6IlpXRk5DR0U3WloyVkI0RFpaR1pNQVNJQTRRIiwic2lkIjoiMTc1NDk4NzQ0MTg3MF9hOTg3N2RmOC0yNzg5LTRjZmQtOTIzMC03MzAxNDQ5ZjQ0NTZfdmE2YzIiLCJydGlkIjoiMTc1NDk4NzQ2OTYwOV8yY2FjNGRhYi1jMTMwLTQzN2UtYjMxYy0yOTA2NjY0ZGQ4YmNfdXcyIiwibW9pIjoiZjJkZDlkZDciLCJwYmEiOiJNZWRTZWNOb0VWLExvd1NlYyIsInJ0ZWEiOiIxNzU2MTk3MDY5NjA5IiwiZXhwaXJlc19pbiI6Ijg2NDAwMDAwIiwic2NvcGUiOiJzZXJ2aWNlX3ByaW5jaXBhbHMucmVhZCxhY2NvdW50X2NsdXN0ZXIucmVhZCxvcGVuaWQsQWRvYmVJRCxyZWFkX29yZ2FuaXphdGlvbnMsZ25hdix1bmlmaWVkX2Rldl9wb3J0YWwscmVhZF9wYy5kbWFfYnVsbHNleWUsYWRvYmVpb19hcGksc2VydmljZV9wcmluY2lwYWxzLndyaXRlLHJlYWRfY2xpZW50X3NlY3JldCxhZGRpdGlvbmFsX2luZm8ucm9sZXMsbWFuYWdlX2NsaWVudF9zZWNyZXRzLGFkZGl0aW9uYWxfaW5mby5wcm9qZWN0ZWRQcm9kdWN0Q29udGV4dCIsImNyZWF0ZWRfYXQiOiIxNzU0OTg3NDY5NjA4In0.EKezLXa-BdMIRKdUwt-kQEVQE7q5RNsj6yoX-EPX7VJEnKs1tuzLW0_bjVI_3ggdY-UAzKXHlgMp8GqMq3F-7Z1AOSrkX9JEe445jpO-0B7l4ZY__NW_CBDKr432zAbbkeWRo1p7NU_WUlZD2505uy1NFAsHjrML9WotxlV6hC-NKYIjc_m4MJjO9Zg3irz0KxBelHTXCTzKxk09Oo9WcXQiaZTtlGkpqfEc4D7HkIlgBA8Gqq0x9E8kNwC8RPIOeZ2pNquggwbXOhmrEQwwI4HLiQi68gyHNbS1cyOY5fd9NuEa0ev5dRl-QSgC11pKewdzDscTcmXhcvVZbymTkw", + expiry: 1755073869608 + }, + refresh_token: { + token: "eyJhbGciOiJSUzI1NiIsIng1dSI6Imltc19uYTEtc3RnMS1rZXktcnQtMS5jZXIiLCJraWQiOiJpbXNfbmExLXN0ZzEta2V5LXJ0LTEiLCJpdHQiOiJydCJ9.eyJpZCI6IjE3NTQ5ODc0Njk2MDlfMmNhYzRkYWItYzEzMC00MzdlLWIzMWMtMjkwNjY2NGRkOGJjX3V3MiIsInR5cGUiOiJyZWZyZXNoX3Rva2VuIiwiY2xpZW50X2lkIjoiYWlvLWNsaS1jb25zb2xlLWF1dGgtc3RhZ2UiLCJ1c2VyX2lkIjoiMUEzNDFBM0I2NTVGNjA2MzBBNDk0MDBEQGY3MTI2MWY0NjI2OTI3MDU0OTQxMjguZSIsInN0YXRlIjoie1wiaWRcIjpcImQyNTEwMTQwXCIsXCJjb2RlX3R5cGVcIjpcImFjY2Vzc190b2tlblwiLFwiY2xpZW50X2lkXCI6XCJhaW8tY2xpLWNvbnNvbGUtYXV0aC1zdGFnZVwiLFwicG9ydFwiOlwiNTMwODBcIixcImVudlwiOlwic3RhZ2VcIn0iLCJhcyI6Imltcy1uYTEtc3RnMSIsImFhX2lkIjoiNTc2OTFEMTc2NDFDOTYzRjBBNDk0MjJGQGM2MmYyNGNjNWI1YjdlMGUwYTQ5NDAwNCIsImZnIjoiWldGTkNHRTdaWjJWQjREWlpHWk1BU0lBNFEiLCJzaWQiOiIxNzU0OTg3NDQxODcwX2E5ODc3ZGY4LTI3ODktNGNmZC05MjMwLTczMDE0NDlmNDQ1Nl92YTZjMiIsImV4cGlyZXNfaW4iOiIxMjA5NjAwMDAwIiwic2NvcGUiOiJ1bmlmaWVkX2Rldl9wb3J0YWwsQWRvYmVJRCxvcGVuaWQsYWNjb3VudF9jbHVzdGVyLnJlYWQsZ25hdixyZWFkX29yZ2FuaXphdGlvbnMsYWRkaXRpb25hbF9pbmZvLnByb2plY3RlZFByb2R1Y3RDb250ZXh0LGFkZGl0aW9uYWxfaW5mby5yb2xlcyxyZWFkX3BjLmRtYV9idWxsc2V5ZSxhZG9iZWlvX2FwaSxyZWFkX2NsaWVudF9zZWNyZXQsbWFuYWdlX2NsaWVudF9zZWNyZXRzLHNlcnZpY2VfcHJpbmNpcGFscy5yZWFkLHNlcnZpY2VfcHJpbmNpcGFscy53cml0ZSIsImNyZWF0ZWRfYXQiOiIxNzU0OTg3NDY5NjA5In0.JupFPPEXzrdr9vbpUl6smJARfz0KSHvqN7GRp2jG_SS8FQ0sNW79ZN9Rhr1j98IIjJSqJ8Zz6kzL9U8KWX4wA_AWv6FTG8PLyOGPhl6iEPduEZNEVam_YwKN0RXKDiSaxcVstnq90WTb_lbn1Vp50TOuqTKgTw2cBHD17OCKSngrTnybJFZFGzaG3wDeoMiCmvkOa1dT3cn9S7Bc0S6JUAfNss17DsrjW6v4Z2JzXAorsfxapVqtzfRI6HL_7UHF7GV1oa5Ud5kJpHN2wKU76IEgQ-tGOsT5lzbl6EVx8afCyk0IYk1bt_2DF68iIzbuJxB6vW6wsmiYdBOE3uJdMQ", + expiry: 1756197069609 + } + } + }, + config: { + current: "fccsstage" + } + }, + cloudmanager_orgid: "F4646ED9626926AA0A49420E@AdobeOrg", + cloudmanager_programid: "84002", + cloudmanager_environmentid: "318764", + cloudmanager_programname: "cod18017-rde-development", + cloudmanager_environmentname: "cod35102-RDE-05", + aem-rde: { + dev-console-url-cache: { + cm-p84002-e318764: { + devConsoleUrl: "http://localhost:3001/", + rdeApiUrl: "http://localhost:3001/api/rde" + } + } + }, + rde_enableNotifications: true +} \ No newline at end of file diff --git a/src/commands/aem/rde/clean.js b/src/commands/aem/rde/clean.js deleted file mode 100644 index 4252787..0000000 --- a/src/commands/aem/rde/clean.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022 Adobe Inc. All rights reserved. - * This file is licensed to you under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - * OF ANY KIND, either express or implied. See the License for the specific language - * governing permissions and limitations under the License. - */ -'use strict'; - -const { BaseCommand, Flags } = require('../../../lib/base-command'); -const { - codes: configurationCodes, -} = require('../../../lib/configuration-errors'); -const { codes: internalCodes } = require('../../../lib/internal-errors'); -const { throwAioError } = require('../../../lib/error-helpers'); -const chalk = require('chalk'); - -class CleanEnvrionment extends BaseCommand { - async runCommand(args, flags) { - let response; - try { - this.spinnerStart(`Cleaning the rde...`); - response = await this.withCloudSdk((cloudSdkAPI) => - cloudSdkAPI.cleanEnv(args.name, flags['drop-content']) - ); - } catch (err) { - this.spinnerStop(); - throwAioError( - err, - new internalCodes.INTERNAL_CLEAN_ERROR({ messageValues: err }) - ); - } - this.spinnerStop(); - if (response?.status === 200) { - this.doLog( - chalk.green( - `RDE cleaned sucessfully. Use 'aio aem rde status' to view the updated state.` - ) - ); - } else if (response?.status === 400) { - throw new configurationCodes.DIFFERENT_ENV_TYPE(); - } else if (response?.status === 404) { - throw new configurationCodes.PROGRAM_OR_ENVIRONMENT_NOT_FOUND(); - } else if (response?.status === 503) { - throw new internalCodes.INVALID_STATE(); - } else { - throw new internalCodes.UNKNOWN(); - } - } -} - -Object.assign(CleanEnvrionment, { - description: 'Old aliases', - aliases: ['aem:rde:suuber'], - deprecateAliases: true, -}); - -Object.assign(CleanEnvrionment, { - description: - 'Removes the deployment and upgrades to latest AEM version while keeping the content. Gives an option to delete the content.', - args: [], - aliases: [], - flags: { - 'drop-content': Flags.boolean({ - description: 'Also delete the content of the environment.', - char: 'd', - multiple: false, - required: false, - default: false, - }), - }, -}); - -module.exports = CleanEnvrionment; From e90f304e36273e35f5e77e956f4fa7403f7b8a46 Mon Sep 17 00:00:00 2001 From: rliechti Date: Mon, 8 Sep 2025 11:26:58 +0200 Subject: [PATCH 67/67] SKYOPS-116391 remove .aio config --- .aio_localdev | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .aio_localdev diff --git a/.aio_localdev b/.aio_localdev deleted file mode 100644 index 7be5d1b..0000000 --- a/.aio_localdev +++ /dev/null @@ -1,35 +0,0 @@ -{ - ims: { - contexts: { - fccsstage: { - cli.bare-output: false, - env: "stage", - access_token: { - token: "eyJhbGciOiJSUzI1NiIsIng1dSI6Imltc19uYTEtc3RnMS1rZXktYXQtMS5jZXIiLCJraWQiOiJpbXNfbmExLXN0ZzEta2V5LWF0LTEiLCJpdHQiOiJhdCJ9.eyJpZCI6IjE3NTQ5ODc0Njk2MDhfMWU3YTZmMjMtN2MwZi00MjVkLWJmY2YtY2JlMTMwNjc4NzBhX3V3MiIsInR5cGUiOiJhY2Nlc3NfdG9rZW4iLCJjbGllbnRfaWQiOiJhaW8tY2xpLWNvbnNvbGUtYXV0aC1zdGFnZSIsInVzZXJfaWQiOiIxQTM0MUEzQjY1NUY2MDYzMEE0OTQwMERAZjcxMjYxZjQ2MjY5MjcwNTQ5NDEyOC5lIiwic3RhdGUiOiJ7XCJpZFwiOlwiZDI1MTAxNDBcIixcImNvZGVfdHlwZVwiOlwiYWNjZXNzX3Rva2VuXCIsXCJjbGllbnRfaWRcIjpcImFpby1jbGktY29uc29sZS1hdXRoLXN0YWdlXCIsXCJwb3J0XCI6XCI1MzA4MFwiLFwiZW52XCI6XCJzdGFnZVwifSIsImFzIjoiaW1zLW5hMS1zdGcxIiwiYWFfaWQiOiI1NzY5MUQxNzY0MUM5NjNGMEE0OTQyMkZAYzYyZjI0Y2M1YjViN2UwZTBhNDk0MDA0IiwiY3RwIjowLCJmZyI6IlpXRk5DR0U3WloyVkI0RFpaR1pNQVNJQTRRIiwic2lkIjoiMTc1NDk4NzQ0MTg3MF9hOTg3N2RmOC0yNzg5LTRjZmQtOTIzMC03MzAxNDQ5ZjQ0NTZfdmE2YzIiLCJydGlkIjoiMTc1NDk4NzQ2OTYwOV8yY2FjNGRhYi1jMTMwLTQzN2UtYjMxYy0yOTA2NjY0ZGQ4YmNfdXcyIiwibW9pIjoiZjJkZDlkZDciLCJwYmEiOiJNZWRTZWNOb0VWLExvd1NlYyIsInJ0ZWEiOiIxNzU2MTk3MDY5NjA5IiwiZXhwaXJlc19pbiI6Ijg2NDAwMDAwIiwic2NvcGUiOiJzZXJ2aWNlX3ByaW5jaXBhbHMucmVhZCxhY2NvdW50X2NsdXN0ZXIucmVhZCxvcGVuaWQsQWRvYmVJRCxyZWFkX29yZ2FuaXphdGlvbnMsZ25hdix1bmlmaWVkX2Rldl9wb3J0YWwscmVhZF9wYy5kbWFfYnVsbHNleWUsYWRvYmVpb19hcGksc2VydmljZV9wcmluY2lwYWxzLndyaXRlLHJlYWRfY2xpZW50X3NlY3JldCxhZGRpdGlvbmFsX2luZm8ucm9sZXMsbWFuYWdlX2NsaWVudF9zZWNyZXRzLGFkZGl0aW9uYWxfaW5mby5wcm9qZWN0ZWRQcm9kdWN0Q29udGV4dCIsImNyZWF0ZWRfYXQiOiIxNzU0OTg3NDY5NjA4In0.EKezLXa-BdMIRKdUwt-kQEVQE7q5RNsj6yoX-EPX7VJEnKs1tuzLW0_bjVI_3ggdY-UAzKXHlgMp8GqMq3F-7Z1AOSrkX9JEe445jpO-0B7l4ZY__NW_CBDKr432zAbbkeWRo1p7NU_WUlZD2505uy1NFAsHjrML9WotxlV6hC-NKYIjc_m4MJjO9Zg3irz0KxBelHTXCTzKxk09Oo9WcXQiaZTtlGkpqfEc4D7HkIlgBA8Gqq0x9E8kNwC8RPIOeZ2pNquggwbXOhmrEQwwI4HLiQi68gyHNbS1cyOY5fd9NuEa0ev5dRl-QSgC11pKewdzDscTcmXhcvVZbymTkw", - expiry: 1755073869608 - }, - refresh_token: { - token: "eyJhbGciOiJSUzI1NiIsIng1dSI6Imltc19uYTEtc3RnMS1rZXktcnQtMS5jZXIiLCJraWQiOiJpbXNfbmExLXN0ZzEta2V5LXJ0LTEiLCJpdHQiOiJydCJ9.eyJpZCI6IjE3NTQ5ODc0Njk2MDlfMmNhYzRkYWItYzEzMC00MzdlLWIzMWMtMjkwNjY2NGRkOGJjX3V3MiIsInR5cGUiOiJyZWZyZXNoX3Rva2VuIiwiY2xpZW50X2lkIjoiYWlvLWNsaS1jb25zb2xlLWF1dGgtc3RhZ2UiLCJ1c2VyX2lkIjoiMUEzNDFBM0I2NTVGNjA2MzBBNDk0MDBEQGY3MTI2MWY0NjI2OTI3MDU0OTQxMjguZSIsInN0YXRlIjoie1wiaWRcIjpcImQyNTEwMTQwXCIsXCJjb2RlX3R5cGVcIjpcImFjY2Vzc190b2tlblwiLFwiY2xpZW50X2lkXCI6XCJhaW8tY2xpLWNvbnNvbGUtYXV0aC1zdGFnZVwiLFwicG9ydFwiOlwiNTMwODBcIixcImVudlwiOlwic3RhZ2VcIn0iLCJhcyI6Imltcy1uYTEtc3RnMSIsImFhX2lkIjoiNTc2OTFEMTc2NDFDOTYzRjBBNDk0MjJGQGM2MmYyNGNjNWI1YjdlMGUwYTQ5NDAwNCIsImZnIjoiWldGTkNHRTdaWjJWQjREWlpHWk1BU0lBNFEiLCJzaWQiOiIxNzU0OTg3NDQxODcwX2E5ODc3ZGY4LTI3ODktNGNmZC05MjMwLTczMDE0NDlmNDQ1Nl92YTZjMiIsImV4cGlyZXNfaW4iOiIxMjA5NjAwMDAwIiwic2NvcGUiOiJ1bmlmaWVkX2Rldl9wb3J0YWwsQWRvYmVJRCxvcGVuaWQsYWNjb3VudF9jbHVzdGVyLnJlYWQsZ25hdixyZWFkX29yZ2FuaXphdGlvbnMsYWRkaXRpb25hbF9pbmZvLnByb2plY3RlZFByb2R1Y3RDb250ZXh0LGFkZGl0aW9uYWxfaW5mby5yb2xlcyxyZWFkX3BjLmRtYV9idWxsc2V5ZSxhZG9iZWlvX2FwaSxyZWFkX2NsaWVudF9zZWNyZXQsbWFuYWdlX2NsaWVudF9zZWNyZXRzLHNlcnZpY2VfcHJpbmNpcGFscy5yZWFkLHNlcnZpY2VfcHJpbmNpcGFscy53cml0ZSIsImNyZWF0ZWRfYXQiOiIxNzU0OTg3NDY5NjA5In0.JupFPPEXzrdr9vbpUl6smJARfz0KSHvqN7GRp2jG_SS8FQ0sNW79ZN9Rhr1j98IIjJSqJ8Zz6kzL9U8KWX4wA_AWv6FTG8PLyOGPhl6iEPduEZNEVam_YwKN0RXKDiSaxcVstnq90WTb_lbn1Vp50TOuqTKgTw2cBHD17OCKSngrTnybJFZFGzaG3wDeoMiCmvkOa1dT3cn9S7Bc0S6JUAfNss17DsrjW6v4Z2JzXAorsfxapVqtzfRI6HL_7UHF7GV1oa5Ud5kJpHN2wKU76IEgQ-tGOsT5lzbl6EVx8afCyk0IYk1bt_2DF68iIzbuJxB6vW6wsmiYdBOE3uJdMQ", - expiry: 1756197069609 - } - } - }, - config: { - current: "fccsstage" - } - }, - cloudmanager_orgid: "F4646ED9626926AA0A49420E@AdobeOrg", - cloudmanager_programid: "84002", - cloudmanager_environmentid: "318764", - cloudmanager_programname: "cod18017-rde-development", - cloudmanager_environmentname: "cod35102-RDE-05", - aem-rde: { - dev-console-url-cache: { - cm-p84002-e318764: { - devConsoleUrl: "http://localhost:3001/", - rdeApiUrl: "http://localhost:3001/api/rde" - } - } - }, - rde_enableNotifications: true -} \ No newline at end of file