configuration-management 0.1.0

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.
Files changed (35) hide show
  1. package/README.md +199 -0
  2. package/actions/configurations/commerce/index.js +55 -0
  3. package/actions/configurations/export-config/index.js +259 -0
  4. package/actions/configurations/ext.config.yaml +151 -0
  5. package/actions/configurations/import-config/index.js +544 -0
  6. package/actions/configurations/registration/index.js +37 -0
  7. package/actions/configurations/sync-store-mappings-from-commerce/index.js +199 -0
  8. package/actions/configurations/system-config-list/index.js +127 -0
  9. package/actions/configurations/system-config-save/index.js +160 -0
  10. package/actions/configurations/system-config-schema/index.js +327 -0
  11. package/actions/utils.js +73 -0
  12. package/package.json +74 -0
  13. package/scripts/setup-app-config.js +114 -0
  14. package/src/abdb-config.js +241 -0
  15. package/src/abdb-helper.js +476 -0
  16. package/src/index.js +20 -0
  17. package/src/oauth1a.js +135 -0
  18. package/src/system-config-crypto.js +113 -0
  19. package/src/system-config-shared.js +89 -0
  20. package/web/src/components/App.js +47 -0
  21. package/web/src/components/AppSectionNav.js +49 -0
  22. package/web/src/components/ExtensionRegistration.js +33 -0
  23. package/web/src/components/MainPage.js +46 -0
  24. package/web/src/components/SystemConfig.js +1464 -0
  25. package/web/src/components/SystemConfigSchemaEditor.js +459 -0
  26. package/web/src/hooks/useConfirm.js +355 -0
  27. package/web/src/hooks/useSystemConfig.js +238 -0
  28. package/web/src/hooks/useSystemConfigSchema.js +102 -0
  29. package/web/src/index.js +41 -0
  30. package/web/src/schema/systemConfigSchema.js +82 -0
  31. package/web/src/settings.js +57 -0
  32. package/web/src/styles/index.css +326 -0
  33. package/web/src/theme.js +104 -0
  34. package/web/src/utils/storeMappingsFromCommerceRest.js +73 -0
  35. package/web/src/utils.js +52 -0
