Skip to content

Commit d3202da

Browse files
authored
Merge pull request #13 from openimis/feature/OSB-44
OSB-44: Integrate 'solution builder' repository with 'solution' repository + OSB-47 changes
2 parents 3b02660 + cad5bbc commit d3202da

11 files changed

Lines changed: 417 additions & 53 deletions

File tree

README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,113 @@ This script processes a `solution.json` file and combines the `menus` key from m
44

55
## How It Works
66

7+
### ⚠️ Requirements
8+
9+
- **Node.js v18 or higher is required**
10+
- This version is necessary for modern features like `Blob` and `arrayBuffer()` support used during ZIP creation.
11+
- You can verify your version with:
12+
```bash
13+
node -v
14+
```
15+
---
16+
17+
### 🚀 What It Does
18+
19+
The `workbench.js` script:
20+
1. **Reads** a solution configuration JSON (e.g., `HF.json`).
21+
2. **Generates** supporting files like:
22+
- `fe-openimis.json`
23+
- `generated-menu.json`
24+
- `generated-roles.json`
25+
- `compose.yml`
26+
3. **Creates** a ZIP archive (`output.zip`) containing the above outputs.
27+
4. **(Optional)** Pushes the generated solution to a new branch in the `openimis/solutions` GitHub repo.
28+
29+
### Usage
30+
31+
#### 📦 Basic: Generate ZIP Only
32+
33+
```bash
34+
node workbench.js
35+
```
36+
37+
This will create `output.zip` in the project root with the generated solution files.
38+
39+
---
40+
41+
#### 🚀 Publish to GitHub
42+
43+
```bash
44+
node workbench.js --publish
45+
```
46+
47+
This generates the ZIP and:
48+
49+
* Clones the [openimis/solutions](https://github.com/openimis/solutions) repository.
50+
* Creates a new branch based on `develop`.
51+
* Pushes the solution into a subfolder matching the default name (`default-solution`).
52+
53+
---
54+
55+
#### 📝 Publish with Custom Folder Name
56+
57+
```bash
58+
node workbench.js --publish --folder=coreMIS-test
59+
```
60+
61+
This behaves like the previous command but publishes the solution inside a subfolder called `coreMIS-test`.
62+
63+
---
64+
65+
### Example Output Structure
66+
67+
After running, you will see:
68+
69+
```
70+
output.zip
71+
solution/
72+
├— fe-openimis.json
73+
├— generated-menu.json
74+
├— generated-roles.json
75+
└— compose.yml
76+
```
77+
78+
---
79+
80+
### 🚼 Cleanup
81+
82+
Temporary folders (`temp_solutions_repo` and `unzipped_output`) are automatically cleaned between runs.
83+
84+
---
85+
86+
### 📁 Default Config Path
87+
88+
Ensure your `<solution>.json` is located at:
89+
90+
```
91+
./solution/solutions/<solution>.json
92+
```
93+
94+
You can customize this path in the script if needed.
95+
96+
---
97+
98+
### 🔥 Dependencies
99+
100+
Install dependencies using:
101+
102+
```bash
103+
npm install jszip simple-git yaml unzipper
104+
```
105+
106+
---
107+
108+
### 📬 Contributing
109+
110+
Merge to `develop` in `openimis/solutions` is disabled. This script creates a **new branch** like `solution/coreMIS-test` and **pushes only that branch**.
111+
112+
---
113+
7114
### file structure
8115

9116
```yaml

solution/solutions/HF.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
"./healthfinancing-bundle.json",
1010
"./core-bundle.json"
1111
],
12-
"services": ["db"],
13-
"fePackageSource": {}
12+
"services":["db"],
13+
"fePackageSource":{
14+
15+
},
16+
"moduleConfiguration": {
17+
"logo": "./sources/logo/openIMIS.png"
18+
}
1419
}

solution/solutions/core-bundle.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@
1212
"../services/base.json",
1313
"../services/cache.json",
1414
"../services/opensearch.json"
15-
]
15+
],
16+
"moduleConfiguration": {
17+
"logo": "./sources/logo/openIMIS.png"
18+
}
1619
}

solution/solutions/coreMIS.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@
88
"./good-distribution-bundle.json",
99
"./core-bundle.json"
1010
],
11-
"services":["db"]
11+
"services":["db"],
12+
"moduleConfiguration": {
13+
"logo": "./sources/logo/coremis.png"
14+
}
1215
}

solution/sources/logo/coremis.png

59.2 KB
Loading

solution/sources/logo/openIMIS.png

28.6 KB
Loading

solution/sources/theme/coremis.json

Whitespace-only changes.

solution/sources/theme/default.json

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"theme":
3+
{
4+
"name": "defaultBlue",
5+
"primaryColor": "#004E96",
6+
"errorColor": "#801a00",
7+
"whiteColor": "#fff",
8+
"backgroundColor": "#e4f2ff",
9+
"headerColor": "#BCD4E6",
10+
"greyColor": "grey",
11+
"selectedTableRowColor": "rgba(0, 0, 0, 0.08)",
12+
"hoveredTableRowColor": "rgba(0, 0, 0, 0.12)",
13+
"toggledButtonColor": "#999999",
14+
"lockedBackgroundPattern": "repeating-linear-gradient(45deg, #D3D3D3 1px, #D3D3D3 1px, #fff 10px, #fff 10px)"
15+
}
16+
}

solutionBuilder.js

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ if(typeof window === 'undefined'){
44
path = require('path'); // CommonJS
55
JSZip = require('jszip'); // CommonJS
66
yaml = require('js-yaml'); //
7+
const { Blob } = require('buffer');
78
}
89

10+
const mime = require('mime-types');
11+
const { v4: uuidv4 } = require('uuid');
12+
913

1014

1115
// solution-builder.js
@@ -140,20 +144,24 @@ function getFePackageConf(name, definition, branch){
140144
}
141145

142146

143-
function makeCoreModuleConfiguration(menus){
147+
function makeCoreModuleConfiguration(menusDict) {
148+
const config = {
149+
menus: Object.values(menusDict).sort((a, b) => (a.position || 0) - (b.position || 0))
150+
};
151+
144152
return [
145153
{
146-
"model": "core.moduleconfiguration",
147-
"fields": {
148-
"id": "ebdbdbe5-c9be-4e66-8c49-edd1e5284c7c",
149-
"module": "fe-core",
150-
"version": "1",
151-
"config": "{\n \"menus\": "+JSON.stringify(menus).replace(/"/g, '\"')+"}",
152-
"is_exposed": true,
153-
"layer": "fe"
154-
}
154+
model: 'core.moduleconfiguration',
155+
fields: {
156+
id: uuidv4(),
157+
module: 'fe-core',
158+
version: '1',
159+
config: JSON.stringify(config, null, 2),
160+
is_exposed: true,
161+
layer: 'fe'
162+
}
155163
}
156-
]
164+
];
157165
}
158166

159167
async function processSolutions(
@@ -163,13 +171,27 @@ async function processSolutions(
163171
branch = 'develop'
164172
)
165173
{
166-
solutionFilePath = getAbsolutePath(typeof solutionFile == 'string'?solutionFile:'', '', false)
167-
const merged = await mergeSolutions(
168-
solutionFile,
169-
directoryPath,
170-
permissionMap
171-
)
172-
let result = {}
174+
const solutionFilePath = getAbsolutePath(typeof solutionFile === 'string' ? solutionFile : '', '', false);
175+
176+
let solutionJson = {};
177+
try {
178+
solutionJson = JSON.parse(fs.readFileSync(solutionFile, 'utf8'));
179+
} catch (e) {
180+
console.warn('⚠️ Failed to parse root solution JSON file:', e.message);
181+
}
182+
183+
let logoPath = null;
184+
if (solutionJson?.moduleConfiguration?.logo) {
185+
logoPath = path.resolve(path.dirname(solutionFilePath), solutionJson.moduleConfiguration.logo);
186+
}
187+
188+
let themePath = null;
189+
if (solutionJson?.moduleConfiguration?.theme) {
190+
themePath = path.resolve(path.dirname(solutionFilePath), solutionJson.moduleConfiguration.theme);
191+
}
192+
193+
let merged = await mergeSolutions(solutionFile, directoryPath, permissionMap);
194+
let result = {};
173195

174196
for (let key in merged.moduleRefDict || {}) {
175197
const depPath = getAbsolutePath(merged.moduleRefDict[key], solutionFilePath);
@@ -228,8 +250,12 @@ async function processSolutions(
228250
if(PIPModules.size>0){
229251
output['be-openimis.json'] ={"modules": [...PIPModules]};
230252
}
231-
if(Object.keys(merged.menusDict).length>0){
232-
output['fixtures/module-configuration-core.json'] =makeCoreModuleConfiguration(merged.menusDict);
253+
if (Object.keys(merged.menusDict).length > 0) {
254+
const coreModuleConfig = makeCoreModuleConfiguration(merged.menusDict);
255+
256+
// Inject logo/theme using resolved paths
257+
await injectLogoTheme(coreModuleConfig[0], logoPath, themePath);
258+
output['fixtures/module-configuration-core.json'] = coreModuleConfig;
233259
}
234260
if(Object.keys(merged.rolesDict).length>0){
235261
output['fixtures/roles.json'] = merged.rolesDict;
@@ -422,6 +448,41 @@ function mergeMenusData(menus, menuDict) {
422448
}
423449

424450

451+
async function injectLogoTheme(menuJson, logoPath, themePath) {
452+
if (!menuJson.fields || !menuJson.fields.config) {
453+
console.warn("⚠️ menuJson is missing 'fields.config'");
454+
return;
455+
}
456+
457+
// Parse the existing config JSON string
458+
let config;
459+
try {
460+
config = JSON.parse(menuJson.fields.config);
461+
} catch (e) {
462+
console.error("❌ Failed to parse menuJson.fields.config:", e);
463+
return;
464+
}
465+
466+
// Inject logo
467+
if (logoPath && fs.existsSync(logoPath)) {
468+
const img = fs.readFileSync(logoPath);
469+
const mimeType = mime.lookup(logoPath) || 'image/png';
470+
const base64 = img.toString('base64');
471+
config.logo = {
472+
value: `data:${mimeType};base64,${base64}`
473+
};
474+
}
475+
476+
// Inject theme
477+
if (themePath && fs.existsSync(themePath)) {
478+
const themeData = JSON.parse(fs.readFileSync(themePath, 'utf8'));
479+
config.theme = themeData.theme;
480+
}
481+
// Write back to config as string
482+
menuJson.fields.config = JSON.stringify(config, null, 2);
483+
}
484+
485+
425486
// Function to get headers with GitHub API key
426487
function getHeaders() {
427488
const headers = {

0 commit comments

Comments
 (0)