@adobe/aio-cli-plugin-app-storage 1.2.0 → 1.3.0-pre.2026-02-12.sha-6f4504ab
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +688 -1
- package/package.json +25 -5
- package/src/BaseCommand.js +11 -58
- package/src/DBBaseCommand.js +112 -0
- package/src/StateBaseCommand.js +87 -0
- package/src/commands/app/add/db.js +20 -0
- package/src/commands/app/db/collection/create.js +119 -0
- package/src/commands/app/db/collection/drop.js +90 -0
- package/src/commands/app/db/collection/list.js +100 -0
- package/src/commands/app/db/collection/rename.js +98 -0
- package/src/commands/app/db/collection/stats.js +94 -0
- package/src/commands/app/db/delete.js +89 -0
- package/src/commands/app/db/document/count.js +96 -0
- package/src/commands/app/db/document/delete.js +95 -0
- package/src/commands/app/db/document/find.js +133 -0
- package/src/commands/app/db/document/insert.js +147 -0
- package/src/commands/app/db/document/replace.js +122 -0
- package/src/commands/app/db/document/update.js +144 -0
- package/src/commands/app/db/index/create.js +170 -0
- package/src/commands/app/db/index/drop.js +87 -0
- package/src/commands/app/db/index/list.js +82 -0
- package/src/commands/app/db/ping.js +77 -0
- package/src/commands/app/db/provision.js +190 -0
- package/src/commands/app/db/stats.js +87 -0
- package/src/commands/app/db/status.js +159 -0
- package/src/commands/app/state/delete.js +3 -3
- package/src/commands/app/state/get.js +2 -2
- package/src/commands/app/state/list.js +3 -3
- package/src/commands/app/state/put.js +4 -4
- package/src/commands/app/state/stats.js +2 -2
- package/src/constants/db.js +32 -0
- package/src/constants/global.js +14 -0
- package/src/{constants.js → constants/state.js} +3 -0
- package/src/utils/inputValidation.js +74 -0
- package/src/utils/output.js +35 -0
- package/oclif.manifest.json +0 -316
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { DBBaseCommand } from '../../../../DBBaseCommand.js'
|
|
14
|
+
import { Args } from '@oclif/core'
|
|
15
|
+
import chalk from 'chalk'
|
|
16
|
+
import { isNonEmptyString } from '../../../../utils/inputValidation.js'
|
|
17
|
+
import { prettyJson } from '../../../../utils/output.js'
|
|
18
|
+
|
|
19
|
+
export class List extends DBBaseCommand {
|
|
20
|
+
async run () {
|
|
21
|
+
const { collection } = this.args
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
this.log(chalk.blue(`Getting indexes from collection '${collection}'...`))
|
|
25
|
+
|
|
26
|
+
const client = await this.db.connect()
|
|
27
|
+
const coll = await client.collection(collection)
|
|
28
|
+
|
|
29
|
+
const result = await coll.getIndexes()
|
|
30
|
+
|
|
31
|
+
this.debugLogger?.info?.('Indexes retrieved successfully:', result)
|
|
32
|
+
|
|
33
|
+
const response = {
|
|
34
|
+
collection,
|
|
35
|
+
namespace: this.rtNamespace,
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
indexes: result
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.log(chalk.green('Indexes retrieved successfully'))
|
|
41
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
42
|
+
this.log(chalk.dim(` Retrieved: ${new Date().toLocaleString()}`))
|
|
43
|
+
if (result && Array.isArray(result) && result.length > 0) {
|
|
44
|
+
this.log(chalk.dim(` Indexes:\n${prettyJson(result)}`))
|
|
45
|
+
} else {
|
|
46
|
+
this.log(chalk.dim(' No indexes found for this collection'))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return response
|
|
50
|
+
} catch (error) {
|
|
51
|
+
this.debugLogger?.error?.('Error getting indexes:', error)
|
|
52
|
+
|
|
53
|
+
this.log(chalk.red('Failed to retrieve indexes'))
|
|
54
|
+
this.log(chalk.dim(` Collection: ${collection}`))
|
|
55
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
56
|
+
this.log(chalk.dim(` Error: ${error.message}`))
|
|
57
|
+
|
|
58
|
+
this.error(`Failed to retrieve indexes from collection '${collection}': ${error.message}`)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
List.description = 'Get the list of indexes from a collection in the database'
|
|
64
|
+
|
|
65
|
+
List.examples = [
|
|
66
|
+
'$ aio app db index list users',
|
|
67
|
+
'$ aio app db index list products --json',
|
|
68
|
+
'$ aio app db idx list orders'
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
List.args = {
|
|
72
|
+
collection: Args.string({
|
|
73
|
+
name: 'collection',
|
|
74
|
+
description: 'The name of the collection to retrieve indexes from',
|
|
75
|
+
required: true,
|
|
76
|
+
parse: input => isNonEmptyString(input, 'Collection name')
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
List.flags = DBBaseCommand.flags
|
|
81
|
+
|
|
82
|
+
List.aliases = ['app:db:idx:list']
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { DBBaseCommand } from '../../../DBBaseCommand.js'
|
|
14
|
+
import chalk from 'chalk'
|
|
15
|
+
|
|
16
|
+
export class Ping extends DBBaseCommand {
|
|
17
|
+
async run () {
|
|
18
|
+
try {
|
|
19
|
+
this.log(chalk.blue('Testing database connectivity...'))
|
|
20
|
+
|
|
21
|
+
const startTime = Date.now()
|
|
22
|
+
const pingResult = await this.db.ping()
|
|
23
|
+
const endTime = Date.now()
|
|
24
|
+
const responseTime = endTime - startTime
|
|
25
|
+
|
|
26
|
+
this.debugLogger?.info?.(`Database ping completed in ${responseTime}ms:`, pingResult)
|
|
27
|
+
|
|
28
|
+
this.log(chalk.green('Database connection successful'))
|
|
29
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
30
|
+
this.log(chalk.dim(` Response time: ${responseTime}ms`))
|
|
31
|
+
|
|
32
|
+
if (typeof pingResult === 'string') {
|
|
33
|
+
this.log(chalk.dim(` Response: ${pingResult}`))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const result = {
|
|
37
|
+
status: 'success',
|
|
38
|
+
namespace: this.rtNamespace,
|
|
39
|
+
responseTime,
|
|
40
|
+
response: pingResult,
|
|
41
|
+
timestamp: new Date().toISOString()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.log(chalk.dim('Database is ready for operations'))
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
} catch (error) {
|
|
48
|
+
this.debugLogger?.error?.('Ping command error:', error)
|
|
49
|
+
|
|
50
|
+
this.log(chalk.red('Database connection failed'))
|
|
51
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
52
|
+
this.log(chalk.dim(` Error: ${error.message}`))
|
|
53
|
+
|
|
54
|
+
const result = {
|
|
55
|
+
status: 'failed',
|
|
56
|
+
namespace: this.rtNamespace,
|
|
57
|
+
error: error.message,
|
|
58
|
+
timestamp: new Date().toISOString()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Ping.description = 'Test connectivity to your App Builder database'
|
|
67
|
+
|
|
68
|
+
Ping.examples = [
|
|
69
|
+
'$ aio app db ping',
|
|
70
|
+
'$ aio app db ping --json'
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
Ping.flags = {
|
|
74
|
+
...DBBaseCommand.flags
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Ping.args = {}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { DBBaseCommand } from '../../../DBBaseCommand.js'
|
|
14
|
+
import chalk from 'chalk'
|
|
15
|
+
import { DB_STATUS } from '../../../constants/db.js'
|
|
16
|
+
import { Flags } from '@oclif/core'
|
|
17
|
+
|
|
18
|
+
export class Provision extends DBBaseCommand {
|
|
19
|
+
async run () {
|
|
20
|
+
const region = this.db.region
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// First check if database is already provisioned
|
|
24
|
+
this.log(chalk.blue('Checking current provisioning status...'))
|
|
25
|
+
|
|
26
|
+
let provisionStatusResponse
|
|
27
|
+
try {
|
|
28
|
+
provisionStatusResponse = await this.db.provisionStatus()
|
|
29
|
+
this.debugLogger?.info?.('Provision status:', provisionStatusResponse)
|
|
30
|
+
} catch (error) {
|
|
31
|
+
this.debugLogger?.info?.('No existing provisioning status found:', error.message)
|
|
32
|
+
provisionStatusResponse = null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (provisionStatusResponse) {
|
|
36
|
+
const currentStatus = provisionStatusResponse.status.toUpperCase()
|
|
37
|
+
const statusRegion = provisionStatusResponse.region
|
|
38
|
+
|
|
39
|
+
if (currentStatus === DB_STATUS.PROVISIONED) {
|
|
40
|
+
this.log(chalk.green('Database is already provisioned and ready for use'))
|
|
41
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
42
|
+
this.log(chalk.dim(` Region: ${statusRegion}`))
|
|
43
|
+
this.log(chalk.dim(` Status: ${currentStatus}`))
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
status: 'already_provisioned',
|
|
47
|
+
namespace: this.rtNamespace,
|
|
48
|
+
details: provisionStatusResponse
|
|
49
|
+
}
|
|
50
|
+
} else if (currentStatus === DB_STATUS.REQUESTED) {
|
|
51
|
+
this.log(chalk.yellow('Database provisioning request has been submitted and is pending'))
|
|
52
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
53
|
+
this.log(chalk.dim(` Region: ${statusRegion}`))
|
|
54
|
+
this.log(chalk.dim(` Status: ${currentStatus}`))
|
|
55
|
+
this.log(chalk.dim('\nUse "aio app db status --watch" to monitor progress'))
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
status: 'in_progress',
|
|
59
|
+
namespace: this.rtNamespace,
|
|
60
|
+
details: provisionStatusResponse
|
|
61
|
+
}
|
|
62
|
+
} else if (currentStatus === DB_STATUS.PROCESSING) {
|
|
63
|
+
this.log(chalk.yellow('Database is currently being provisioned'))
|
|
64
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
65
|
+
this.log(chalk.dim(` Region: ${statusRegion}`))
|
|
66
|
+
this.log(chalk.dim(` Status: ${currentStatus}`))
|
|
67
|
+
this.log(chalk.dim('\nUse "aio app db status --watch" to monitor progress'))
|
|
68
|
+
this.log(chalk.red('If provisioning takes unusually long, please contact the App Builder team'))
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
status: 'in_progress',
|
|
72
|
+
namespace: this.rtNamespace,
|
|
73
|
+
details: provisionStatusResponse
|
|
74
|
+
}
|
|
75
|
+
} else if (currentStatus === DB_STATUS.FAILED) {
|
|
76
|
+
this.log(chalk.red('Previous database provisioning failed'))
|
|
77
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
78
|
+
this.log(chalk.dim(` Status: ${currentStatus}`))
|
|
79
|
+
this.log(chalk.yellow('\nAttempting to provision again...'))
|
|
80
|
+
this.log(chalk.red('If the problem persists, please contact the App Builder team'))
|
|
81
|
+
} else if (currentStatus === DB_STATUS.REJECTED) {
|
|
82
|
+
this.log(chalk.red('Previous database provisioning request was rejected'))
|
|
83
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
84
|
+
this.log(chalk.dim(` Status: ${currentStatus}`))
|
|
85
|
+
this.log(chalk.yellow('\nAttempting to provision again...'))
|
|
86
|
+
this.log(chalk.red('If the problem persists, please contact the App Builder team for assistance'))
|
|
87
|
+
} else if (currentStatus !== DB_STATUS.NOT_PROVISIONED) {
|
|
88
|
+
this.log(chalk.yellow(`Database status is '${currentStatus}' - attempting to provision...`))
|
|
89
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
90
|
+
this.log(chalk.dim(` Status: ${currentStatus}`))
|
|
91
|
+
this.log(chalk.red('If you encounter issues, please contact the App Builder team'))
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Create a new database if not yet provisioned
|
|
96
|
+
this.warn('Database provisioning will create new database resources')
|
|
97
|
+
|
|
98
|
+
// Skip confirmation prompt if --yes flag is used
|
|
99
|
+
if (!this.flags.yes) {
|
|
100
|
+
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
|
101
|
+
const { confirm } = await import('@inquirer/prompts')
|
|
102
|
+
|
|
103
|
+
const confirmed = await confirm({
|
|
104
|
+
message: `Provision database for namespace '${this.rtNamespace}'?`,
|
|
105
|
+
default: false
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
if (!confirmed) {
|
|
109
|
+
this.log('Database provisioning cancelled')
|
|
110
|
+
return { status: 'cancelled' }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Start provisioning
|
|
115
|
+
this.log(chalk.blue(`Submitting a request for a database to be provisioned for the '${this.rtNamespace}' namespace...`))
|
|
116
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
117
|
+
this.log(chalk.dim(` Region: ${region}`))
|
|
118
|
+
|
|
119
|
+
const provisionResult = await this.db.provisionRequest()
|
|
120
|
+
this.debugLogger?.info?.('Provision request result:', provisionResult)
|
|
121
|
+
|
|
122
|
+
// Handle different provision result statuses
|
|
123
|
+
const resultStatus = provisionResult?.status?.toUpperCase() || DB_STATUS.UNKNOWN
|
|
124
|
+
|
|
125
|
+
if (resultStatus === DB_STATUS.PROVISIONED) {
|
|
126
|
+
this.log(chalk.green('Database provisioned successfully and ready for use!'))
|
|
127
|
+
} else if (resultStatus === DB_STATUS.REQUESTED) {
|
|
128
|
+
this.log(chalk.blue('Database provisioning request submitted successfully'))
|
|
129
|
+
this.log(chalk.dim('Provisioning is now pending...'))
|
|
130
|
+
} else if (resultStatus === DB_STATUS.PROCESSING) {
|
|
131
|
+
this.log(chalk.yellow('Database is being provisioned...'))
|
|
132
|
+
} else if (resultStatus === DB_STATUS.FAILED) {
|
|
133
|
+
this.error(`Database provisioning failed: ${provisionResult.message || 'Unknown error'}`)
|
|
134
|
+
} else if (resultStatus === DB_STATUS.REJECTED) {
|
|
135
|
+
this.error(`Database provisioning request was rejected: ${provisionResult.message || 'Unknown reason'}`)
|
|
136
|
+
} else if (resultStatus === DB_STATUS.UNKNOWN) {
|
|
137
|
+
this.warn(`Database provisioning request returned unrecognized status '${provisionResult.status || 'undefined'}', an update to the aio cli tool may be necessary.`)
|
|
138
|
+
this.warn('If the issue persists, please contact the App Builder team.')
|
|
139
|
+
} else {
|
|
140
|
+
this.warn(`Database provisioning request returned unexpected status '${resultStatus}', an update to the aio cli tool may be necessary.`)
|
|
141
|
+
this.warn('If the issue persists, please contact the App Builder team.')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const result = {
|
|
145
|
+
status: resultStatus.toLowerCase(),
|
|
146
|
+
namespace: this.rtNamespace,
|
|
147
|
+
timestamp: new Date().toISOString(),
|
|
148
|
+
details: provisionResult
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// If region was specified as a CLI flag, include it in the output for next steps
|
|
152
|
+
const { region: regionFlag } = this.flags
|
|
153
|
+
const regionFlagString = regionFlag ? ` --region ${regionFlag}` : ''
|
|
154
|
+
|
|
155
|
+
if (resultStatus !== DB_STATUS.PROVISIONED) {
|
|
156
|
+
this.log(chalk.dim('\nNext steps:'))
|
|
157
|
+
this.log(chalk.dim(` - Monitor progress: aio app db status${regionFlagString} --watch`))
|
|
158
|
+
this.log(chalk.dim(` - Check status: aio app db status${regionFlagString}`))
|
|
159
|
+
} else {
|
|
160
|
+
this.log(chalk.dim('\nNext steps:'))
|
|
161
|
+
this.log(chalk.dim(` - Test connection: aio app db ping${regionFlagString}`))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result
|
|
165
|
+
} catch (error) {
|
|
166
|
+
this.debugLogger?.error?.('Provision command error:', error)
|
|
167
|
+
this.error(`Database provisioning failed: ${error.message}`)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Provision.description = 'Provision a new database for your App Builder application'
|
|
173
|
+
|
|
174
|
+
Provision.examples = [
|
|
175
|
+
'$ aio app db provision',
|
|
176
|
+
'$ aio app db provision --region amer',
|
|
177
|
+
'$ aio app db provision --json',
|
|
178
|
+
'$ aio app db provision --yes'
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
Provision.flags = {
|
|
182
|
+
...DBBaseCommand.flags,
|
|
183
|
+
yes: Flags.boolean({
|
|
184
|
+
char: 'y',
|
|
185
|
+
description: 'Skip confirmation prompt and provision automatically',
|
|
186
|
+
default: false
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
Provision.args = {}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { DBBaseCommand } from '../../../DBBaseCommand.js'
|
|
14
|
+
import chalk from 'chalk'
|
|
15
|
+
import { prettyJson } from '../../../utils/output.js'
|
|
16
|
+
|
|
17
|
+
export class Stats extends DBBaseCommand {
|
|
18
|
+
async run () {
|
|
19
|
+
try {
|
|
20
|
+
this.log(chalk.blue('Fetching database statistics...'))
|
|
21
|
+
|
|
22
|
+
const client = await this.db.connect()
|
|
23
|
+
const stats = await client.dbStats()
|
|
24
|
+
|
|
25
|
+
this.debugLogger?.info?.('Database statistics retrieved:', stats)
|
|
26
|
+
|
|
27
|
+
const result = {
|
|
28
|
+
...stats,
|
|
29
|
+
namespace: this.rtNamespace,
|
|
30
|
+
timestamp: new Date().toISOString()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.displayStats(stats)
|
|
34
|
+
|
|
35
|
+
return result
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this.debugLogger?.error?.('Stats command error:', error)
|
|
38
|
+
|
|
39
|
+
this.log(chalk.red('Failed to retrieve database statistics'))
|
|
40
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
41
|
+
this.log(chalk.dim(` Error: ${error.message}`))
|
|
42
|
+
|
|
43
|
+
this.error(`Failed to fetch database statistics: ${error.message}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
displayStats (stats) {
|
|
48
|
+
this.log(chalk.green('Database Statistics:'))
|
|
49
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
50
|
+
|
|
51
|
+
if (stats && typeof stats === 'object') {
|
|
52
|
+
// Format and display stats in a readable way
|
|
53
|
+
Object.entries(stats).forEach(([key, value]) => {
|
|
54
|
+
this.log(chalk.dim(` ${key}: ${this.formatValue(value)}`))
|
|
55
|
+
})
|
|
56
|
+
} else {
|
|
57
|
+
this.log(chalk.dim(` Raw Stats: ${this.formatValue(stats)}`))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.log('')
|
|
61
|
+
this.log(chalk.dim(` Retrieved: ${new Date().toLocaleString()}`))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
formatValue (value) {
|
|
65
|
+
if (typeof value === 'number') {
|
|
66
|
+
// Format large numbers with commas
|
|
67
|
+
return value.toLocaleString()
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === 'object' && value !== null) {
|
|
70
|
+
return `\n${prettyJson(value)}`
|
|
71
|
+
}
|
|
72
|
+
return String(value)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Stats.description = 'Get statistics about your App Builder database'
|
|
77
|
+
|
|
78
|
+
Stats.examples = [
|
|
79
|
+
'$ aio app db stats',
|
|
80
|
+
'$ aio app db stats --json'
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
Stats.flags = {
|
|
84
|
+
...DBBaseCommand.flags
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Stats.args = {}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { DBBaseCommand } from '../../../DBBaseCommand.js'
|
|
14
|
+
import { Flags } from '@oclif/core'
|
|
15
|
+
import chalk from 'chalk'
|
|
16
|
+
import { DB_STATUS } from '../../../constants/db.js'
|
|
17
|
+
|
|
18
|
+
export class Status extends DBBaseCommand {
|
|
19
|
+
async run () {
|
|
20
|
+
const { watch } = this.flags
|
|
21
|
+
|
|
22
|
+
this.debugLogger?.info?.('Checking database provisioning status')
|
|
23
|
+
|
|
24
|
+
if (watch) {
|
|
25
|
+
return this.watchStatus()
|
|
26
|
+
} else {
|
|
27
|
+
return this.checkStatus()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async checkStatus () {
|
|
32
|
+
try {
|
|
33
|
+
this.log(chalk.blue('Checking database provisioning status...'))
|
|
34
|
+
|
|
35
|
+
const provisionStatusResponse = await this.db.provisionStatus()
|
|
36
|
+
this.debugLogger?.info?.('Status result:', provisionStatusResponse)
|
|
37
|
+
|
|
38
|
+
this.displayStatus(provisionStatusResponse)
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
...provisionStatusResponse,
|
|
42
|
+
namespace: this.rtNamespace,
|
|
43
|
+
timestamp: new Date().toISOString()
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
this.debugLogger?.error?.('Status command error:', error)
|
|
47
|
+
|
|
48
|
+
if (error.httpStatusCode === 404) {
|
|
49
|
+
this.log(chalk.yellow('No database has been provisioned for this workspace'))
|
|
50
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
51
|
+
this.log(chalk.dim(` Status: ${DB_STATUS.NOT_PROVISIONED}`))
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
status: DB_STATUS.NOT_PROVISIONED,
|
|
55
|
+
namespace: this.rtNamespace,
|
|
56
|
+
timestamp: new Date().toISOString()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.error(`Failed to check database status: ${error.message}`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async watchStatus () {
|
|
65
|
+
this.log(chalk.blue('Watching database provisioning status (press Ctrl+C to stop)...'))
|
|
66
|
+
|
|
67
|
+
let previousStatus = null
|
|
68
|
+
const checkInterval = 3000 // 3 seconds
|
|
69
|
+
|
|
70
|
+
const watchLoop = async () => {
|
|
71
|
+
try {
|
|
72
|
+
const provisionStatusResponse = await this.db.provisionStatus()
|
|
73
|
+
|
|
74
|
+
// Only display if status changed
|
|
75
|
+
if (previousStatus?.status !== provisionStatusResponse?.status) {
|
|
76
|
+
this.log(chalk.dim(`\n[${new Date().toLocaleTimeString()}]`))
|
|
77
|
+
this.displayStatus(provisionStatusResponse, false) // Don't show timestamp in watch mode
|
|
78
|
+
previousStatus = provisionStatusResponse
|
|
79
|
+
|
|
80
|
+
// Stop watching if provisioning is complete or failed
|
|
81
|
+
const currentStatus = provisionStatusResponse.status?.toUpperCase()
|
|
82
|
+
if (currentStatus !== DB_STATUS.REQUESTED && currentStatus !== DB_STATUS.PROCESSING) {
|
|
83
|
+
this.log(chalk.dim('\nStopping watch mode.'))
|
|
84
|
+
return provisionStatusResponse
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Schedule next check
|
|
89
|
+
setTimeout(watchLoop, checkInterval)
|
|
90
|
+
} catch (error) {
|
|
91
|
+
this.debugLogger?.error?.('Watch status error:', error)
|
|
92
|
+
this.log(chalk.red(`\n[${new Date().toLocaleTimeString()}] Error: ${error.message}`))
|
|
93
|
+
|
|
94
|
+
// Continue watching despite errors
|
|
95
|
+
setTimeout(watchLoop, checkInterval)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Start watching
|
|
100
|
+
return watchLoop()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
displayStatus (provisionStatusResponse, showTimestamp = true) {
|
|
104
|
+
const currentStatus = provisionStatusResponse.status.toUpperCase()
|
|
105
|
+
const statusColor = this.getStatusColor(currentStatus)
|
|
106
|
+
|
|
107
|
+
this.log(statusColor(`Database Status: ${currentStatus}`))
|
|
108
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
109
|
+
|
|
110
|
+
if (provisionStatusResponse.message) {
|
|
111
|
+
this.log(chalk.dim(` Message: ${provisionStatusResponse.message}`))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (provisionStatusResponse.submitted) {
|
|
115
|
+
this.log(chalk.dim(` Submitted: ${new Date(provisionStatusResponse.submitted).toLocaleString()}`))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (showTimestamp) {
|
|
119
|
+
this.log(chalk.dim(` Checked: ${new Date().toLocaleString()}`))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* istanbul ignore next */
|
|
124
|
+
getStatusColor (statusValue) {
|
|
125
|
+
const status = statusValue.toUpperCase()
|
|
126
|
+
switch (status) {
|
|
127
|
+
case DB_STATUS.PROVISIONED:
|
|
128
|
+
return chalk.green
|
|
129
|
+
case DB_STATUS.REQUESTED:
|
|
130
|
+
case DB_STATUS.PROCESSING:
|
|
131
|
+
return chalk.yellow
|
|
132
|
+
case DB_STATUS.FAILED:
|
|
133
|
+
case DB_STATUS.REJECTED:
|
|
134
|
+
return chalk.red
|
|
135
|
+
case DB_STATUS.NOT_PROVISIONED:
|
|
136
|
+
return chalk.blue
|
|
137
|
+
default:
|
|
138
|
+
return chalk.gray
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Status.description = 'Check the provisioning status of your App Builder database'
|
|
144
|
+
|
|
145
|
+
Status.examples = [
|
|
146
|
+
'$ aio app db status',
|
|
147
|
+
'$ aio app db status --watch',
|
|
148
|
+
'$ aio app db status --json'
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
Status.flags = {
|
|
152
|
+
...DBBaseCommand.flags,
|
|
153
|
+
watch: Flags.boolean({
|
|
154
|
+
description: 'Watch for status changes (press Ctrl+C to stop)',
|
|
155
|
+
default: false
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Status.args = {}
|
|
@@ -10,12 +10,12 @@ OF ANY KIND, either express or implied. See the License for the specific languag
|
|
|
10
10
|
governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
import chalk from 'chalk'
|
|
13
|
-
import {
|
|
13
|
+
import { StateBaseCommand } from '../../../StateBaseCommand.js'
|
|
14
14
|
import { Args, Flags } from '@oclif/core'
|
|
15
15
|
|
|
16
16
|
const MAX_ARGV_NO_CONFIRM = 5
|
|
17
17
|
|
|
18
|
-
export class Delete extends
|
|
18
|
+
export class Delete extends StateBaseCommand {
|
|
19
19
|
async run () {
|
|
20
20
|
const { match, force } = this.flags
|
|
21
21
|
const { argv: keysToDelete } = await this.parse(Delete)
|
|
@@ -100,7 +100,7 @@ Delete.args = {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
Delete.flags = {
|
|
103
|
-
...
|
|
103
|
+
...StateBaseCommand.flags,
|
|
104
104
|
match: Flags.string({
|
|
105
105
|
description: '[use with caution!] deletes ALL key-values matching the provided glob-like pattern',
|
|
106
106
|
required: false
|
|
@@ -10,10 +10,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag
|
|
|
10
10
|
governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
import chalk from 'chalk'
|
|
13
|
-
import {
|
|
13
|
+
import { StateBaseCommand } from '../../../StateBaseCommand.js'
|
|
14
14
|
import { Args } from '@oclif/core'
|
|
15
15
|
|
|
16
|
-
export class Get extends
|
|
16
|
+
export class Get extends StateBaseCommand {
|
|
17
17
|
async run () {
|
|
18
18
|
const ret = await this.state.get(this.args.key)
|
|
19
19
|
|
|
@@ -9,14 +9,14 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
|
|
|
9
9
|
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import { StateBaseCommand } from '../../../StateBaseCommand.js'
|
|
13
13
|
import { Flags } from '@oclif/core'
|
|
14
14
|
import chalk from 'chalk'
|
|
15
15
|
|
|
16
16
|
const MAX_KEYS = 5000
|
|
17
17
|
const COUNT_HINT = 500 // per iteration
|
|
18
18
|
|
|
19
|
-
export class List extends
|
|
19
|
+
export class List extends StateBaseCommand {
|
|
20
20
|
async run () {
|
|
21
21
|
const allKeys = []
|
|
22
22
|
|
|
@@ -55,7 +55,7 @@ List.examples = [
|
|
|
55
55
|
]
|
|
56
56
|
|
|
57
57
|
List.flags = {
|
|
58
|
-
...
|
|
58
|
+
...StateBaseCommand.flags,
|
|
59
59
|
match: Flags.string({
|
|
60
60
|
name: 'match',
|
|
61
61
|
char: 'm',
|
|
@@ -9,12 +9,12 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
|
|
|
9
9
|
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import { StateBaseCommand } from '../../../StateBaseCommand.js'
|
|
13
13
|
import { Args, Flags } from '@oclif/core'
|
|
14
|
-
import { DEFAULT_TTL_SECONDS } from '../../../constants.js'
|
|
14
|
+
import { DEFAULT_TTL_SECONDS } from '../../../constants/state.js'
|
|
15
15
|
import chalk from 'chalk'
|
|
16
16
|
|
|
17
|
-
export class Put extends
|
|
17
|
+
export class Put extends StateBaseCommand {
|
|
18
18
|
async run () {
|
|
19
19
|
const { key, value } = this.args
|
|
20
20
|
const { json, ttl } = this.flags
|
|
@@ -55,7 +55,7 @@ Put.args = {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
Put.flags = {
|
|
58
|
-
...
|
|
58
|
+
...StateBaseCommand.flags,
|
|
59
59
|
ttl: Flags.integer({
|
|
60
60
|
char: 't',
|
|
61
61
|
description: 'Time to live in seconds. Default is 86400 (24 hours), max is 31536000 (1 year).',
|