configuration-management 0.1.6 → 0.1.10
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 +26 -24
- package/package.json +7 -7
- package/scripts/build-web.js +10 -1
- package/scripts/setup.js +3 -101
- package/src/abdb-config.js +1 -234
- package/src/abdb-helper.js +1 -469
- package/src/index.js +12 -10
- package/src/oauth1a.js +1 -128
- package/src/system-config-crypto.js +1 -106
- package/src/system-config-shared.js +1 -82
- package/web/dist/index.css +294 -0
- package/web/index.js +1 -0
- package/web/src/index.js +2 -0
- package/web/styles.css +326 -0
- package/scripts/templates/exc-runtime.js +0 -14
- package/scripts/templates/web-src-index.html +0 -16
- package/scripts/templates/web-src-index.js +0 -61
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Schema-driven system configuration for **Adobe Commerce** and **Adobe App Builder** sync applications.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Config reading (`getConfig`, ABDB, crypto, Commerce REST) lives in the companion package [`configuration-get-config`](https://www.npmjs.com/package/configuration-get-config). This package adds the React Admin UI, OpenWhisk actions, and App Builder setup.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -27,7 +27,12 @@ npm install react react-dom @adobe/react-spectrum @adobe/uix-guest @adobe/exc-ap
|
|
|
27
27
|
Read a config value from ABDB inside an App Builder action:
|
|
28
28
|
|
|
29
29
|
```js
|
|
30
|
+
// Preferred — config reader package (installed automatically with configuration-management)
|
|
31
|
+
const { getConfig } = require('configuration-get-config')
|
|
32
|
+
|
|
33
|
+
// Or re-exported from this package
|
|
30
34
|
const { getConfig } = require('configuration-management')
|
|
35
|
+
// const { getConfig } = require('configuration-management/config')
|
|
31
36
|
|
|
32
37
|
async function main (params) {
|
|
33
38
|
const apiUrl = await getConfig('sync_general/api/url', params, {
|
|
@@ -40,7 +45,9 @@ async function main (params) {
|
|
|
40
45
|
|
|
41
46
|
## API
|
|
42
47
|
|
|
43
|
-
### Config resolution
|
|
48
|
+
### Config resolution (`configuration-get-config`)
|
|
49
|
+
|
|
50
|
+
Implemented in [`configuration-get-config`](https://www.npmjs.com/package/configuration-get-config) and re-exported here.
|
|
44
51
|
|
|
45
52
|
| Export | Description |
|
|
46
53
|
|--------|-------------|
|
|
@@ -98,8 +105,17 @@ createRoot(document.getElementById('root')).render(
|
|
|
98
105
|
)
|
|
99
106
|
```
|
|
100
107
|
|
|
101
|
-
The web UI is **pre-built** in the package
|
|
102
|
-
|
|
108
|
+
The web UI is **pre-built** in the package. Import the JS entry — styles load automatically:
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
import { ConfigurationManagementApp, configureWeb } from 'configuration-management/web'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or import styles separately:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
import 'configuration-management/web/styles.css'
|
|
118
|
+
```
|
|
103
119
|
|
|
104
120
|
| Export | Description |
|
|
105
121
|
|--------|-------------|
|
|
@@ -117,9 +133,8 @@ OpenWhisk runtime actions and the Commerce Admin extension manifest ship with th
|
|
|
117
133
|
|
|
118
134
|
#### Automatic wiring on `npm install`
|
|
119
135
|
|
|
120
|
-
The package runs a **postinstall** script that
|
|
121
|
-
|
|
122
|
-
1. Patches your project's `app.config.yaml` (if present) with:
|
|
136
|
+
The package runs a **postinstall** script that patches your project's `app.config.yaml`
|
|
137
|
+
(if present) with:
|
|
123
138
|
|
|
124
139
|
```yaml
|
|
125
140
|
extensions:
|
|
@@ -127,20 +142,8 @@ extensions:
|
|
|
127
142
|
$include: node_modules/configuration-management/actions/configurations/ext.config.yaml
|
|
128
143
|
```
|
|
129
144
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
web-src/
|
|
134
|
-
├── index.html
|
|
135
|
-
└── src/
|
|
136
|
-
├── index.js ← imports ConfigurationManagementApp from the package
|
|
137
|
-
├── exc-runtime.js
|
|
138
|
-
└── config.json ← created empty; filled by aio app deploy
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Custom `web-src` files are left untouched unless they were created by a prior install
|
|
142
|
-
(files containing the `configuration-management: auto-generated bootstrap` marker, or
|
|
143
|
-
an older bootstrap that imports `configuration-management/web`).
|
|
145
|
+
It does **not** modify your `web-src/` files. Add the UI to your existing App Builder
|
|
146
|
+
bootstrap manually (see React Admin UI above).
|
|
144
147
|
|
|
145
148
|
- Run `npm install` from your App Builder project root (where `app.config.yaml` lives).
|
|
146
149
|
- Do not use `npm install --ignore-scripts` (that skips postinstall).
|
|
@@ -176,11 +179,10 @@ The bundled `ext.config.yaml` declares all actions under the `ConfigurationManag
|
|
|
176
179
|
|
|
177
180
|
```
|
|
178
181
|
my-app/
|
|
179
|
-
├── app.config.yaml ← $include ext.config
|
|
182
|
+
├── app.config.yaml ← $include ext.config (auto-patched on npm install)
|
|
180
183
|
├── web-src/
|
|
181
|
-
│ ├── index.html
|
|
182
184
|
│ └── src/
|
|
183
|
-
│ ├── index.js ←
|
|
185
|
+
│ ├── index.js ← your app bootstrap + package imports (see above)
|
|
184
186
|
│ └── config.json ← generated by aio app deploy
|
|
185
187
|
├── .env
|
|
186
188
|
└── package.json ← depends on configuration-management
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configuration-management",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Schema-driven system configuration for Adobe Commerce App Builder sync apps. Magento-style scoped config in Adobe App Builder Database (ABDB) with encryption, Commerce REST helpers, and React Admin UI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Adobe Inc.",
|
|
@@ -30,9 +30,10 @@
|
|
|
30
30
|
"./crypto": "./src/system-config-crypto.js",
|
|
31
31
|
"./shared": "./src/system-config-shared.js",
|
|
32
32
|
"./oauth1a": "./src/oauth1a.js",
|
|
33
|
-
"./web": "./web/
|
|
34
|
-
"./web/index.js": "./web/
|
|
35
|
-
"./web/styles.css": "./web/
|
|
33
|
+
"./web": "./web/index.js",
|
|
34
|
+
"./web/index.js": "./web/index.js",
|
|
35
|
+
"./web/styles.css": "./web/styles.css",
|
|
36
|
+
"./web/dist/index.css": "./web/dist/index.css",
|
|
36
37
|
"./web/src/styles/index.css": "./web/src/styles/index.css",
|
|
37
38
|
"./actions/utils": "./actions/utils.js",
|
|
38
39
|
"./actions/ext.config.yaml": "./actions/configurations/ext.config.yaml"
|
|
@@ -63,9 +64,8 @@
|
|
|
63
64
|
"react-router-dom": "^6.8.1"
|
|
64
65
|
},
|
|
65
66
|
"dependencies": {
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"oauth-1.0a": "^2.2.6"
|
|
67
|
+
"configuration-get-config": "^0.1.0",
|
|
68
|
+
"esbuild": "^0.25.12"
|
|
69
69
|
},
|
|
70
70
|
"repository": {
|
|
71
71
|
"type": "git",
|
package/scripts/build-web.js
CHANGED
|
@@ -20,6 +20,8 @@ async function main () {
|
|
|
20
20
|
const entry = path.join(pkgRoot, 'web/src/index.js')
|
|
21
21
|
const outdir = path.join(pkgRoot, 'web/dist')
|
|
22
22
|
const outfile = path.join(outdir, 'index.js')
|
|
23
|
+
const stylesSrc = path.join(pkgRoot, 'web/src/styles/index.css')
|
|
24
|
+
const stylesFlat = path.join(pkgRoot, 'web/styles.css')
|
|
23
25
|
|
|
24
26
|
fs.mkdirSync(outdir, { recursive: true })
|
|
25
27
|
|
|
@@ -31,12 +33,19 @@ async function main () {
|
|
|
31
33
|
outfile,
|
|
32
34
|
packages: 'external',
|
|
33
35
|
jsx: 'automatic',
|
|
34
|
-
loader: { '.js': 'jsx' },
|
|
36
|
+
loader: { '.js': 'jsx', '.css': 'css' },
|
|
35
37
|
target: ['chrome79', 'firefox85', 'safari13'],
|
|
36
38
|
logLevel: 'info'
|
|
37
39
|
})
|
|
38
40
|
|
|
41
|
+
// Parcel does not resolve nested @import inside node_modules — ship a flat file.
|
|
42
|
+
fs.copyFileSync(stylesSrc, stylesFlat)
|
|
43
|
+
|
|
39
44
|
console.log('[configuration-management] built web/dist/index.js')
|
|
45
|
+
if (fs.existsSync(path.join(outdir, 'index.css'))) {
|
|
46
|
+
console.log('[configuration-management] built web/dist/index.css')
|
|
47
|
+
}
|
|
48
|
+
console.log('[configuration-management] copied web/styles.css')
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
if (require.main === module) {
|
package/scripts/setup.js
CHANGED
|
@@ -10,9 +10,6 @@ const path = require('path')
|
|
|
10
10
|
const EXTENSION_POINT = 'commerce/backend-ui/1'
|
|
11
11
|
const INCLUDE_REL = 'node_modules/configuration-management/actions/configurations/ext.config.yaml'
|
|
12
12
|
const MARKER = '# configuration-management (auto-linked on npm install)'
|
|
13
|
-
const WEB_BOOTSTRAP_MARKER = 'configuration-management: auto-generated bootstrap'
|
|
14
|
-
|
|
15
|
-
const TEMPLATES_DIR = path.join(__dirname, 'templates')
|
|
16
13
|
|
|
17
14
|
function escapeRe (str) {
|
|
18
15
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
@@ -21,10 +18,7 @@ function escapeRe (str) {
|
|
|
21
18
|
function findProjectRoot (startDir) {
|
|
22
19
|
let dir = startDir
|
|
23
20
|
while (dir && dir !== path.dirname(dir)) {
|
|
24
|
-
if (
|
|
25
|
-
fs.existsSync(path.join(dir, 'app.config.yaml')) ||
|
|
26
|
-
fs.existsSync(path.join(dir, 'web-src'))
|
|
27
|
-
) {
|
|
21
|
+
if (fs.existsSync(path.join(dir, 'app.config.yaml'))) {
|
|
28
22
|
return dir
|
|
29
23
|
}
|
|
30
24
|
dir = path.dirname(dir)
|
|
@@ -41,57 +35,6 @@ function resolveProjectRoot () {
|
|
|
41
35
|
return findProjectRoot(process.cwd())
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
function readTemplate (name) {
|
|
45
|
-
return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf8')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function writeIfMissingOrManaged (filePath, content, marker) {
|
|
49
|
-
if (fs.existsSync(filePath)) {
|
|
50
|
-
const existing = fs.readFileSync(filePath, 'utf8')
|
|
51
|
-
if (existing.includes(marker)) {
|
|
52
|
-
const dir = path.dirname(filePath)
|
|
53
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
54
|
-
const changed = existing !== content
|
|
55
|
-
if (changed) {
|
|
56
|
-
fs.writeFileSync(filePath, content, 'utf8')
|
|
57
|
-
}
|
|
58
|
-
return { changed, reason: changed ? 'updated-managed' : 'unchanged-managed', path: filePath }
|
|
59
|
-
}
|
|
60
|
-
return { changed: false, reason: 'skipped-custom-file', path: filePath }
|
|
61
|
-
}
|
|
62
|
-
const dir = path.dirname(filePath)
|
|
63
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
64
|
-
fs.writeFileSync(filePath, content, 'utf8')
|
|
65
|
-
return { changed: true, reason: 'written', path: filePath }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function writeWebSrcIndex (projectRoot) {
|
|
69
|
-
const filePath = path.join(projectRoot, 'web-src', 'src', 'index.js')
|
|
70
|
-
const content = readTemplate('web-src-index.js')
|
|
71
|
-
|
|
72
|
-
if (!fs.existsSync(filePath)) {
|
|
73
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
74
|
-
fs.writeFileSync(filePath, content, 'utf8')
|
|
75
|
-
return { changed: true, reason: 'written', path: filePath }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const existing = fs.readFileSync(filePath, 'utf8')
|
|
79
|
-
if (existing.includes(WEB_BOOTSTRAP_MARKER)) {
|
|
80
|
-
const changed = existing !== content
|
|
81
|
-
if (changed) {
|
|
82
|
-
fs.writeFileSync(filePath, content, 'utf8')
|
|
83
|
-
}
|
|
84
|
-
return { changed, reason: changed ? 'updated-managed' : 'unchanged-managed', path: filePath }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (existing.includes('configuration-management/web')) {
|
|
88
|
-
fs.writeFileSync(filePath, content, 'utf8')
|
|
89
|
-
return { changed: true, reason: 'migrated-stale-bootstrap', path: filePath }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return { changed: false, reason: 'skipped-custom-file', path: filePath }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
38
|
function alreadyLinked (content) {
|
|
96
39
|
return content.includes('configuration-management/actions/configurations/ext.config.yaml')
|
|
97
40
|
}
|
|
@@ -176,38 +119,6 @@ function setupAppConfig (projectRoot) {
|
|
|
176
119
|
return { changed: true, reason, detail: INCLUDE_REL }
|
|
177
120
|
}
|
|
178
121
|
|
|
179
|
-
function setupWebSrc (projectRoot) {
|
|
180
|
-
const webSrcDir = path.join(projectRoot, 'web-src')
|
|
181
|
-
const results = []
|
|
182
|
-
|
|
183
|
-
const indexJs = writeWebSrcIndex(projectRoot)
|
|
184
|
-
results.push(indexJs)
|
|
185
|
-
|
|
186
|
-
const indexHtml = writeIfMissingOrManaged(
|
|
187
|
-
path.join(webSrcDir, 'index.html'),
|
|
188
|
-
readTemplate('web-src-index.html'),
|
|
189
|
-
'configuration-management'
|
|
190
|
-
)
|
|
191
|
-
results.push(indexHtml)
|
|
192
|
-
|
|
193
|
-
const excRuntime = writeIfMissingOrManaged(
|
|
194
|
-
path.join(webSrcDir, 'src', 'exc-runtime.js'),
|
|
195
|
-
readTemplate('exc-runtime.js'),
|
|
196
|
-
'Module Runtime: Needs to be within an iframe'
|
|
197
|
-
)
|
|
198
|
-
results.push(excRuntime)
|
|
199
|
-
|
|
200
|
-
const configPath = path.join(webSrcDir, 'src', 'config.json')
|
|
201
|
-
if (!fs.existsSync(configPath)) {
|
|
202
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true })
|
|
203
|
-
fs.writeFileSync(configPath, '{}\n', 'utf8')
|
|
204
|
-
results.push({ changed: true, reason: 'created-config-json', path: configPath })
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const changed = results.some((r) => r.changed)
|
|
208
|
-
return { changed, results }
|
|
209
|
-
}
|
|
210
|
-
|
|
211
122
|
function main () {
|
|
212
123
|
if (process.env.CONFIGURATION_MANAGEMENT_SKIP_SETUP === '1') {
|
|
213
124
|
return
|
|
@@ -228,18 +139,10 @@ function main () {
|
|
|
228
139
|
`[configuration-management] Updated app.config.yaml (${app.reason}):\n` +
|
|
229
140
|
` $include: ${app.detail}`
|
|
230
141
|
)
|
|
142
|
+
return
|
|
231
143
|
}
|
|
232
144
|
|
|
233
|
-
|
|
234
|
-
for (const r of web.results || []) {
|
|
235
|
-
if (r.changed) {
|
|
236
|
-
console.log(`[configuration-management] Wrote ${r.path}`)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!app.changed && !web.changed) {
|
|
241
|
-
console.log('[configuration-management] Project already configured.')
|
|
242
|
-
}
|
|
145
|
+
console.log('[configuration-management] app.config.yaml already configured.')
|
|
243
146
|
}
|
|
244
147
|
|
|
245
148
|
if (require.main === module) {
|
|
@@ -254,7 +157,6 @@ if (require.main === module) {
|
|
|
254
157
|
module.exports = {
|
|
255
158
|
patchAppConfig,
|
|
256
159
|
setupAppConfig,
|
|
257
|
-
setupWebSrc,
|
|
258
160
|
INCLUDE_REL,
|
|
259
161
|
EXTENSION_POINT
|
|
260
162
|
}
|
package/src/abdb-config.js
CHANGED
|
@@ -5,237 +5,4 @@ you may not use this file except in compliance with the License. You may obtain
|
|
|
5
5
|
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const {
|
|
10
|
-
isValidPath,
|
|
11
|
-
toStateKey,
|
|
12
|
-
buildInheritanceChain,
|
|
13
|
-
normalizeScope,
|
|
14
|
-
normalizeScopeId
|
|
15
|
-
} = require('./system-config-shared')
|
|
16
|
-
const { getClient } = require('./abdb-helper')
|
|
17
|
-
const { isEncrypted, decrypt } = require('./system-config-crypto')
|
|
18
|
-
|
|
19
|
-
const COLLECTION = 'system_config_data'
|
|
20
|
-
const CACHE_TTL_MS = 5 * 60 * 1000
|
|
21
|
-
|
|
22
|
-
// Per-lookup result cache.
|
|
23
|
-
const cache = new Map() // key: `${scope}:${scopeId}:${path}` → { value, expiresAt }
|
|
24
|
-
|
|
25
|
-
// Commerce code → numeric id maps. Refreshed at most every CACHE_TTL_MS.
|
|
26
|
-
let websiteCodeToId = null // Map<code, id>
|
|
27
|
-
let websiteCodeToIdAt = 0
|
|
28
|
-
let storeCodeToId = null // Map<code, id> + parentWebsiteId
|
|
29
|
-
let storeCodeToIdAt = 0
|
|
30
|
-
|
|
31
|
-
function maybeParseJson (value) {
|
|
32
|
-
if (typeof value !== 'string') return value
|
|
33
|
-
const trimmed = value.trim()
|
|
34
|
-
if (!trimmed) return value
|
|
35
|
-
if (!(trimmed.startsWith('{') || trimmed.startsWith('['))) return value
|
|
36
|
-
try { return JSON.parse(trimmed) } catch { return value }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function tryFindOne (collection, query) {
|
|
40
|
-
try {
|
|
41
|
-
const arr = await collection.find(query).limit(1).toArray()
|
|
42
|
-
return arr && arr.length ? arr[0] : null
|
|
43
|
-
} catch (err) {
|
|
44
|
-
const msg = err && err.message ? String(err.message) : String(err)
|
|
45
|
-
if (/not found/i.test(msg)) return null
|
|
46
|
-
throw err
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function pickCommerceCreds (params) {
|
|
51
|
-
return {
|
|
52
|
-
url: params.COMMERCE_BASE_URL || process.env.COMMERCE_BASE_URL,
|
|
53
|
-
consumerKey: params.COMMERCE_CONSUMER_KEY || process.env.COMMERCE_CONSUMER_KEY,
|
|
54
|
-
consumerSecret: params.COMMERCE_CONSUMER_SECRET || process.env.COMMERCE_CONSUMER_SECRET,
|
|
55
|
-
accessToken: params.COMMERCE_ACCESS_TOKEN || process.env.COMMERCE_ACCESS_TOKEN,
|
|
56
|
-
accessTokenSecret: params.COMMERCE_ACCESS_TOKEN_SECRET || process.env.COMMERCE_ACCESS_TOKEN_SECRET
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function loadWebsiteCodeMap (params) {
|
|
61
|
-
const now = Date.now()
|
|
62
|
-
if (websiteCodeToId && (now - websiteCodeToIdAt) < CACHE_TTL_MS) return websiteCodeToId
|
|
63
|
-
|
|
64
|
-
const creds = pickCommerceCreds(params)
|
|
65
|
-
if (!creds.url) return websiteCodeToId || new Map()
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const oauth = getCommerceOauthClient(creds, { error: () => {}, info: () => {} })
|
|
69
|
-
const websites = await oauth.get('store/websites')
|
|
70
|
-
const map = new Map()
|
|
71
|
-
if (Array.isArray(websites)) {
|
|
72
|
-
for (const w of websites) {
|
|
73
|
-
if (w && w.code != null && w.id != null) {
|
|
74
|
-
map.set(String(w.code), String(w.id))
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
websiteCodeToId = map
|
|
79
|
-
websiteCodeToIdAt = now
|
|
80
|
-
return map
|
|
81
|
-
} catch (_) {
|
|
82
|
-
return websiteCodeToId || new Map()
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function loadStoreViewCodeMap (params) {
|
|
87
|
-
const now = Date.now()
|
|
88
|
-
if (storeCodeToId && (now - storeCodeToIdAt) < CACHE_TTL_MS) return storeCodeToId
|
|
89
|
-
|
|
90
|
-
const creds = pickCommerceCreds(params)
|
|
91
|
-
if (!creds.url) return storeCodeToId || new Map()
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const oauth = getCommerceOauthClient(creds, { error: () => {}, info: () => {} })
|
|
95
|
-
const stores = await oauth.get('store/storeViews')
|
|
96
|
-
const map = new Map()
|
|
97
|
-
if (Array.isArray(stores)) {
|
|
98
|
-
for (const s of stores) {
|
|
99
|
-
if (s && s.code != null && s.id != null) {
|
|
100
|
-
map.set(String(s.code), { id: String(s.id), websiteId: s.website_id != null ? String(s.website_id) : null })
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
storeCodeToId = map
|
|
105
|
-
storeCodeToIdAt = now
|
|
106
|
-
return map
|
|
107
|
-
} catch (_) {
|
|
108
|
-
return storeCodeToId || new Map()
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Resolve a scope code (e.g. website 'ch', store view 'en_ch') to its numeric
|
|
114
|
-
* id via Commerce REST. Returns null when the code can't be resolved AND no
|
|
115
|
-
* verbatim fallback is wanted.
|
|
116
|
-
*/
|
|
117
|
-
async function resolveScopeId (scope, code, params) {
|
|
118
|
-
if (!code) return null
|
|
119
|
-
if (scope === 'websites') {
|
|
120
|
-
const map = await loadWebsiteCodeMap(params)
|
|
121
|
-
return map.get(String(code)) || null
|
|
122
|
-
}
|
|
123
|
-
if (scope === 'stores') {
|
|
124
|
-
const map = await loadStoreViewCodeMap(params)
|
|
125
|
-
return map.get(String(code))?.id || null
|
|
126
|
-
}
|
|
127
|
-
return null
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Look up a single config value from ABDB.
|
|
132
|
-
*
|
|
133
|
-
* @param {string} path `<section>/<group>/<field>` (e.g. 'campaign_general/url/url')
|
|
134
|
-
* @param {object} [params] action params containing OAuth + crypto + Commerce creds.
|
|
135
|
-
* Falls back to process.env when omitted.
|
|
136
|
-
* @param {object} [options]
|
|
137
|
-
* @param {string} [options.scope='default'] 'default' | 'websites' | 'stores'
|
|
138
|
-
* @param {string} [options.scopeId] website / store id (numeric string); takes precedence over scopeCode
|
|
139
|
-
* @param {string} [options.scopeCode] website / store-view CODE — resolved to numeric id via Commerce REST
|
|
140
|
-
* @param {string|number} [options.parentWebsiteId] used when scope='stores' to fall back to the parent website
|
|
141
|
-
* @param {string} [options.parentWebsiteCode] same as parentWebsiteId but resolved from a website code
|
|
142
|
-
* @param {boolean} [options.fresh] bypass the cache
|
|
143
|
-
* @returns {Promise<*|null>}
|
|
144
|
-
*/
|
|
145
|
-
async function getConfig (path, params = {}, options = {}) {
|
|
146
|
-
if (!isValidPath(path)) return null
|
|
147
|
-
|
|
148
|
-
let scope
|
|
149
|
-
try {
|
|
150
|
-
scope = normalizeScope(options.scope)
|
|
151
|
-
} catch (_) {
|
|
152
|
-
return null
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// 1. Resolve the active scope id (numeric).
|
|
156
|
-
let resolvedScopeId
|
|
157
|
-
if (scope === 'default') {
|
|
158
|
-
resolvedScopeId = '0'
|
|
159
|
-
} else if (options.scopeId != null && options.scopeId !== '') {
|
|
160
|
-
resolvedScopeId = String(options.scopeId)
|
|
161
|
-
} else if (options.scopeCode) {
|
|
162
|
-
const fromCommerce = await resolveScopeId(scope, options.scopeCode, params)
|
|
163
|
-
// If Commerce isn't reachable, fall back to using the code verbatim — the
|
|
164
|
-
// value still gets queried, and the legacy `getSystemConfig` shim writes
|
|
165
|
-
// under the literal code so this keeps working.
|
|
166
|
-
resolvedScopeId = fromCommerce || String(options.scopeCode)
|
|
167
|
-
} else {
|
|
168
|
-
return null
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 2. Resolve the parent website id for store-scope inheritance.
|
|
172
|
-
let parentWebsiteId = options.parentWebsiteId
|
|
173
|
-
if (parentWebsiteId == null && options.parentWebsiteCode) {
|
|
174
|
-
parentWebsiteId =
|
|
175
|
-
await resolveScopeId('websites', options.parentWebsiteCode, params) ||
|
|
176
|
-
String(options.parentWebsiteCode)
|
|
177
|
-
}
|
|
178
|
-
// Auto-derive parentWebsiteId from the store view itself when not given.
|
|
179
|
-
if (parentWebsiteId == null && scope === 'stores' && options.scopeCode) {
|
|
180
|
-
const sMap = await loadStoreViewCodeMap(params)
|
|
181
|
-
parentWebsiteId = sMap.get(String(options.scopeCode))?.websiteId || undefined
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
let normalizedScopeId
|
|
185
|
-
try {
|
|
186
|
-
normalizedScopeId = normalizeScopeId(scope, resolvedScopeId)
|
|
187
|
-
} catch (_) {
|
|
188
|
-
return null
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const cacheKey = `${scope}:${normalizedScopeId}:${path}`
|
|
192
|
-
const now = Date.now()
|
|
193
|
-
if (!options.fresh) {
|
|
194
|
-
const c = cache.get(cacheKey)
|
|
195
|
-
if (c && c.expiresAt > now) return c.value
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
let handle
|
|
199
|
-
try {
|
|
200
|
-
handle = await getClient(params)
|
|
201
|
-
} catch (_) {
|
|
202
|
-
return null
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
const collection = await handle.client.collection(COLLECTION)
|
|
207
|
-
const chain = buildInheritanceChain(scope, normalizedScopeId, parentWebsiteId)
|
|
208
|
-
|
|
209
|
-
let resolved = null
|
|
210
|
-
for (const link of chain) {
|
|
211
|
-
const id = toStateKey(link.scope, link.scopeId, path)
|
|
212
|
-
const doc = await tryFindOne(collection, { _id: id })
|
|
213
|
-
if (!doc || doc.value === undefined) continue
|
|
214
|
-
let value = doc.value
|
|
215
|
-
if (isEncrypted(value)) {
|
|
216
|
-
try { value = decrypt(value, params) } catch (_) { /* keep raw */ }
|
|
217
|
-
}
|
|
218
|
-
value = maybeParseJson(value)
|
|
219
|
-
resolved = value
|
|
220
|
-
break
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
cache.set(cacheKey, { value: resolved, expiresAt: now + CACHE_TTL_MS })
|
|
224
|
-
return resolved
|
|
225
|
-
} finally {
|
|
226
|
-
try { await handle.close() } catch (_) { /* noop */ }
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** Clear the entire in-process cache (e.g. after a re-migration). */
|
|
231
|
-
function clearAbdbConfigCache () {
|
|
232
|
-
cache.clear()
|
|
233
|
-
websiteCodeToId = null
|
|
234
|
-
storeCodeToId = null
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
module.exports = {
|
|
238
|
-
COLLECTION,
|
|
239
|
-
getConfig,
|
|
240
|
-
clearAbdbConfigCache
|
|
241
|
-
}
|
|
8
|
+
module.exports = require('configuration-get-config/config')
|