@@ -0,0 +1,41 @@
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
+
8
+ import App from './components/App'
9
+ import { MainPage } from './components/MainPage'
10
+ import ExtensionRegistration from './components/ExtensionRegistration'
11
+ import SystemConfig from './components/SystemConfig'
12
+ import SystemConfigSchemaEditor from './components/SystemConfigSchemaEditor'
13
+ import AppSectionNav, { NAV_ITEMS } from './components/AppSectionNav'
14
+
15
+ export { useSystemConfig } from './hooks/useSystemConfig'
16
+ export { useSystemConfigSchema } from './hooks/useSystemConfigSchema'
17
+ export { useConfirm } from './hooks/useConfirm'
18
+
19
+ export * from './schema/systemConfigSchema'
20
+ export { buildStoreMappingsFromCommercePayload } from './utils/storeMappingsFromCommerceRest'
21
+ export { callAction } from './utils'
22
+ export {
23
+ configureWeb,
24
+ getExtensionId,
25
+ getActionKey,
26
+ DEFAULT_ACTION_KEYS
27
+ } from './settings'
28
+ export { THEME, PALETTE, RADIUS, SHADOW, SPACE, FONT } from './theme'
29
+
30
+ export {
31
+ App,
32
+ MainPage,
33
+ ExtensionRegistration,
34
+ SystemConfig,
35
+ SystemConfigSchemaEditor,
36
+ AppSectionNav,
37
+ NAV_ITEMS
38
+ }
39
+
40
+ /** Full Commerce Admin extension shell (router + Spectrum provider). */
41
+ export { default as ConfigurationManagementApp } from './components/App'
@@ -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
+
8
+ /**
9
+ * Schema is no longer defined statically — it is stored in App Builder DB
10
+ * and managed entirely through the Schema Designer UI. This module only
11
+ * exposes helpers for working with whatever schema shape is loaded at runtime.
12
+ *
13
+ * Schema shape (returned from the system-config-schema action):
14
+ * {
15
+ * sections: [{
16
+ * id, label,
17
+ * groups: [{
18
+ * id, label,
19
+ * fields: [{
20
+ * id, label, type, default,
21
+ * showIn: ['default' | 'websites' | 'stores'],
22
+ * sensitive?: boolean,
23
+ * options?: [{ value, label }] // type === 'select'
24
+ * }]
25
+ * }]
26
+ * }]
27
+ * }
28
+ */
29
+
30
+ export const FIELD_TYPES = ['text', 'textarea', 'password', 'number', 'select', 'boolean']
31
+ export const SCOPES = ['default', 'websites', 'stores']
32
+
33
+ const SENSITIVE_FIELD_TYPES = new Set(['password'])
34
+
35
+ export function emptySchema () {
36
+ return { sections: [] }
37
+ }
38
+
39
+ export function getFieldPath (sectionId, groupId, fieldId) {
40
+ return `${sectionId}/${groupId}/${fieldId}`
41
+ }
42
+
43
+ export function isFieldSensitive (field) {
44
+ return !!field?.sensitive || SENSITIVE_FIELD_TYPES.has(field?.type)
45
+ }
46
+
47
+ export function isFieldVisibleAtScope (field, scope) {
48
+ const allowed = field?.showIn || ['default']
49
+ return allowed.includes(scope)
50
+ }
51
+
52
+ export function flattenFields (schema) {
53
+ const out = []
54
+ if (!schema || !Array.isArray(schema.sections)) return out
55
+ for (const section of schema.sections) {
56
+ if (!Array.isArray(section.groups)) continue
57
+ for (const group of section.groups) {
58
+ if (!Array.isArray(group.fields)) continue
59
+ for (const field of group.fields) {
60
+ out.push({
61
+ section,
62
+ group,
63
+ field,
64
+ path: getFieldPath(section.id, group.id, field.id),
65
+ sensitive: isFieldSensitive(field)
66
+ })
67
+ }
68
+ }
69
+ }
70
+ return out
71
+ }
72
+
73
+ export function coerceDefault (field) {
74
+ switch (field?.type) {
75
+ case 'boolean':
76
+ return !!field.default
77
+ case 'number':
78
+ return typeof field.default === 'number' ? field.default : Number(field.default) || 0
79
+ default:
80
+ return field?.default ?? ''
81
+ }
82
+ }
@@ -0,0 +1,57 @@
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
+
8
+ /** Default OpenWhisk action keys (must match keys in deploy-time config.json). */
9
+ export const DEFAULT_ACTION_KEYS = {
10
+ commerceRestGet: 'ConfigurationManagement/commerce-rest-get',
11
+ systemConfigList: 'ConfigurationManagement/system-config-list',
12
+ systemConfigSave: 'ConfigurationManagement/system-config-save',
13
+ systemConfigSchema: 'ConfigurationManagement/system-config-schema',
14
+ exportConfig: 'ConfigurationManagement/export-config',
15
+ importConfig: 'ConfigurationManagement/import-config',
16
+ syncStoreMappings: 'ConfigurationManagement/sync-store-mappings-from-commerce'
17
+ }
18
+
19
+ let extensionId = 'ConfigurationManagement'
20
+ let actionUrls = {}
21
+ let actionKeys = { ...DEFAULT_ACTION_KEYS }
22
+
23
+ export function getExtensionId () {
24
+ return extensionId
25
+ }
26
+
27
+ export function getActionKey (name) {
28
+ return actionKeys[name] || name
29
+ }
30
+
31
+ export function getActionUrl (actionKey) {
32
+ return actionUrls[actionKey]
33
+ }
34
+
35
+ /**
36
+ * Configure the web UI before rendering.
37
+ *
38
+ * @param {object} [options]
39
+ * @param {string} [options.extensionId] - UIX guest extension id
40
+ * @param {Record<string, string>} [options.actionUrls] - map of action key → deployed URL (from config.json)
41
+ * @param {Partial<typeof DEFAULT_ACTION_KEYS>} [options.actionKeys] - override default action key names
42
+ */
43
+ export function configureWeb ({
44
+ extensionId: nextExtensionId,
45
+ actionUrls: nextActionUrls,
46
+ actionKeys: nextActionKeys
47
+ } = {}) {
48
+ if (nextExtensionId != null) {
49
+ extensionId = String(nextExtensionId)
50
+ }
51
+ if (nextActionUrls) {
52
+ actionUrls = { ...nextActionUrls }
53
+ }
54
+ if (nextActionKeys) {
55
+ actionKeys = { ...actionKeys, ...nextActionKeys }
56
+ }
57
+ }
@@ -0,0 +1,326 @@
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
+
8
+ /* =========================================================================
9
+ sync-management — single source of truth for the UI theme.
10
+
11
+ To re-skin the app, change values under :root. Components consume these
12
+ tokens either directly via CSS classes (.sm-*) or via the JS facade in
13
+ `web-src/src/theme.js` which re-exports them as `var(--sm-*)` strings.
14
+
15
+ Token naming:
16
+ --sm-color-* colors
17
+ --sm-radius-* border radii
18
+ --sm-space-* spacing scale
19
+ --sm-shadow-* elevation
20
+ --sm-font-* typography
21
+ --sm-control-* interactive control sizing
22
+ ========================================================================= */
23
+
24
+ :root {
25
+ /* ---- Colors --------------------------------------------------------- */
26
+ --sm-color-bg: #f7f8fa;
27
+ --sm-color-surface: #ffffff;
28
+ --sm-color-surface-muted: #f3f4f6;
29
+ --sm-color-surface-subtle: #fafbfc;
30
+ --sm-color-border: #e5e7eb;
31
+ --sm-color-border-strong: #d1d5db;
32
+ --sm-color-text: #111827;
33
+ --sm-color-text-muted: #6b7280;
34
+ --sm-color-text-inverse: #ffffff;
35
+
36
+ --sm-color-accent: #1473e6;
37
+ --sm-color-accent-hover: #0f5fc4;
38
+ --sm-color-accent-soft: #e8f1fc;
39
+
40
+ --sm-color-success: #22863a;
41
+ --sm-color-success-hover: #1a6e2f;
42
+ --sm-color-success-soft: #ecfdf5;
43
+ --sm-color-warning: #b58105;
44
+ --sm-color-warning-hover: #946c04;
45
+ --sm-color-warning-soft: #fff7ed;
46
+ --sm-color-warning-border: #fde68a;
47
+ --sm-color-warning-text: #92400e;
48
+ --sm-color-warning-tint: #fef3c7;
49
+ --sm-color-danger: #c0392b;
50
+ --sm-color-danger-hover: #a32d20;
51
+ --sm-color-danger-soft: #fef2f2;
52
+ --sm-color-danger-tint: #fee2e2;
53
+ --sm-color-accent-tint: #dbeafe;
54
+
55
+ --sm-color-neutral-soft: #eef2f7;
56
+ --sm-color-neutral-text: #374151;
57
+
58
+ /* Surfaces used by the modal scaffolding (header text, body text, panel). */
59
+ --sm-color-text-strong: #1f2937;
60
+ --sm-color-text-soft: #475569;
61
+ --sm-color-surface-panel: #f9fafb;
62
+
63
+ /* Modal / overlay scrim */
64
+ --sm-color-overlay: rgba(15, 23, 42, 0.45);
65
+
66
+ /* ---- Radii ---------------------------------------------------------- */
67
+ --sm-radius-sm: 4px;
68
+ --sm-radius-md: 8px;
69
+ --sm-radius-lg: 10px;
70
+ --sm-radius-xl: 12px;
71
+ --sm-radius-2xl: 14px;
72
+ --sm-radius-pill: 999px;
73
+
74
+ /* ---- Spacing -------------------------------------------------------- */
75
+ --sm-space-1: 4px;
76
+ --sm-space-2: 8px;
77
+ --sm-space-3: 12px;
78
+ --sm-space-4: 16px;
79
+ --sm-space-5: 20px;
80
+ --sm-space-6: 24px;
81
+
82
+ /* ---- Shadows -------------------------------------------------------- */
83
+ --sm-shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.03);
84
+ --sm-shadow-sm: 0 1px 4px rgba(15, 23, 42, 0.04);
85
+ --sm-shadow-md: 0 1px 3px rgba(15, 23, 42, 0.12), 0 1px 1px rgba(15, 23, 42, 0.06);
86
+ --sm-shadow-pill: 0 1px 3px rgba(15, 23, 42, 0.10), 0 1px 1px rgba(15, 23, 42, 0.06);
87
+ --sm-shadow-floating: 0 4px 14px rgba(15, 23, 42, 0.08);
88
+ --sm-shadow-dropdown: 0 12px 28px rgba(15, 23, 42, 0.16), 0 2px 4px rgba(15, 23, 42, 0.06);
89
+ --sm-shadow-modal: 0 24px 60px rgba(15, 23, 42, 0.25), 0 2px 8px rgba(15, 23, 42, 0.08);
90
+ --sm-shadow-inset: inset 0 1px 2px rgba(15, 23, 42, 0.04);
91
+
92
+ /* ---- Typography ----------------------------------------------------- */
93
+ --sm-font-family: adobe-clean, 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
94
+ --sm-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
95
+ --sm-font-size-xs: 11px;
96
+ --sm-font-size-sm: 12px;
97
+ --sm-font-size-md: 13px;
98
+ --sm-font-size-lg: 14px;
99
+ --sm-font-weight-regular: 400;
100
+ --sm-font-weight-medium: 500;
101
+ --sm-font-weight-semibold:600;
102
+ --sm-font-weight-bold: 700;
103
+
104
+ /* ---- Control sizing ------------------------------------------------- */
105
+ --sm-control-height-sm: 28px;
106
+ --sm-control-height-md: 32px;
107
+ --sm-control-height-lg: 40px;
108
+ --sm-control-padding-x: 16px;
109
+
110
+ /* ---- Z-index -------------------------------------------------------- */
111
+ --sm-z-nav: 30;
112
+ --sm-z-sticky: 20;
113
+ --sm-z-modal: 100;
114
+
115
+ /* =====================================================================
116
+ Spectrum token overrides — re-skin React-Spectrum widgets without
117
+ forking the theme. Keep these in sync with the --sm-* tokens above
118
+ so default Buttons / TextField / Picker pick up our accent automatically.
119
+ ===================================================================== */
120
+ --spectrum-accent-color-900: var(--sm-color-accent);
121
+ --spectrum-accent-color-1000: var(--sm-color-accent-hover);
122
+ /* Text colours are left to Spectrum so secondary/quiet buttons keep
123
+ their proper contrast on both light and dark surfaces. */
124
+
125
+ color-scheme: light;
126
+ }
127
+
128
+ /* =========================================================================
129
+ Base
130
+ ========================================================================= */
131
+ html,
132
+ body,
133
+ #root {
134
+ margin: 0;
135
+ min-height: 100%;
136
+ background: var(--sm-color-bg);
137
+ color: var(--sm-color-text);
138
+ font-family: var(--sm-font-family);
139
+ }
140
+
141
+ /* React-Spectrum's <Provider> wraps its tree in a div. We mark it with
142
+ `UNSAFE_className="sm-provider"` and set its background here so the page
143
+ bg fills the whole iframe — but we only paint the wrapper, NOT any
144
+ descendants. Painting descendants stomps on inputs / buttons / pickers. */
145
+ .sm-provider {
146
+ background: var(--sm-color-bg);
147
+ min-height: 100vh;
148
+ }
149
+
150
+ /* =========================================================================
151
+ Cards
152
+ ========================================================================= */
153
+ .sm-card {
154
+ background: var(--sm-color-surface);
155
+ border: 1px solid var(--sm-color-border);
156
+ border-radius: var(--sm-radius-lg);
157
+ box-shadow: var(--sm-shadow-xs);
158
+ padding: var(--sm-space-5);
159
+ }
160
+ .sm-card--flush { padding: 0; }
161
+
162
+ /* =========================================================================
163
+ Pills
164
+ ========================================================================= */
165
+ .sm-pill {
166
+ display: inline-flex;
167
+ align-items: center;
168
+ gap: var(--sm-space-1);
169
+ padding: 2px var(--sm-space-2);
170
+ border-radius: var(--sm-radius-pill);
171
+ background: var(--sm-color-neutral-soft);
172
+ color: var(--sm-color-neutral-text);
173
+ font-size: var(--sm-font-size-xs);
174
+ font-weight: var(--sm-font-weight-semibold);
175
+ line-height: 16px;
176
+ letter-spacing: 0.2px;
177
+ white-space: nowrap;
178
+ }
179
+ .sm-pill--accent { background: var(--sm-color-accent-soft); color: var(--sm-color-accent); }
180
+ .sm-pill--success { background: var(--sm-color-success-soft); color: var(--sm-color-success); }
181
+ .sm-pill--warning { background: var(--sm-color-warning-soft); color: var(--sm-color-warning); }
182
+ .sm-pill--danger { background: var(--sm-color-danger-soft); color: var(--sm-color-danger); }
183
+
184
+ /* =========================================================================
185
+ Tab bar (used by AppSectionNav)
186
+ ========================================================================= */
187
+ .sm-tab-bar {
188
+ position: sticky;
189
+ top: 0;
190
+ z-index: var(--sm-z-nav);
191
+ background: var(--sm-color-surface);
192
+ border-bottom: 1px solid var(--sm-color-border);
193
+ padding: 10px var(--sm-space-4);
194
+ box-shadow: var(--sm-shadow-sm);
195
+ box-sizing: border-box;
196
+ max-width: 100%;
197
+ }
198
+ .sm-tab-bar__track {
199
+ display: inline-flex;
200
+ padding: var(--sm-space-1);
201
+ background: var(--sm-color-surface-muted);
202
+ border: 1px solid var(--sm-color-border);
203
+ border-radius: var(--sm-radius-pill);
204
+ box-shadow: var(--sm-shadow-inset);
205
+ gap: 2px;
206
+ font-family: var(--sm-font-family);
207
+ }
208
+ .sm-tab {
209
+ display: inline-flex;
210
+ align-items: center;
211
+ gap: var(--sm-space-2);
212
+ padding: var(--sm-space-2) var(--sm-control-padding-x);
213
+ border: 0;
214
+ border-radius: var(--sm-radius-pill);
215
+ background: transparent;
216
+ color: var(--sm-color-neutral-text);
217
+ font-size: var(--sm-font-size-md);
218
+ font-weight: var(--sm-font-weight-semibold);
219
+ letter-spacing: 0.1px;
220
+ cursor: pointer;
221
+ transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
222
+ }
223
+ .sm-tab:hover {
224
+ background: var(--sm-color-surface);
225
+ color: var(--sm-color-text);
226
+ }
227
+ .sm-tab.is-active {
228
+ background: var(--sm-color-surface);
229
+ color: var(--sm-color-accent);
230
+ font-weight: var(--sm-font-weight-bold);
231
+ box-shadow: var(--sm-shadow-pill);
232
+ cursor: default;
233
+ }
234
+ .sm-tab__icon { display: inline-flex; opacity: 0.75; }
235
+ .sm-tab.is-active .sm-tab__icon { opacity: 1; }
236
+
237
+ /* =========================================================================
238
+ Spectrum textareas inside the system-config field renderer
239
+ ========================================================================= */
240
+ .sm-textarea textarea {
241
+ height: 160px !important;
242
+ max-height: 160px !important;
243
+ min-height: 160px !important;
244
+ overflow-y: auto !important;
245
+ resize: none !important;
246
+ font-family: var(--sm-font-mono);
247
+ font-size: var(--sm-font-size-sm);
248
+ }
249
+
250
+ /* =========================================================================
251
+ Note banner (used by ComingSoon, info callouts, etc.)
252
+ ========================================================================= */
253
+ .sm-note {
254
+ margin-top: var(--sm-space-3);
255
+ padding: var(--sm-space-3);
256
+ border-radius: var(--sm-radius-md);
257
+ background: var(--sm-color-surface-muted);
258
+ border: 1px solid var(--sm-color-border);
259
+ color: var(--sm-color-text);
260
+ font-size: var(--sm-font-size-md);
261
+ white-space: pre-line;
262
+ font-family: var(--sm-font-mono);
263
+ }
264
+ .sm-note--warning {
265
+ background: var(--sm-color-warning-soft);
266
+ border-color: var(--sm-color-warning-border);
267
+ color: var(--sm-color-warning-text);
268
+ font-family: var(--sm-font-family);
269
+ }
270
+
271
+ /* =========================================================================
272
+ Animations (used by progress / status indicators)
273
+ ========================================================================= */
274
+ @keyframes sm-pulse {
275
+ 0%, 100% { opacity: 1; }
276
+ 50% { opacity: 0.3; }
277
+ }
278
+ @keyframes sm-indeterminate {
279
+ 0% { left: -40%; width: 40%; }
280
+ 50% { left: 30%; width: 40%; }
281
+ 100% { left: 100%; width: 40%; }
282
+ }
283
+ @keyframes sm-fade-in {
284
+ from { opacity: 0; }
285
+ to { opacity: 1; }
286
+ }
287
+ @keyframes sm-pop-in {
288
+ from { opacity: 0; transform: translateY(8px) scale(0.98); }
289
+ to { opacity: 1; transform: translateY(0) scale(1); }
290
+ }
291
+
292
+ /* =========================================================================
293
+ Legacy SideNav classes (kept for backwards compat with any remaining
294
+ menu markup; safe to delete once nothing references them).
295
+ ========================================================================= */
296
+ .SideNav {
297
+ list-style-type: none;
298
+ margin: 0;
299
+ padding: 0;
300
+ outline: none;
301
+ height: 100%;
302
+ }
303
+ .SideNav-item {
304
+ list-style-type: none;
305
+ margin: var(--spectrum-global-dimension-size-50) 0;
306
+ }
307
+ .SideNav-itemLink {
308
+ position: relative;
309
+ display: inline-flex;
310
+ align-items: center;
311
+ box-sizing: border-box;
312
+ width: 100%;
313
+ padding: var(--sm-space-2) var(--sm-space-3);
314
+ border-radius: var(--sm-radius-sm);
315
+ font-size: var(--sm-font-size-lg);
316
+ font-weight: var(--sm-font-weight-regular);
317
+ text-decoration: none;
318
+ word-break: break-word;
319
+ cursor: pointer;
320
+ background: transparent;
321
+ color: var(--sm-color-text);
322
+ }
323
+ .SideNav-itemLink.is-selected {
324
+ color: var(--sm-color-accent);
325
+ background: var(--sm-color-accent-soft);
326
+ }
@@ -0,0 +1,104 @@
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
+
8
+ /**
9
+ * JS facade for the design tokens defined in `index.css`.
10
+ *
11
+ * Every value here is a CSS `var()` reference. Use these in inline styles
12
+ * (`style={{ color: THEME.color.accent }}`) so the rendered value resolves
13
+ * through CSS at runtime — changing the variable in `web/src/styles/index.css` re-skins
14
+ * everything that uses this object without touching any JS.
15
+ *
16
+ * Naming mirrors the CSS variable structure: --sm-color-accent → color.accent.
17
+ */
18
+ export const THEME = {
19
+ color: {
20
+ bg: 'var(--sm-color-bg)',
21
+ surface: 'var(--sm-color-surface)',
22
+ surfaceMuted: 'var(--sm-color-surface-muted)',
23
+ surfaceSubtle: 'var(--sm-color-surface-subtle)',
24
+ border: 'var(--sm-color-border)',
25
+ borderStrong: 'var(--sm-color-border-strong)',
26
+ text: 'var(--sm-color-text)',
27
+ textMuted: 'var(--sm-color-text-muted)',
28
+ textStrong: 'var(--sm-color-text-strong)',
29
+ textSoft: 'var(--sm-color-text-soft)',
30
+ textInverse: 'var(--sm-color-text-inverse)',
31
+ surfacePanel: 'var(--sm-color-surface-panel)',
32
+ accent: 'var(--sm-color-accent)',
33
+ accentHover: 'var(--sm-color-accent-hover)',
34
+ accentSoft: 'var(--sm-color-accent-soft)',
35
+ accentTint: 'var(--sm-color-accent-tint)',
36
+ success: 'var(--sm-color-success)',
37
+ successHover: 'var(--sm-color-success-hover)',
38
+ successSoft: 'var(--sm-color-success-soft)',
39
+ warning: 'var(--sm-color-warning)',
40
+ warningHover: 'var(--sm-color-warning-hover)',
41
+ warningSoft: 'var(--sm-color-warning-soft)',
42
+ warningBorder: 'var(--sm-color-warning-border)',
43
+ warningText: 'var(--sm-color-warning-text)',
44
+ warningTint: 'var(--sm-color-warning-tint)',
45
+ danger: 'var(--sm-color-danger)',
46
+ dangerHover: 'var(--sm-color-danger-hover)',
47
+ dangerSoft: 'var(--sm-color-danger-soft)',
48
+ dangerTint: 'var(--sm-color-danger-tint)',
49
+ neutralSoft: 'var(--sm-color-neutral-soft)',
50
+ neutralText: 'var(--sm-color-neutral-text)',
51
+ overlay: 'var(--sm-color-overlay)'
52
+ },
53
+ radius: {
54
+ sm: 'var(--sm-radius-sm)',
55
+ md: 'var(--sm-radius-md)',
56
+ lg: 'var(--sm-radius-lg)',
57
+ xl: 'var(--sm-radius-xl)',
58
+ xxl: 'var(--sm-radius-2xl)',
59
+ pill: 'var(--sm-radius-pill)'
60
+ },
61
+ space: {
62
+ 1: 'var(--sm-space-1)',
63
+ 2: 'var(--sm-space-2)',
64
+ 3: 'var(--sm-space-3)',
65
+ 4: 'var(--sm-space-4)',
66
+ 5: 'var(--sm-space-5)',
67
+ 6: 'var(--sm-space-6)'
68
+ },
69
+ shadow: {
70
+ xs: 'var(--sm-shadow-xs)',
71
+ sm: 'var(--sm-shadow-sm)',
72
+ md: 'var(--sm-shadow-md)',
73
+ pill: 'var(--sm-shadow-pill)',
74
+ floating: 'var(--sm-shadow-floating)',
75
+ dropdown: 'var(--sm-shadow-dropdown)',
76
+ modal: 'var(--sm-shadow-modal)',
77
+ inset: 'var(--sm-shadow-inset)'
78
+ },
79
+ font: {
80
+ family: 'var(--sm-font-family)',
81
+ mono: 'var(--sm-font-mono)',
82
+ sizeXs: 'var(--sm-font-size-xs)',
83
+ sizeSm: 'var(--sm-font-size-sm)',
84
+ sizeMd: 'var(--sm-font-size-md)',
85
+ sizeLg: 'var(--sm-font-size-lg)',
86
+ weightRegular: 'var(--sm-font-weight-regular)',
87
+ weightMedium: 'var(--sm-font-weight-medium)',
88
+ weightSemi: 'var(--sm-font-weight-semibold)',
89
+ weightBold: 'var(--sm-font-weight-bold)'
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Flat alias of `THEME.color` — every colour token reachable as `PALETTE.x`.
95
+ *
96
+ * Components should use this for inline styles so there is exactly one
97
+ * facade for the design system: change a CSS variable in `web/src/styles/index.css` →
98
+ * every reference here resolves to the new value at runtime.
99
+ */
100
+ export const PALETTE = { ...THEME.color }
101
+ export const RADIUS = { ...THEME.radius }
102
+ export const SHADOW = { ...THEME.shadow }
103
+ export const SPACE = { ...THEME.space }
104
+ export const FONT = { ...THEME.font }
@@ -0,0 +1,73 @@
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
+
8
+ /** @param {string} locale e.g. en_US → en */
9
+ function localeToLanguageCode (locale) {
10
+ if (locale == null || locale === '') return null
11
+ const s = String(locale).trim()
12
+ const head = s.split(/[-_]/u)[0]
13
+ if (head && /^[a-zA-Z]{2,8}$/.test(head)) return head.toLowerCase()
14
+ return null
15
+ }
16
+
17
+ /** e.g. en_ch → en, de_ch → de */
18
+ function inferLanguageFromStoreCode (code) {
19
+ if (typeof code !== 'string') return null
20
+ const m = /^([a-z]{2})[-_]/i.exec(code)
21
+ return m ? m[1].toLowerCase() : null
22
+ }
23
+
24
+ /**
25
+ * Build `general/settings/store_mappings` shape from Commerce REST payloads
26
+ * (same shape as server middleware expects: keyed by store view id string).
27
+ *
28
+ * @param {object[]|null|undefined} websitesRaw from `store/websites`
29
+ * @param {object[]|null|undefined} storeViewsRaw from `store/storeViews`
30
+ * @param {object[]|null|undefined} storeConfigsRaw from `store/storeConfigs` (optional)
31
+ * @returns {Record<string, { code: string, language_code: string, website_code: string, website_id: string }>}
32
+ */
33
+ export function buildStoreMappingsFromCommercePayload (websitesRaw, storeViewsRaw, storeConfigsRaw) {
34
+ const websiteIdToCode = new Map()
35
+ if (Array.isArray(websitesRaw)) {
36
+ for (const w of websitesRaw) {
37
+ if (w && w.id != null && w.code != null) {
38
+ websiteIdToCode.set(String(w.id), String(w.code))
39
+ }
40
+ }
41
+ }
42
+
43
+ const storeCodeToLocale = new Map()
44
+ if (Array.isArray(storeConfigsRaw)) {
45
+ for (const cfg of storeConfigsRaw) {
46
+ if (cfg && cfg.code != null && cfg.locale != null) {
47
+ storeCodeToLocale.set(String(cfg.code), String(cfg.locale))
48
+ }
49
+ }
50
+ }
51
+
52
+ const mappings = {}
53
+ if (!Array.isArray(storeViewsRaw)) return mappings
54
+
55
+ for (const s of storeViewsRaw) {
56
+ if (!s || s.id == null || s.code == null) continue
57
+ const id = String(s.id)
58
+ const code = String(s.code)
59
+ const websiteId = s.website_id != null ? String(s.website_id) : ''
60
+ const websiteCode = websiteIdToCode.get(websiteId) || ''
61
+ const languageCode =
62
+ localeToLanguageCode(storeCodeToLocale.get(code)) ||
63
+ inferLanguageFromStoreCode(code) ||
64
+ 'en'
65
+ mappings[id] = {
66
+ code,
67
+ language_code: languageCode,
68
+ website_code: websiteCode,
69
+ website_id: websiteId
70
+ }
71
+ }
72
+ return mappings
73
+ }