From c16ea38dc8b900ac696fe2c76c49a679f142d1db Mon Sep 17 00:00:00 2001 From: ajaz Date: Wed, 5 Nov 2025 14:21:18 +0530 Subject: [PATCH 1/5] feat: update deploy command to provision db --- README.md | 10 +-- package.json | 2 +- src/commands/app/deploy.js | 31 ++++++++ test/commands/app/deploy.test.js | 130 +++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 85dd50dc..e749166b 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ USAGE $ aio app create [PATH] [-v] [--version] [-i ] ARGUMENTS - PATH [default: .] Path to the app directory + [PATH] [default: .] Path to the app directory FLAGS -i, --import= Import an Adobe I/O Developer Console configuration file @@ -337,7 +337,7 @@ USAGE $ aio app delete action [ACTION-NAME] [-v] [--version] [-y] ARGUMENTS - ACTION-NAME Action `pkg/name` to delete, you can specify multiple actions via a comma separated list + [ACTION-NAME] Action `pkg/name` to delete, you can specify multiple actions via a comma separated list FLAGS -v, --verbose Verbose output @@ -543,7 +543,7 @@ USAGE | | ] [--confirm-new-workspace] [--use-jwt] [--github-pat ] [--linter none|basic|adobe-recommended] ARGUMENTS - PATH [default: .] Path to the app directory + [PATH] [default: .] Path to the app directory FLAGS -e, --extension=... Extension point(s) to implement @@ -674,7 +674,7 @@ USAGE $ aio app pack [PATH] [-v] [--version] [--lock-file] [-o ] ARGUMENTS - PATH [default: .] Path to the app directory to package + [PATH] [default: .] Path to the app directory to package FLAGS -o, --output= [default: dist/app.zip] The packaged app output file path @@ -773,7 +773,7 @@ USAGE [--confirm-new-workspace] [--no-service-sync | --confirm-service-sync] [--no-input] [--use-jwt] ARGUMENTS - CONFIG_FILE_PATH path to an Adobe I/O Developer Console configuration file + [CONFIG_FILE_PATH] path to an Adobe I/O Developer Console configuration file FLAGS -g, --global Use the global Adobe Developer Console Org / Project / Workspace configuration, diff --git a/package.json b/package.json index 90ae9bdb..a239af93 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Adobe Inc.", "bugs": "https://github.com/adobe/aio-cli-plugin-app/issues", "dependencies": { - "@adobe/aio-cli-lib-app-config": "^4.0.3", + "@adobe/aio-cli-lib-app-config": "../aio-cli-lib-app-config", "@adobe/aio-cli-lib-console": "^5.0.3", "@adobe/aio-lib-core-config": "^5", "@adobe/aio-lib-core-logging": "^3", diff --git a/src/commands/app/deploy.js b/src/commands/app/deploy.js index 84b400f9..91a03a65 100644 --- a/src/commands/app/deploy.js +++ b/src/commands/app/deploy.js @@ -203,6 +203,11 @@ class Deploy extends BuildCommand { this.error(err) } + // provision database if configured + if (config.manifest?.full?.database?.['auto-provision'] === true) { + await this.provisionDatabase(config, spinner, flags) + } + if (flags.actions) { if (config.app.hasBackend) { let filterEntities @@ -299,6 +304,32 @@ class Deploy extends BuildCommand { } } + async provisionDatabase (config, spinner, flags) { + const region = config.manifest?.full?.database?.region || 'amer' + + if (!config.manifest?.full?.database?.region) { + spinner.info(chalk.green('No region is configured for the database, deploying in default region amer')) + } + + const message = `Deploying database for region '${region}'` + + try { + spinner.start(message) + + if (flags.verbose) { + spinner.info(chalk.dim(`Running: aio app db provision --region ${region} --yes`)) + spinner.start(message) + } + + await this.config.runCommand('app:db:provision', ['--region', region, '--yes']) + + spinner.succeed(chalk.green(`Deployed database for application in region '${region}'`)) + } catch (error) { + spinner.fail(chalk.red('Database deployment failed')) + throw error + } + } + async publishExtensionPoints (deployConfigs, aioConfig, force) { const libConsoleCLI = await this.getLibConsoleCLI() diff --git a/test/commands/app/deploy.test.js b/test/commands/app/deploy.test.js index c6b7ada3..fe55faf5 100644 --- a/test/commands/app/deploy.test.js +++ b/test/commands/app/deploy.test.js @@ -18,6 +18,7 @@ const helpersActual = jest.requireActual('../../../src/lib/app-helper.js') const authHelpersActual = jest.requireActual('../../../src/lib/auth-helper') const open = require('open') +const ora = require('ora') const mockBundleFunc = jest.fn() jest.mock('../../../src/lib/app-helper.js') @@ -203,6 +204,7 @@ beforeEach(() => { command = new TheCommand([]) command.error = jest.fn() command.log = jest.fn() + command.warn = jest.fn() command.appConfig = cloneDeep(mockConfigData) command.appConfig.actions = { dist: 'actions' } command.appConfig.web.distProd = 'dist' @@ -1628,4 +1630,132 @@ describe('run', () => { expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1) expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1) }) + + test('should deploy successfully when auto-provision database is configured', async () => { + // Create app config with database auto-provision enabled + const appConfigWithDb = createAppConfig({ + ...command.appConfig, + manifest: { + full: { + database: { + 'auto-provision': true + } + }, + database: { + region: 'emea' + } + } + }) + + command.getAppExtConfigs.mockResolvedValueOnce(appConfigWithDb) + command.provisionDatabase = jest.fn().mockResolvedValue() + + await command.run() + + expect(command.error).toHaveBeenCalledTimes(0) + expect(command.provisionDatabase).toHaveBeenCalledTimes(1) + expect(command.provisionDatabase).toHaveBeenCalledWith( + appConfigWithDb.application, + expect.any(Object), // spinner + expect.objectContaining({ 'force-build': true }) // flags + ) + expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1) + expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1) + }) +}) + +describe('database provisioning', () => { + const createDatabaseConfig = (region = null, autoProvision = true) => ({ + manifest: { + full: { + database: { + 'auto-provision': autoProvision, + ...(region && { region }) + } + } + } + }) + + // Helper function to set up and run test + const runProvisionTest = async (config, flags, mockResult) => { + const spinner = ora() + + if (mockResult instanceof Error) { + command.config.runCommand.mockRejectedValueOnce(mockResult) + } else { + command.config.runCommand.mockResolvedValueOnce(mockResult) + } + + return { + result: await command.provisionDatabase(config, spinner, flags).catch(e => { throw e }), + spinner + } + } + + test('should use default region when not specified in manifest', async () => { + const config = createDatabaseConfig() + const flags = { verbose: false } + + const { spinner } = await runProvisionTest(config, flags) + + expect(spinner.info).toHaveBeenCalledWith(expect.stringContaining('No region is configured for the database, deploying in default region amer')) + expect(spinner.start).toHaveBeenCalledWith('Deploying database for region \'amer\'') + expect(command.config.runCommand).toHaveBeenCalledWith('app:db:provision', ['--region', 'amer', '--yes']) + expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application in region \'amer\'')) + }) + + test('should use configured region when specified in manifest', async () => { + const config = createDatabaseConfig('emea') + const flags = { verbose: false } + + const { spinner } = await runProvisionTest(config, flags) + + expect(spinner.info).not.toHaveBeenCalledWith(expect.stringContaining('No region is configured for the database')) + expect(spinner.start).toHaveBeenCalledWith('Deploying database for region \'emea\'') + expect(command.config.runCommand).toHaveBeenCalledWith('app:db:provision', ['--region', 'emea', '--yes']) + expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application in region \'emea\'')) + }) + + test('should show verbose output when verbose flag is true', async () => { + const config = createDatabaseConfig('amer') + const flags = { verbose: true } + + const { spinner } = await runProvisionTest(config, flags) + + expect(spinner.info).toHaveBeenCalledWith(expect.stringContaining('Running: aio app db provision --region amer --yes')) + expect(spinner.info).not.toHaveBeenCalledWith(expect.stringContaining('No region is configured for the database')) + expect(spinner.start).toHaveBeenCalledTimes(2) // Once initially, once after verbose info + expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application in region \'amer\'')) + }) + + test('should handle provision command failure', async () => { + const config = createDatabaseConfig('amer') + const flags = { verbose: false } + const error = new Error('Database provision failed') + + await expect(runProvisionTest(config, flags, error)) + .rejects.toThrow('Database provision failed') + + expect(command.warn).not.toHaveBeenCalled() + }) + + test('should show verbose error details when verbose flag is true and provision fails', async () => { + const config = createDatabaseConfig('amer') + const flags = { verbose: true } + const error = new Error('Database provision failed') + + await expect(runProvisionTest(config, flags, error)) + .rejects.toThrow('Database provision failed') + + expect(command.warn).not.toHaveBeenCalled() + }) + + test('should fail if invalid region specified', async () => { + const config = createDatabaseConfig('invalid-region') + const flags = { verbose: false } + const error = new Error('Invalid region: invalid-region') + + await expect(runProvisionTest(config, flags, error)) + .rejects.toThrow('Invalid region: invalid-region') + }) }) From 2e44d03e903d97c41f794e98f7a6deaca6c175c1 Mon Sep 17 00:00:00 2001 From: ajaz Date: Wed, 5 Nov 2025 17:41:05 +0530 Subject: [PATCH 2/5] chore: revert package.json README.md --- README.md | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e749166b..85dd50dc 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ USAGE $ aio app create [PATH] [-v] [--version] [-i ] ARGUMENTS - [PATH] [default: .] Path to the app directory + PATH [default: .] Path to the app directory FLAGS -i, --import= Import an Adobe I/O Developer Console configuration file @@ -337,7 +337,7 @@ USAGE $ aio app delete action [ACTION-NAME] [-v] [--version] [-y] ARGUMENTS - [ACTION-NAME] Action `pkg/name` to delete, you can specify multiple actions via a comma separated list + ACTION-NAME Action `pkg/name` to delete, you can specify multiple actions via a comma separated list FLAGS -v, --verbose Verbose output @@ -543,7 +543,7 @@ USAGE | | ] [--confirm-new-workspace] [--use-jwt] [--github-pat ] [--linter none|basic|adobe-recommended] ARGUMENTS - [PATH] [default: .] Path to the app directory + PATH [default: .] Path to the app directory FLAGS -e, --extension=... Extension point(s) to implement @@ -674,7 +674,7 @@ USAGE $ aio app pack [PATH] [-v] [--version] [--lock-file] [-o ] ARGUMENTS - [PATH] [default: .] Path to the app directory to package + PATH [default: .] Path to the app directory to package FLAGS -o, --output= [default: dist/app.zip] The packaged app output file path @@ -773,7 +773,7 @@ USAGE [--confirm-new-workspace] [--no-service-sync | --confirm-service-sync] [--no-input] [--use-jwt] ARGUMENTS - [CONFIG_FILE_PATH] path to an Adobe I/O Developer Console configuration file + CONFIG_FILE_PATH path to an Adobe I/O Developer Console configuration file FLAGS -g, --global Use the global Adobe Developer Console Org / Project / Workspace configuration, diff --git a/package.json b/package.json index a239af93..90ae9bdb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Adobe Inc.", "bugs": "https://github.com/adobe/aio-cli-plugin-app/issues", "dependencies": { - "@adobe/aio-cli-lib-app-config": "../aio-cli-lib-app-config", + "@adobe/aio-cli-lib-app-config": "^4.0.3", "@adobe/aio-cli-lib-console": "^5.0.3", "@adobe/aio-lib-core-config": "^5", "@adobe/aio-lib-core-logging": "^3", From 6c8bca34441baab6e059100548532e7719f8fb31 Mon Sep 17 00:00:00 2001 From: ajaz Date: Thu, 6 Nov 2025 18:19:05 +0530 Subject: [PATCH 3/5] address review comment: remove default region --- src/commands/app/deploy.js | 17 ++++++++++------- test/commands/app/deploy.test.js | 22 +++++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/commands/app/deploy.js b/src/commands/app/deploy.js index 91a03a65..0ccec731 100644 --- a/src/commands/app/deploy.js +++ b/src/commands/app/deploy.js @@ -305,25 +305,28 @@ class Deploy extends BuildCommand { } async provisionDatabase (config, spinner, flags) { - const region = config.manifest?.full?.database?.region || 'amer' + const region = config.manifest?.full?.database?.region + const args = ['--yes'] - if (!config.manifest?.full?.database?.region) { - spinner.info(chalk.green('No region is configured for the database, deploying in default region amer')) + if (region) { + args.unshift('--region', region) } - const message = `Deploying database for region '${region}'` + const message = region ? `Deploying database in region '${region}'` : 'Deploying database in default region' try { spinner.start(message) if (flags.verbose) { - spinner.info(chalk.dim(`Running: aio app db provision --region ${region} --yes`)) + const commandStr = region ? `aio app db provision --region ${region} --yes` : 'aio app db provision --yes' + spinner.info(chalk.dim(`Running: ${commandStr}`)) spinner.start(message) } - await this.config.runCommand('app:db:provision', ['--region', region, '--yes']) + await this.config.runCommand('app:db:provision', args) - spinner.succeed(chalk.green(`Deployed database for application in region '${region}'`)) + const successMessage = region ? `Deployed database for application in region '${region}'` : 'Deployed database for application' + spinner.succeed(chalk.green(successMessage)) } catch (error) { spinner.fail(chalk.red('Database deployment failed')) throw error diff --git a/test/commands/app/deploy.test.js b/test/commands/app/deploy.test.js index fe55faf5..6d8ab0ad 100644 --- a/test/commands/app/deploy.test.js +++ b/test/commands/app/deploy.test.js @@ -1698,10 +1698,9 @@ describe('database provisioning', () => { const { spinner } = await runProvisionTest(config, flags) - expect(spinner.info).toHaveBeenCalledWith(expect.stringContaining('No region is configured for the database, deploying in default region amer')) - expect(spinner.start).toHaveBeenCalledWith('Deploying database for region \'amer\'') - expect(command.config.runCommand).toHaveBeenCalledWith('app:db:provision', ['--region', 'amer', '--yes']) - expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application in region \'amer\'')) + expect(spinner.start).toHaveBeenCalledWith('Deploying database in default region') + expect(command.config.runCommand).toHaveBeenCalledWith('app:db:provision', ['--yes']) + expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application')) }) test('should use configured region when specified in manifest', async () => { @@ -1710,8 +1709,7 @@ describe('database provisioning', () => { const { spinner } = await runProvisionTest(config, flags) - expect(spinner.info).not.toHaveBeenCalledWith(expect.stringContaining('No region is configured for the database')) - expect(spinner.start).toHaveBeenCalledWith('Deploying database for region \'emea\'') + expect(spinner.start).toHaveBeenCalledWith('Deploying database in region \'emea\'') expect(command.config.runCommand).toHaveBeenCalledWith('app:db:provision', ['--region', 'emea', '--yes']) expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application in region \'emea\'')) }) @@ -1723,11 +1721,21 @@ describe('database provisioning', () => { const { spinner } = await runProvisionTest(config, flags) expect(spinner.info).toHaveBeenCalledWith(expect.stringContaining('Running: aio app db provision --region amer --yes')) - expect(spinner.info).not.toHaveBeenCalledWith(expect.stringContaining('No region is configured for the database')) expect(spinner.start).toHaveBeenCalledTimes(2) // Once initially, once after verbose info expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application in region \'amer\'')) }) + test('should show verbose output without region when no region configured', async () => { + const config = createDatabaseConfig() + const flags = { verbose: true } + + const { spinner } = await runProvisionTest(config, flags) + + expect(spinner.info).toHaveBeenCalledWith(expect.stringContaining('Running: aio app db provision --yes')) + expect(spinner.start).toHaveBeenCalledTimes(2) // Once initially, once after verbose info + expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application')) + }) + test('should handle provision command failure', async () => { const config = createDatabaseConfig('amer') const flags = { verbose: false } From 445e9d8afd5ed04945cb7fcba8dab132094eb798 Mon Sep 17 00:00:00 2001 From: ajaz Date: Fri, 7 Nov 2025 17:59:48 +0530 Subject: [PATCH 4/5] fix: region level --- test/commands/app/deploy.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/commands/app/deploy.test.js b/test/commands/app/deploy.test.js index 6d8ab0ad..72033e09 100644 --- a/test/commands/app/deploy.test.js +++ b/test/commands/app/deploy.test.js @@ -1638,11 +1638,9 @@ describe('run', () => { manifest: { full: { database: { - 'auto-provision': true + 'auto-provision': true, + region: 'emea' } - }, - database: { - region: 'emea' } } }) From 8972415c2435ab1c4a7077ea205f5b107130d44d Mon Sep 17 00:00:00 2001 From: ajaz Date: Fri, 7 Nov 2025 18:45:37 +0530 Subject: [PATCH 5/5] fix: test --- test/commands/app/deploy.test.js | 130 +++++++++++++++---------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/test/commands/app/deploy.test.js b/test/commands/app/deploy.test.js index 72033e09..4f4a1b99 100644 --- a/test/commands/app/deploy.test.js +++ b/test/commands/app/deploy.test.js @@ -1630,51 +1630,20 @@ describe('run', () => { expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1) expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1) }) - - test('should deploy successfully when auto-provision database is configured', async () => { - // Create app config with database auto-provision enabled - const appConfigWithDb = createAppConfig({ - ...command.appConfig, - manifest: { - full: { - database: { - 'auto-provision': true, - region: 'emea' - } - } - } - }) - - command.getAppExtConfigs.mockResolvedValueOnce(appConfigWithDb) - command.provisionDatabase = jest.fn().mockResolvedValue() - - await command.run() - - expect(command.error).toHaveBeenCalledTimes(0) - expect(command.provisionDatabase).toHaveBeenCalledTimes(1) - expect(command.provisionDatabase).toHaveBeenCalledWith( - appConfigWithDb.application, - expect.any(Object), // spinner - expect.objectContaining({ 'force-build': true }) // flags - ) - expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1) - expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1) - }) }) describe('database provisioning', () => { - const createDatabaseConfig = (region = null, autoProvision = true) => ({ + // Helper functions for unit tests + const createDatabaseConfig = (region = null) => ({ manifest: { full: { database: { - 'auto-provision': autoProvision, ...(region && { region }) } } } }) - // Helper function to set up and run test const runProvisionTest = async (config, flags, mockResult) => { const spinner = ora() @@ -1690,18 +1659,60 @@ describe('database provisioning', () => { } } - test('should use default region when not specified in manifest', async () => { - const config = createDatabaseConfig() - const flags = { verbose: false } + test('should provision database when auto-provision is true', async () => { + const appConfigWithDb = createAppConfig({ + ...command.appConfig, + manifest: { + full: { + database: { + 'auto-provision': true, + region: 'emea' + } + } + } + }) - const { spinner } = await runProvisionTest(config, flags) + command.getAppExtConfigs.mockResolvedValueOnce(appConfigWithDb) + command.config.runCommand.mockResolvedValue() - expect(spinner.start).toHaveBeenCalledWith('Deploying database in default region') - expect(command.config.runCommand).toHaveBeenCalledWith('app:db:provision', ['--yes']) - expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application')) + await command.run() + + expect(command.error).toHaveBeenCalledTimes(0) + expect(command.config.runCommand).toHaveBeenCalledWith('app:db:provision', ['--region', 'emea', '--yes']) + expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1) + expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1) + expect(command.log).toHaveBeenCalledWith( + expect.stringContaining('Successful deployment 🏄') + ) + }) + + test('should not provision database when auto-provision is false', async () => { + const appConfigWithoutDb = createAppConfig({ + ...command.appConfig, + manifest: { + full: { + database: { + 'auto-provision': false + } + } + } + }) + + command.getAppExtConfigs.mockResolvedValueOnce(appConfigWithoutDb) + + await command.run() + + expect(command.error).toHaveBeenCalledTimes(0) + expect(command.config.runCommand).not.toHaveBeenCalledWith('app:db:provision', expect.anything()) + expect(mockRuntimeLib.deployActions).toHaveBeenCalledTimes(1) + expect(mockWebLib.deployWeb).toHaveBeenCalledTimes(1) + expect(command.log).toHaveBeenCalledWith( + expect.stringContaining('Successful deployment 🏄') + ) }) - test('should use configured region when specified in manifest', async () => { + // tests for provisionDatabase method behavior + test('should run provision command correctly with region', async () => { const config = createDatabaseConfig('emea') const flags = { verbose: false } @@ -1712,7 +1723,18 @@ describe('database provisioning', () => { expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application in region \'emea\'')) }) - test('should show verbose output when verbose flag is true', async () => { + test('should run provision command correctly without region', async () => { + const config = createDatabaseConfig() + const flags = { verbose: false } + + const { spinner } = await runProvisionTest(config, flags) + + expect(spinner.start).toHaveBeenCalledWith('Deploying database in default region') + expect(command.config.runCommand).toHaveBeenCalledWith('app:db:provision', ['--yes']) + expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application')) + }) + + test('should show verbose output with region', async () => { const config = createDatabaseConfig('amer') const flags = { verbose: true } @@ -1723,7 +1745,7 @@ describe('database provisioning', () => { expect(spinner.succeed).toHaveBeenCalledWith(expect.stringContaining('Deployed database for application in region \'amer\'')) }) - test('should show verbose output without region when no region configured', async () => { + test('should show verbose output without region', async () => { const config = createDatabaseConfig() const flags = { verbose: true } @@ -1741,27 +1763,5 @@ describe('database provisioning', () => { await expect(runProvisionTest(config, flags, error)) .rejects.toThrow('Database provision failed') - - expect(command.warn).not.toHaveBeenCalled() - }) - - test('should show verbose error details when verbose flag is true and provision fails', async () => { - const config = createDatabaseConfig('amer') - const flags = { verbose: true } - const error = new Error('Database provision failed') - - await expect(runProvisionTest(config, flags, error)) - .rejects.toThrow('Database provision failed') - - expect(command.warn).not.toHaveBeenCalled() - }) - - test('should fail if invalid region specified', async () => { - const config = createDatabaseConfig('invalid-region') - const flags = { verbose: false } - const error = new Error('Invalid region: invalid-region') - - await expect(runProvisionTest(config, flags, error)) - .rejects.toThrow('Invalid region: invalid-region') }) })