@adobedjangir/commerce-admin-management 0.0.15 → 0.0.17
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/package.json +1 -1
- package/scripts/setup.js +134 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobedjangir/commerce-admin-management",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
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.",
|
package/scripts/setup.js
CHANGED
|
@@ -591,27 +591,142 @@ function ensureHostDeps (projectRoot) {
|
|
|
591
591
|
try { pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) } catch (_) {
|
|
592
592
|
return { changed: false, reason: 'unreadable-package-json' }
|
|
593
593
|
}
|
|
594
|
-
pkg.dependencies = pkg.dependencies || {}
|
|
595
594
|
|
|
595
|
+
// Decide which deps need bumping by reading the current package.json.
|
|
596
596
|
const bumped = []
|
|
597
597
|
for (const [name, floor] of Object.entries(REQUIRED_HOST_DEPS)) {
|
|
598
|
-
const declared = pkg.dependencies
|
|
598
|
+
const declared = (pkg.dependencies && pkg.dependencies[name]) ||
|
|
599
|
+
(pkg.devDependencies && pkg.devDependencies[name])
|
|
599
600
|
if (declared && satisfiesFloor(declared, floor)) continue
|
|
600
|
-
// Move it to dependencies (out of devDependencies if it was there) and
|
|
601
|
-
// set to the floor.
|
|
602
|
-
pkg.dependencies[name] = floor
|
|
603
|
-
if (pkg.devDependencies && pkg.devDependencies[name]) {
|
|
604
|
-
delete pkg.devDependencies[name]
|
|
605
|
-
}
|
|
606
601
|
bumped.push({ name, was: declared || '(missing)', now: floor })
|
|
607
602
|
}
|
|
608
603
|
|
|
609
604
|
if (bumped.length === 0) return { changed: false, reason: 'already-satisfies' }
|
|
610
605
|
|
|
611
|
-
|
|
606
|
+
// Persist via `npm pkg set` rather than fs.writeFileSync. The outer
|
|
607
|
+
// `npm install <pkg>` keeps a buffered copy of package.json that it
|
|
608
|
+
// writes at the very end of the install (to add the package itself
|
|
609
|
+
// as a dependency) — that write clobbers anything we'd done via
|
|
610
|
+
// fs.writeFileSync. `npm pkg set` goes through npm's own metadata
|
|
611
|
+
// layer and persists across that final write.
|
|
612
|
+
const { execSync } = require('child_process')
|
|
613
|
+
const args = bumped
|
|
614
|
+
.map((b) => `dependencies.${b.name}=${b.now}`)
|
|
615
|
+
// Some keys contain "@" which `npm pkg set` accepts unquoted; we
|
|
616
|
+
// shell-quote the whole assignment to be safe.
|
|
617
|
+
.map((a) => `'${a.replace(/'/g, "'\\''")}'`)
|
|
618
|
+
.join(' ')
|
|
619
|
+
try {
|
|
620
|
+
execSync(`npm pkg set ${args}`, {
|
|
621
|
+
cwd: projectRoot,
|
|
622
|
+
stdio: 'pipe',
|
|
623
|
+
env: { ...process.env, COMMERCE_ADMIN_MANAGEMENT_SKIP_SETUP: '1' }
|
|
624
|
+
})
|
|
625
|
+
} catch (e) {
|
|
626
|
+
return { changed: false, reason: `npm-pkg-set-failed: ${e.message}` }
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Also remove from devDependencies so the bumped value in dependencies
|
|
630
|
+
// is the only spec npm will see on the next resolution.
|
|
631
|
+
const inDev = bumped.filter((b) => pkg.devDependencies && pkg.devDependencies[b.name])
|
|
632
|
+
if (inDev.length) {
|
|
633
|
+
const delArgs = inDev.map((b) => `'devDependencies.${b.name}'`).join(' ')
|
|
634
|
+
try {
|
|
635
|
+
execSync(`npm pkg delete ${delArgs}`, {
|
|
636
|
+
cwd: projectRoot,
|
|
637
|
+
stdio: 'pipe',
|
|
638
|
+
env: { ...process.env, COMMERCE_ADMIN_MANAGEMENT_SKIP_SETUP: '1' }
|
|
639
|
+
})
|
|
640
|
+
} catch (_) { /* non-fatal */ }
|
|
641
|
+
}
|
|
642
|
+
|
|
612
643
|
return { changed: true, bumped }
|
|
613
644
|
}
|
|
614
645
|
|
|
646
|
+
/**
|
|
647
|
+
* Seed the host's .env with the two values our actions can't run without:
|
|
648
|
+
* - AIO_DB_REGION defaults to "emea" (override after install if needed)
|
|
649
|
+
* - SYSTEM_CONFIG_CRYPT_KEY freshly generated AES-256 key, base64-encoded
|
|
650
|
+
*
|
|
651
|
+
* Never overwrites an existing value. The crypt key in particular must
|
|
652
|
+
* remain stable for the life of the workspace — rotating it makes every
|
|
653
|
+
* encrypted value already in ABDB undecryptable. So we only write it
|
|
654
|
+
* when the key is missing or empty.
|
|
655
|
+
*
|
|
656
|
+
* Returns { changed, set: [{key, source}], file }.
|
|
657
|
+
*/
|
|
658
|
+
function ensureEnvDefaults (projectRoot) {
|
|
659
|
+
const envPath = path.join(projectRoot, '.env')
|
|
660
|
+
let lines = []
|
|
661
|
+
let existed = false
|
|
662
|
+
if (fs.existsSync(envPath)) {
|
|
663
|
+
existed = true
|
|
664
|
+
try {
|
|
665
|
+
lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/)
|
|
666
|
+
} catch (_) {
|
|
667
|
+
return { changed: false, set: [], file: envPath, reason: 'unreadable' }
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Read current values (ignore comments + blanks). Treat KEY= (empty)
|
|
672
|
+
// as missing so a half-stubbed .env still gets populated.
|
|
673
|
+
const current = new Map()
|
|
674
|
+
for (const raw of lines) {
|
|
675
|
+
const line = raw.trim()
|
|
676
|
+
if (!line || line.startsWith('#')) continue
|
|
677
|
+
const eq = line.indexOf('=')
|
|
678
|
+
if (eq === -1) continue
|
|
679
|
+
const k = line.slice(0, eq).trim()
|
|
680
|
+
const v = line.slice(eq + 1).trim()
|
|
681
|
+
if (k) current.set(k, v)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const set = []
|
|
685
|
+
const defaults = [
|
|
686
|
+
{ key: 'AIO_DB_REGION', value: 'emea',
|
|
687
|
+
comment: '# App Builder Database region — one of: amer | emea | apac | aus' },
|
|
688
|
+
{ key: 'SYSTEM_CONFIG_CRYPT_KEY', value: () => require('crypto').randomBytes(32).toString('base64'),
|
|
689
|
+
comment: '# AES-256 master key for at-rest encryption.\n# DO NOT rotate — values already in ABDB become undecryptable if you do.\n# Auto-generated on install; back this up like a database password.' }
|
|
690
|
+
]
|
|
691
|
+
|
|
692
|
+
// Build additions for keys that are missing OR empty.
|
|
693
|
+
const additions = []
|
|
694
|
+
for (const def of defaults) {
|
|
695
|
+
const cur = current.get(def.key)
|
|
696
|
+
if (cur && cur !== '' && cur !== '""' && cur !== "''") continue
|
|
697
|
+
const v = typeof def.value === 'function' ? def.value() : def.value
|
|
698
|
+
additions.push({ key: def.key, value: v, comment: def.comment })
|
|
699
|
+
set.push({ key: def.key, source: cur === undefined ? 'added' : 'filled-empty' })
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (additions.length === 0) return { changed: false, set: [], file: envPath }
|
|
703
|
+
|
|
704
|
+
// If the file existed, we either replace empty assignments in place
|
|
705
|
+
// (preserves user comments / ordering) or append at the bottom.
|
|
706
|
+
let next = existed ? lines.slice() : []
|
|
707
|
+
for (const add of additions) {
|
|
708
|
+
const cur = current.get(add.key)
|
|
709
|
+
if (cur === '' || cur === '""' || cur === "''") {
|
|
710
|
+
// Replace the empty line in place.
|
|
711
|
+
for (let i = 0; i < next.length; i++) {
|
|
712
|
+
const trimmed = next[i].trim()
|
|
713
|
+
if (trimmed.startsWith(add.key + '=') || trimmed.startsWith(add.key + ' =')) {
|
|
714
|
+
next[i] = `${add.key}=${add.value}`
|
|
715
|
+
break
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
// Append.
|
|
720
|
+
if (next.length && next[next.length - 1].trim() !== '') next.push('')
|
|
721
|
+
next.push(add.comment)
|
|
722
|
+
next.push(`${add.key}=${add.value}`)
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
fs.writeFileSync(envPath, next.join('\n') + (next[next.length - 1] === '' ? '' : '\n'), 'utf8')
|
|
727
|
+
return { changed: true, set, file: envPath, existed }
|
|
728
|
+
}
|
|
729
|
+
|
|
615
730
|
function setupAppConfig (projectRoot) {
|
|
616
731
|
const appConfigPath = path.join(projectRoot, 'app.config.yaml')
|
|
617
732
|
if (!fs.existsSync(appConfigPath)) {
|
|
@@ -658,6 +773,16 @@ function main () {
|
|
|
658
773
|
console.log('[@adobedjangir/commerce-admin-management] removed src/dx-excshell-1/ (replaced by commerce/backend-ui/1)')
|
|
659
774
|
}
|
|
660
775
|
|
|
776
|
+
// Seed .env with AIO_DB_REGION + SYSTEM_CONFIG_CRYPT_KEY so the consumer
|
|
777
|
+
// doesn't have to. Never overwrites an existing crypt key — see
|
|
778
|
+
// ensureEnvDefaults for why.
|
|
779
|
+
const env = ensureEnvDefaults(projectRoot)
|
|
780
|
+
if (env.changed) {
|
|
781
|
+
for (const s of env.set) {
|
|
782
|
+
console.log(`[@adobedjangir/commerce-admin-management] .env ${s.source}: ${s.key}`)
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
661
786
|
// Bump host package.json so React-18 + Spectrum-4 peers are satisfied
|
|
662
787
|
// without the consumer running a long `npm install --save react@^18 ...`
|
|
663
788
|
// chant. If anything was bumped, automatically re-run npm install so the
|