@adobe/aio-cli-plugin-app-storage 1.0.3 → 1.2.0-pre.2025-11-18.sha-9c4079ac
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 +676 -12
- package/package.json +33 -6
- 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 -306
|
@@ -0,0 +1,170 @@
|
|
|
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, Flags } from '@oclif/core'
|
|
15
|
+
import chalk from 'chalk'
|
|
16
|
+
import { asObject, isNonEmptyString } from '../../../../utils/inputValidation.js'
|
|
17
|
+
import { prettyJson } from '../../../../utils/output.js'
|
|
18
|
+
|
|
19
|
+
// Regular expression to match the -s/--spec or -k/--key flags in <flag>=<value> format
|
|
20
|
+
// if match.groups.spec is defined, it means the flag was -s or --spec
|
|
21
|
+
// if match.groups.key is defined, it means the flag was -k or --key
|
|
22
|
+
const specFlagMatch = /^((?<spec>-s|--spec)|(?<key>-k|--key))=(?<val>.+)/
|
|
23
|
+
|
|
24
|
+
export class Create extends DBBaseCommand {
|
|
25
|
+
getOrderedSpecs () {
|
|
26
|
+
// Key/spec order matters when creating an index and both key and spec can be specified,
|
|
27
|
+
// so we need to parse argv to obtain the proper order since using this.flags loses the order between the two
|
|
28
|
+
const args = this.argv.slice(1) // Ignore the first element (collection name)
|
|
29
|
+
const fullSpec = []
|
|
30
|
+
let specOption = false
|
|
31
|
+
args.forEach((arg) => {
|
|
32
|
+
if (specOption === 'key') {
|
|
33
|
+
// Previous arg was -k or --key
|
|
34
|
+
fullSpec.push(arg)
|
|
35
|
+
specOption = false
|
|
36
|
+
} else if (specOption === 'spec') {
|
|
37
|
+
// Previous arg was -s or --spec
|
|
38
|
+
fullSpec.push(asObject(arg))
|
|
39
|
+
specOption = false
|
|
40
|
+
} else if (arg === '-k' || arg === '--key') {
|
|
41
|
+
// Next arg is a key
|
|
42
|
+
specOption = 'key'
|
|
43
|
+
} else if (arg === '-s' || arg === '--spec') {
|
|
44
|
+
// Next arg is a spec
|
|
45
|
+
specOption = 'spec'
|
|
46
|
+
} else {
|
|
47
|
+
// Check if the arg is in the form of -s=<val>/--spec=<val> or -k=<val>/--key=<val>
|
|
48
|
+
const specMatch = arg.match(specFlagMatch)
|
|
49
|
+
if (specMatch?.groups?.spec) {
|
|
50
|
+
// Spec is a JSON object
|
|
51
|
+
fullSpec.push(asObject(specMatch.groups.val))
|
|
52
|
+
} else if (specMatch?.groups?.key) {
|
|
53
|
+
// Key is a string
|
|
54
|
+
fullSpec.push(specMatch.groups.val)
|
|
55
|
+
}
|
|
56
|
+
specOption = false
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return fullSpec
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async run () {
|
|
64
|
+
const { collection } = this.args
|
|
65
|
+
const { name, unique } = this.flags
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const fullSpec = this.getOrderedSpecs()
|
|
69
|
+
const prettySpec = prettyJson(fullSpec)
|
|
70
|
+
|
|
71
|
+
this.log(chalk.blue(`Creating index ${name ? `'${name}' ` : ''}on collection '${collection}'...`))
|
|
72
|
+
this.log(chalk.dim(` Specification:\n${prettySpec}`))
|
|
73
|
+
if (unique) this.log(chalk.dim(` Unique index: ${unique}`))
|
|
74
|
+
|
|
75
|
+
const client = await this.db.connect()
|
|
76
|
+
const coll = await client.collection(collection)
|
|
77
|
+
|
|
78
|
+
// Build options
|
|
79
|
+
const options = {}
|
|
80
|
+
if (name) options.name = name
|
|
81
|
+
if (unique) options.unique = unique
|
|
82
|
+
|
|
83
|
+
const result = await coll.createIndex(fullSpec, options)
|
|
84
|
+
|
|
85
|
+
this.debugLogger?.info?.('Index created successfully:', result)
|
|
86
|
+
|
|
87
|
+
const response = {
|
|
88
|
+
collection,
|
|
89
|
+
indexName: result,
|
|
90
|
+
specification: fullSpec,
|
|
91
|
+
status: 'created',
|
|
92
|
+
namespace: this.rtNamespace,
|
|
93
|
+
timestamp: new Date().toISOString()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (Object.keys(options).length > 0) response.options = options
|
|
97
|
+
|
|
98
|
+
this.log(chalk.green(`Index '${result}' created successfully in the '${collection}' collection`))
|
|
99
|
+
this.log(chalk.dim(` Specification:\n${prettySpec}`))
|
|
100
|
+
if (unique) this.log(chalk.dim(` Unique: ${unique}`))
|
|
101
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
102
|
+
this.log(chalk.dim(` Created: ${new Date().toLocaleString()}`))
|
|
103
|
+
|
|
104
|
+
return response
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.debugLogger?.error?.('Error creating index:', error)
|
|
107
|
+
|
|
108
|
+
this.log(chalk.red('Failed to create index'))
|
|
109
|
+
this.log(chalk.dim(` Collection: ${collection}`))
|
|
110
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
111
|
+
|
|
112
|
+
this.error(`Failed to create index on collection '${collection}': ${error.message}`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Create.description = 'Create a new index on a collection in the database'
|
|
118
|
+
|
|
119
|
+
Create.examples = [
|
|
120
|
+
'$ aio app db index create users --spec \'{"name":1, "age":-1}\'',
|
|
121
|
+
'$ aio app db index create users -s \'{"name":1, "age":-1}\' --name "name_age_index"',
|
|
122
|
+
'$ aio app db index create students -s \'{"name":1}\' --key grade --unique',
|
|
123
|
+
'$ aio app db index create reviews -k sku -k rating',
|
|
124
|
+
'$ aio app db index create products -s \'{"name":"text", "category":"text"}\' --json',
|
|
125
|
+
'$ aio app db index create books -s \'{"author":1}\' -k year',
|
|
126
|
+
'$ aio app db idx create orders --spec \'{"customerId":1}\' --spec \'{"orderDate":-1}\' --name "customer_order_index" --unique'
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
Create.args = {
|
|
130
|
+
collection: Args.string({
|
|
131
|
+
name: 'collection',
|
|
132
|
+
description: 'The name of the collection to create the index on',
|
|
133
|
+
required: true,
|
|
134
|
+
parse: input => isNonEmptyString(input, 'Collection name')
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Create.flags = {
|
|
139
|
+
...DBBaseCommand.flags,
|
|
140
|
+
spec: Flags.string({
|
|
141
|
+
char: 's',
|
|
142
|
+
helpGroup: 'Requires at least one of the index definition',
|
|
143
|
+
description: 'Index specification as a JSON object (e.g., \'{"name":1, "age":-1}\')',
|
|
144
|
+
multiple: true,
|
|
145
|
+
atLeastOne: ['key', 'spec'],
|
|
146
|
+
parse: input => asObject(input, 'Index specification')
|
|
147
|
+
}),
|
|
148
|
+
key: Flags.string({
|
|
149
|
+
char: 'k',
|
|
150
|
+
helpGroup: 'Requires at least one of the index definition',
|
|
151
|
+
description: 'Index key to use with default specification',
|
|
152
|
+
multiple: true,
|
|
153
|
+
atLeastOne: ['key', 'spec'],
|
|
154
|
+
// Note: untrimmed whitespace input is allowed for index keys
|
|
155
|
+
parse: input => isNonEmptyString(input, 'Index key')
|
|
156
|
+
}),
|
|
157
|
+
name: Flags.string({
|
|
158
|
+
char: 'n',
|
|
159
|
+
description: 'A name that uniquely identifies the index',
|
|
160
|
+
// Note: untrimmed whitespace input is allowed for index names
|
|
161
|
+
parse: input => isNonEmptyString(input, 'Index name')
|
|
162
|
+
}),
|
|
163
|
+
unique: Flags.boolean({
|
|
164
|
+
char: 'u',
|
|
165
|
+
description: 'Creates a unique index so that the collection will not accept insertion or update of documents where the index key value matches an existing value in the index',
|
|
166
|
+
default: false
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
Create.aliases = ['app:db:idx:create']
|
|
@@ -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 { 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 Drop extends DBBaseCommand {
|
|
20
|
+
async run () {
|
|
21
|
+
const { collection, indexName } = this.args
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
this.log(chalk.blue(`Dropping index '${indexName}' from collection '${collection}'...`))
|
|
25
|
+
|
|
26
|
+
const client = await this.db.connect()
|
|
27
|
+
const coll = await client.collection(collection)
|
|
28
|
+
|
|
29
|
+
const result = await coll.dropIndex(indexName)
|
|
30
|
+
|
|
31
|
+
this.debugLogger?.info?.('Index dropped successfully:', result)
|
|
32
|
+
|
|
33
|
+
const response = {
|
|
34
|
+
collection,
|
|
35
|
+
indexName,
|
|
36
|
+
status: 'dropped',
|
|
37
|
+
namespace: this.rtNamespace,
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.log(chalk.green(`Index '${indexName}' dropped successfully`))
|
|
43
|
+
this.log(chalk.dim(` Details:\n${prettyJson(result)}`))
|
|
44
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
45
|
+
this.log(chalk.dim(` Dropped: ${new Date().toLocaleString()}`))
|
|
46
|
+
|
|
47
|
+
return response
|
|
48
|
+
} catch (error) {
|
|
49
|
+
this.debugLogger?.error?.('Error dropping index:', error)
|
|
50
|
+
|
|
51
|
+
this.log(chalk.red('Failed to drop index'))
|
|
52
|
+
this.log(chalk.dim(` Collection: ${collection}`))
|
|
53
|
+
this.log(chalk.dim(` Index: ${indexName}`))
|
|
54
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
55
|
+
this.log(chalk.dim(` Error: ${error.message}`))
|
|
56
|
+
|
|
57
|
+
this.error(`Failed to drop index '${indexName}' from collection '${collection}': ${error.message}`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Drop.description = 'Drop an index from a collection in the database'
|
|
63
|
+
|
|
64
|
+
Drop.examples = [
|
|
65
|
+
'$ aio app db index drop users name_age_index',
|
|
66
|
+
'$ aio app db index drop products category_1 --json',
|
|
67
|
+
'$ aio app db idx drop orders orderDate_index'
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
Drop.args = {
|
|
71
|
+
collection: Args.string({
|
|
72
|
+
name: 'collection',
|
|
73
|
+
description: 'The name of the collection to drop the index from',
|
|
74
|
+
required: true,
|
|
75
|
+
parse: input => isNonEmptyString(input, 'Collection name')
|
|
76
|
+
}),
|
|
77
|
+
indexName: Args.string({
|
|
78
|
+
name: 'indexName',
|
|
79
|
+
description: 'The name of the index to drop',
|
|
80
|
+
required: true,
|
|
81
|
+
parse: input => isNonEmptyString(input, 'Index name')
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Drop.flags = DBBaseCommand.flags
|
|
86
|
+
|
|
87
|
+
Drop.aliases = ['app:db:idx:drop']
|
|
@@ -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 = {}
|