@intecoag/inteco-cli 1.3.0 → 1.4.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.
- package/.github/dependabot.yml +12 -0
- package/.github/workflows/publish.yml +2 -2
- package/.vscode/settings.json +6 -0
- package/package.json +5 -4
- package/src/index.js +4 -0
- package/src/modules/configMutation.js +360 -0
- package/src/modules/syncConfig.js +81 -9
- package/src/ressources/cmds.json +3 -0
- package/src/utils/fs/FS.js +60 -4
package/.github/dependabot.yml
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
version: 2
|
|
2
2
|
updates:
|
|
3
|
+
- package-ecosystem: "github-actions"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
day: "monday"
|
|
8
|
+
time: "03:00"
|
|
9
|
+
timezone: "UTC"
|
|
3
10
|
- package-ecosystem: "npm"
|
|
4
11
|
directory: "/"
|
|
12
|
+
versioning-strategy: increase
|
|
5
13
|
schedule:
|
|
6
14
|
interval: "weekly"
|
|
7
15
|
day: "monday"
|
|
@@ -14,3 +22,7 @@ updates:
|
|
|
14
22
|
patterns:
|
|
15
23
|
- "*"
|
|
16
24
|
open-pull-requests-limit: 10
|
|
25
|
+
commit-message:
|
|
26
|
+
prefix: fix
|
|
27
|
+
prefix-development: chore
|
|
28
|
+
include: scope
|
|
@@ -14,11 +14,11 @@ jobs:
|
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
16
|
- name: Checkout
|
|
17
|
-
uses: actions/checkout@
|
|
17
|
+
uses: actions/checkout@v6
|
|
18
18
|
with:
|
|
19
19
|
fetch-depth: 0
|
|
20
20
|
- name: Setup Node.js
|
|
21
|
-
uses: actions/setup-node@
|
|
21
|
+
uses: actions/setup-node@v6
|
|
22
22
|
with:
|
|
23
23
|
node-version: "lts/*"
|
|
24
24
|
- name: Install dependencies
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intecoag/inteco-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "CLI-Tools for Inteco",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"cli-meow-help": "^4.0.0",
|
|
20
20
|
"cli-table3": "^0.6.5",
|
|
21
21
|
"csv-parser": "^3.0.0",
|
|
22
|
+
"fast-glob": "^3.3.3",
|
|
22
23
|
"fuzzysort": "^3.1.0",
|
|
23
24
|
"graphql": "^16.6.0",
|
|
24
25
|
"meow": "^14.0.0",
|
|
@@ -36,10 +37,10 @@
|
|
|
36
37
|
"inteco": "src/index.js"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@commitlint/cli": "^20.
|
|
40
|
-
"@commitlint/config-conventional": "^20.
|
|
40
|
+
"@commitlint/cli": "^20.4.0",
|
|
41
|
+
"@commitlint/config-conventional": "^20.4.0",
|
|
41
42
|
"husky": "^9.1.7",
|
|
42
|
-
"semantic-release": "^25.0.
|
|
43
|
+
"semantic-release": "^25.0.3"
|
|
43
44
|
},
|
|
44
45
|
"release": {
|
|
45
46
|
"branches": [
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import commands from "./ressources/cmds.json" with {type: 'json'};
|
|
|
18
18
|
import packageJson from "../package.json" with {type: 'json'}
|
|
19
19
|
import extdSearch from './modules/extdSearch.js';
|
|
20
20
|
import syncConfig from './modules/syncConfig.js';
|
|
21
|
+
import configMutation from './modules/configMutation.js';
|
|
21
22
|
import bundleProduct from './modules/bundleProduct.js';
|
|
22
23
|
|
|
23
24
|
import updateNotifier from 'update-notifier';
|
|
@@ -87,6 +88,9 @@ switch (cli.input[0]) {
|
|
|
87
88
|
case "sync_config":
|
|
88
89
|
syncConfig();
|
|
89
90
|
break;
|
|
91
|
+
case "config_mutation":
|
|
92
|
+
configMutation();
|
|
93
|
+
break;
|
|
90
94
|
case "bundle_product":
|
|
91
95
|
bundleProduct(cli);
|
|
92
96
|
break;
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import prompts from "prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { Config } from "../utils/config/config.js";
|
|
4
|
+
import FS from "fs"
|
|
5
|
+
import * as CliFS from "../utils/fs/FS.js";
|
|
6
|
+
import YAML from "yaml";
|
|
7
|
+
import { YAMLMap, YAMLSeq } from "yaml";
|
|
8
|
+
import fg from "fast-glob";
|
|
9
|
+
import path from "path";
|
|
10
|
+
|
|
11
|
+
export default async function mutateConfig() {
|
|
12
|
+
console.log()
|
|
13
|
+
|
|
14
|
+
const config = await Config.getConfig();
|
|
15
|
+
const configDirectories = [
|
|
16
|
+
{ title: '[Update all]', value: '*' },
|
|
17
|
+
...FS.readdirSync(config.configIndividualPathEclipse, { withFileTypes: true }).filter(dirent => dirent.isDirectory())
|
|
18
|
+
.map(dirent => { return { title: dirent.name, value: dirent.name } })
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
let success = true;
|
|
22
|
+
|
|
23
|
+
const responses = await prompts([
|
|
24
|
+
{
|
|
25
|
+
type: 'select',
|
|
26
|
+
name: 'mergeType',
|
|
27
|
+
message: 'Select Merge Type:',
|
|
28
|
+
choices: [
|
|
29
|
+
{ title: 'Only Create missing Keys (add missing keys from source to target)', value: 'only_create' },
|
|
30
|
+
{ title: 'Create, Update and Overwrite Keys (copy everything except keys that only exist in target)', value: 'create_update_overwrite' },
|
|
31
|
+
{ title: 'Remove missing Keys (remove all keys from target that don\'t exist in source)', value: 'remove_missing' }
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'toggle',
|
|
36
|
+
name: 'mergeClients',
|
|
37
|
+
message: 'Include Mand? (Apply changes to 1_, 2_, ...)',
|
|
38
|
+
active: false
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: 'autocomplete',
|
|
42
|
+
name: 'configDest',
|
|
43
|
+
message: 'Update Target?',
|
|
44
|
+
choices: configDirectories
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'toggle',
|
|
48
|
+
name: 'dryRun',
|
|
49
|
+
message: 'Dry run? (show what would happen without making changes)',
|
|
50
|
+
initial: false,
|
|
51
|
+
active: 'yes',
|
|
52
|
+
inactive: 'no'
|
|
53
|
+
}
|
|
54
|
+
], {
|
|
55
|
+
onCancel: () => {
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(chalk.red("Cancelled Operation!"));
|
|
58
|
+
console.log();
|
|
59
|
+
success = false;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if (success) {
|
|
65
|
+
const sourceConfigs = await CliFS.FS.filePicker(path.join(path.dirname(config.configIndividualPathEclipse), "config", "yaml"),
|
|
66
|
+
path.join(path.dirname(config.configIndividualPathEclipse), "config", "yaml"));
|
|
67
|
+
|
|
68
|
+
const configsToUpdate = responses.configDest == '*'
|
|
69
|
+
? FS.readdirSync(config.configIndividualPathEclipse, { withFileTypes: true }).filter(f => f.isDirectory()).map(f => f.name)
|
|
70
|
+
: [responses.configDest];
|
|
71
|
+
|
|
72
|
+
switch (responses.mergeType) {
|
|
73
|
+
case 'only_create':
|
|
74
|
+
await processAddMissing(sourceConfigs, config.configIndividualPathEclipse, configsToUpdate, responses.mergeClients, responses.dryRun);
|
|
75
|
+
break;
|
|
76
|
+
case 'remove_missing':
|
|
77
|
+
await processRemoveMissing(sourceConfigs, config.configIndividualPathEclipse, configsToUpdate, responses.mergeClients, responses.dryRun);
|
|
78
|
+
break;
|
|
79
|
+
case 'create_update_overwrite':
|
|
80
|
+
await processMergeOverwrite(sourceConfigs, config.configIndividualPathEclipse, configsToUpdate, responses.mergeClients, responses.dryRun);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (responses.dryRun) {
|
|
85
|
+
console.log()
|
|
86
|
+
const confirmationResults = await prompts([
|
|
87
|
+
{
|
|
88
|
+
type: 'confirm',
|
|
89
|
+
name: 'confirmation',
|
|
90
|
+
message: 'Would you like to execute the dry-run?',
|
|
91
|
+
initial: true
|
|
92
|
+
}
|
|
93
|
+
], {
|
|
94
|
+
onCancel: () => {
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(chalk.red("Cancelled Operation!"));
|
|
97
|
+
console.log();
|
|
98
|
+
success = false;
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
if(confirmationResults.confirmation){
|
|
103
|
+
switch (responses.mergeType) {
|
|
104
|
+
case 'only_create':
|
|
105
|
+
await processAddMissing(sourceConfigs, config.configIndividualPathEclipse, configsToUpdate, responses.mergeClients, false);
|
|
106
|
+
break;
|
|
107
|
+
case 'remove_missing':
|
|
108
|
+
await processRemoveMissing(sourceConfigs, config.configIndividualPathEclipse, configsToUpdate, responses.mergeClients, false);
|
|
109
|
+
break;
|
|
110
|
+
case 'create_update_overwrite':
|
|
111
|
+
await processMergeOverwrite(sourceConfigs, config.configIndividualPathEclipse, configsToUpdate, responses.mergeClients, false);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
console.log();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function processMergeOverwrite(filesToUpdate, configIndividualPathEclipse, configsToUpdate, mergeClients, dryRun) {
|
|
121
|
+
await processInEachFile(filesToUpdate, configIndividualPathEclipse,
|
|
122
|
+
configsToUpdate, mergeClients, dryRun, mergeOverwriteNodes);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function processRemoveMissing(filesToUpdate, configIndividualPathEclipse, configsToUpdate, mergeClients, dryRun) {
|
|
126
|
+
await processInEachFile(filesToUpdate, configIndividualPathEclipse,
|
|
127
|
+
configsToUpdate, mergeClients, dryRun, removeMissingNodes);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function processAddMissing(filesToUpdate, configIndividualPathEclipse, configsToUpdate, mergeClients, dryRun) {
|
|
131
|
+
await processInEachFile(filesToUpdate, configIndividualPathEclipse,
|
|
132
|
+
configsToUpdate, mergeClients, dryRun, addMissingNodes);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function processEachFile(filesToUpdate, configIndividualPathEclipse, configsToUpdate, mergeClients, dryRun, processAction) {
|
|
136
|
+
let updatedFiles = 0;
|
|
137
|
+
const globalConfig = path.join(path.dirname(configIndividualPathEclipse), "config");
|
|
138
|
+
for(const file of filesToUpdate) {
|
|
139
|
+
for(const c of configsToUpdate) {
|
|
140
|
+
const individualConfigPath = path.join(configIndividualPathEclipse, c, "yaml", file);
|
|
141
|
+
const relatedConfigsForFile = await getRelatedConfigs(individualConfigPath, mergeClients);
|
|
142
|
+
for(const config of relatedConfigsForFile) {
|
|
143
|
+
const hasUpdates = processAction(path.join(globalConfig, "yaml", file), config, dryRun);
|
|
144
|
+
if(hasUpdates) updatedFiles++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log();
|
|
150
|
+
console.log(chalk.green(`Summary: Updated ${updatedFiles} files.`));
|
|
151
|
+
if (dryRun) {
|
|
152
|
+
console.log(chalk.yellow("Dry run complete — no changes were made."));
|
|
153
|
+
} else {
|
|
154
|
+
console.log(chalk.green("Config mutate completed successfully."));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Loop through each file in filesToUpdate and executes processAction
|
|
159
|
+
async function processInEachFile(filesToUpdate, configIndividualPathEclipse, configsToUpdate, mergeClients, dryRun, processAction) {
|
|
160
|
+
return await processEachFile(filesToUpdate, configIndividualPathEclipse,
|
|
161
|
+
configsToUpdate, mergeClients, dryRun,
|
|
162
|
+
(globalConfig, individualConfig, dryRun) => processInFile(globalConfig, individualConfig, dryRun, processAction));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Executes processAction for a yaml config file
|
|
166
|
+
function processInFile(compareFrom, compareTo, dryRun, processAction) {
|
|
167
|
+
try {
|
|
168
|
+
if(!FS.existsSync(compareFrom) || !FS.existsSync(compareTo)) return;
|
|
169
|
+
const fromDocument = YAML.parseDocument(FS.readFileSync(compareFrom, "utf-8"));
|
|
170
|
+
const toDocument = YAML.parseDocument(FS.readFileSync(compareTo, "utf-8"));
|
|
171
|
+
|
|
172
|
+
const hasChanges = processAction(fromDocument.contents, toDocument.contents);
|
|
173
|
+
const resultText = toDocument.toString();
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if(hasChanges) {
|
|
177
|
+
if(dryRun) {
|
|
178
|
+
console.debug(chalk.gray(`[Debug] From file ${compareFrom}`));
|
|
179
|
+
console.log(chalk.green(`[DryRun] Would Update file: ${compareTo}`));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(chalk.green(`Updating file ${compareTo}`));
|
|
183
|
+
FS.writeFileSync(compareTo, resultText);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return hasChanges;
|
|
187
|
+
}
|
|
188
|
+
catch(error) {
|
|
189
|
+
if(dryRun) {
|
|
190
|
+
console.log(chalk.red(`[DryRun] Error in file: ${compareFrom} <-> ${compareTo}: ${error.message}`));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.log(chalk.red(`Could not update file ${compareFrom} <-> ${compareTo}: ${error.message}`));
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Returns related configs for a base config file (config.yaml, 1_config.yaml, 2_config.yaml, ...)
|
|
200
|
+
async function getRelatedConfigs(file, withClients) {
|
|
201
|
+
const dir = path.dirname(file);
|
|
202
|
+
const ext = path.extname(file);
|
|
203
|
+
const name = path.basename(file, ext);
|
|
204
|
+
|
|
205
|
+
let patterns = [
|
|
206
|
+
path.join(dir, `${name}${ext}`), // name.yaml
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
if(withClients)
|
|
210
|
+
patterns.push(path.join(dir, `*_${name}${ext}`)); // *_name.yaml (für mandanten)
|
|
211
|
+
|
|
212
|
+
patterns = patterns.map(p => p.replaceAll(path.sep, '/'));
|
|
213
|
+
|
|
214
|
+
return (await fg(patterns, {
|
|
215
|
+
onlyFiles: true,
|
|
216
|
+
unique: true
|
|
217
|
+
})).map(f => f.replaceAll('/', path.sep));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// only_create
|
|
221
|
+
function addMissingNodes(fromNode, toNode) {
|
|
222
|
+
let hasChanges = false;
|
|
223
|
+
|
|
224
|
+
if (!fromNode || !toNode) return;
|
|
225
|
+
|
|
226
|
+
if (fromNode instanceof YAMLMap && toNode instanceof YAMLMap) {
|
|
227
|
+
for (const pair of fromNode.items) {
|
|
228
|
+
const keyNode = pair.key;
|
|
229
|
+
const fromVal = pair.value;
|
|
230
|
+
|
|
231
|
+
if (!toNode.has(keyNode)) {
|
|
232
|
+
toNode.items.push(pair);
|
|
233
|
+
hasChanges = true;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const toVal = toNode.get(keyNode, true);
|
|
238
|
+
hasChanges |= addMissingNodes(fromVal, toVal);
|
|
239
|
+
}
|
|
240
|
+
return hasChanges;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (fromNode instanceof YAMLSeq && toNode instanceof YAMLSeq) {
|
|
244
|
+
for (const fromItem of fromNode.items) {
|
|
245
|
+
const fromJson = nodeToJs(fromItem);
|
|
246
|
+
let exists = false;
|
|
247
|
+
|
|
248
|
+
for (const toItem of toNode.items) {
|
|
249
|
+
if (deepEqual(fromJson, nodeToJs(toItem))) {
|
|
250
|
+
exists = true;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!exists) {
|
|
256
|
+
toNode.items.push(fromItem);
|
|
257
|
+
hasChanges = true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return hasChanges;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// remove_missing
|
|
265
|
+
function removeMissingNodes(fromNode, toNode) {
|
|
266
|
+
let hasChanges = false;
|
|
267
|
+
|
|
268
|
+
if (!fromNode || !toNode) return false;
|
|
269
|
+
|
|
270
|
+
if (fromNode instanceof YAMLMap && toNode instanceof YAMLMap) {
|
|
271
|
+
for (let i = toNode.items.length - 1; i >= 0; i--) {
|
|
272
|
+
const toPair = toNode.items[i];
|
|
273
|
+
const keyNode = toPair.key;
|
|
274
|
+
|
|
275
|
+
if (!fromNode.has(keyNode)) {
|
|
276
|
+
toNode.items.splice(i, 1);
|
|
277
|
+
hasChanges = true;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const fromVal = fromNode.get(keyNode, true);
|
|
282
|
+
hasChanges |= removeMissingNodes(fromVal, toPair.value);
|
|
283
|
+
}
|
|
284
|
+
return hasChanges;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (fromNode instanceof YAMLSeq && toNode instanceof YAMLSeq) {
|
|
288
|
+
for (let i = toNode.items.length - 1; i >= 0; i--) {
|
|
289
|
+
const toItem = toNode.items[i];
|
|
290
|
+
const toJson = nodeToJs(toItem);
|
|
291
|
+
|
|
292
|
+
let exists = false;
|
|
293
|
+
for (const fromItem of fromNode.items) {
|
|
294
|
+
if (deepEqual(toJson, nodeToJs(fromItem))) {
|
|
295
|
+
exists = true;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!exists) {
|
|
301
|
+
toNode.items.splice(i, 1);
|
|
302
|
+
hasChanges = true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return hasChanges;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// create_update_overwrite
|
|
312
|
+
function mergeOverwriteNodes(fromNode, toNode) {
|
|
313
|
+
let hasChanges = false;
|
|
314
|
+
|
|
315
|
+
if (!fromNode || !toNode) return false;
|
|
316
|
+
|
|
317
|
+
if (fromNode instanceof YAMLMap && toNode instanceof YAMLMap) {
|
|
318
|
+
for (const fromPair of fromNode.items) {
|
|
319
|
+
const keyNode = fromPair.key;
|
|
320
|
+
const fromVal = fromPair.value;
|
|
321
|
+
|
|
322
|
+
if (!toNode.has(keyNode)) {
|
|
323
|
+
toNode.items.push(fromPair);
|
|
324
|
+
hasChanges = true;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const toVal = toNode.get(keyNode, true);
|
|
329
|
+
|
|
330
|
+
if (
|
|
331
|
+
fromVal?.constructor === toVal?.constructor &&
|
|
332
|
+
(fromVal instanceof YAMLMap || fromVal instanceof YAMLSeq)
|
|
333
|
+
) {
|
|
334
|
+
hasChanges |= mergeOverwriteNodes(fromVal, toVal);
|
|
335
|
+
} else {
|
|
336
|
+
toNode.set(keyNode, fromVal);
|
|
337
|
+
hasChanges = true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return hasChanges;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (fromNode instanceof YAMLSeq && toNode instanceof YAMLSeq) {
|
|
344
|
+
toNode.items = [...fromNode.items];
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
function nodeToJs(node) {
|
|
353
|
+
return node && typeof node.toJSON === "function"
|
|
354
|
+
? node.toJSON()
|
|
355
|
+
: node;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function deepEqual(a, b) {
|
|
359
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
360
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import prompts from "prompts";
|
|
2
|
-
import { mkdirSync, existsSync, readdirSync, rmSync } from "fs";
|
|
2
|
+
import { mkdirSync, existsSync, readdirSync, rmSync, copyFileSync, statSync } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { Config } from "../utils/config/config.js";
|
|
@@ -9,9 +9,7 @@ export default async function syncConfig() {
|
|
|
9
9
|
console.log()
|
|
10
10
|
|
|
11
11
|
const config = await Config.getConfig();
|
|
12
|
-
|
|
13
12
|
const configDirectoriesEclipse = readdirSync(config.configIndividualPathEclipse, { withFileTypes: true }).filter(dirent => dirent.isDirectory()).map(dirent => { return { title: dirent.name } })
|
|
14
|
-
|
|
15
13
|
const configDirectories = readdirSync(config.configIndividualPath, { withFileTypes: true }).filter(dirent => dirent.isDirectory()).map(dirent => { return { title: dirent.name } })
|
|
16
14
|
|
|
17
15
|
let success = true;
|
|
@@ -29,7 +27,8 @@ export default async function syncConfig() {
|
|
|
29
27
|
{ title: 'Import Everything (Repository → Work)', value: 'import_all' },
|
|
30
28
|
{ title: 'Export Everything (Work → Repository)', value: 'export_all' },
|
|
31
29
|
{ title: 'Import All ConfigIndividual (Repository → Work)', value: 'import_all_individuals' },
|
|
32
|
-
{ title: 'Export All ConfigIndividual (Work → Repository)', value: 'export_all_individuals' }
|
|
30
|
+
{ title: 'Export All ConfigIndividual (Work → Repository)', value: 'export_all_individuals' },
|
|
31
|
+
{ title: 'Sync from Config to ConfigIndividual (Repository → Repository)', value: 'sync_to_configIndividual' },
|
|
33
32
|
]
|
|
34
33
|
},
|
|
35
34
|
{
|
|
@@ -61,6 +60,16 @@ export default async function syncConfig() {
|
|
|
61
60
|
return filtered;
|
|
62
61
|
}
|
|
63
62
|
},
|
|
63
|
+
{
|
|
64
|
+
type: (prev, values) => values.direction == 'sync_to_configIndividual' ? 'autocomplete' : null,
|
|
65
|
+
name: 'configIndividualSelection',
|
|
66
|
+
message: 'ConfigIndividual (Destination)?',
|
|
67
|
+
choices: readdirSync(config.configIndividualPathEclipse, { withFileTypes: true }).filter(e => e.isDirectory())
|
|
68
|
+
.map(e => ({
|
|
69
|
+
title: e.name,
|
|
70
|
+
value: path.join(config.configIndividualPathEclipse, e.name)
|
|
71
|
+
}))
|
|
72
|
+
},
|
|
64
73
|
{
|
|
65
74
|
type: 'toggle',
|
|
66
75
|
name: 'dryRun',
|
|
@@ -73,8 +82,8 @@ export default async function syncConfig() {
|
|
|
73
82
|
type: 'select',
|
|
74
83
|
name: 'type',
|
|
75
84
|
message: 'Sync Type?',
|
|
76
|
-
choices: [
|
|
77
|
-
{ title: 'UPDATE', value: 'UPDATE' },
|
|
85
|
+
choices: (prev, values) => [
|
|
86
|
+
values.direction == 'sync_to_configIndividual' ? { title: 'CREATE (if not exists)', value: 'CREATE_IF_NOT_EXISTS' } : { title: 'UPDATE', value: 'UPDATE' },
|
|
78
87
|
{ title: 'OVERWRITE', value: 'OVERWRITE' }
|
|
79
88
|
]
|
|
80
89
|
}
|
|
@@ -90,7 +99,7 @@ export default async function syncConfig() {
|
|
|
90
99
|
|
|
91
100
|
|
|
92
101
|
if (success) {
|
|
93
|
-
let sourcePaths, destPaths;
|
|
102
|
+
let sourcePaths, destPaths, syncFiles;
|
|
94
103
|
|
|
95
104
|
switch (responses.direction) {
|
|
96
105
|
case 'import':
|
|
@@ -140,9 +149,24 @@ export default async function syncConfig() {
|
|
|
140
149
|
destPaths = [findConfigDirNamedConfigIn(destParent)];
|
|
141
150
|
break;
|
|
142
151
|
}
|
|
152
|
+
|
|
153
|
+
case 'sync_to_configIndividual': {
|
|
154
|
+
const sourceParent = path.resolve(config.configIndividualPathEclipse, '..');
|
|
155
|
+
|
|
156
|
+
sourcePaths = [findConfigDirNamedConfigIn(sourceParent)];
|
|
157
|
+
destPaths = [path.resolve(config.configIndividualPathEclipse, responses.configIndividualSelection)];
|
|
158
|
+
|
|
159
|
+
syncFiles = await FS.filePicker(path.resolve(config.configIndividualPathEclipse, '..', "config"), path.resolve(config.configIndividualPathEclipse, '..', "config"));
|
|
160
|
+
}
|
|
143
161
|
}
|
|
144
162
|
|
|
145
|
-
|
|
163
|
+
if(responses.direction != "sync_to_configIndividual")
|
|
164
|
+
processMultiple(responses, responses.dryRun, sourcePaths, destPaths);
|
|
165
|
+
else
|
|
166
|
+
processSyncToIndividual(responses.dryRun, syncFiles,
|
|
167
|
+
responses.configIndividualSelection,
|
|
168
|
+
findConfigDirNamedConfigIn(path.resolve(config.configIndividualPathEclipse, '..')),
|
|
169
|
+
responses.type == "OVERWRITE");
|
|
146
170
|
|
|
147
171
|
if (responses.dryRun) {
|
|
148
172
|
console.log()
|
|
@@ -163,13 +187,61 @@ export default async function syncConfig() {
|
|
|
163
187
|
})
|
|
164
188
|
|
|
165
189
|
if(confirmationResults.confirmation){
|
|
166
|
-
|
|
190
|
+
if(responses.direction != "sync_to_configIndividual")
|
|
191
|
+
processMultiple(responses, false, sourcePaths, destPaths);
|
|
192
|
+
else
|
|
193
|
+
processSyncToIndividual(false, syncFiles,
|
|
194
|
+
responses.configIndividualSelection,
|
|
195
|
+
findConfigDirNamedConfigIn(path.resolve(config.configIndividualPathEclipse, '..')),
|
|
196
|
+
responses.type == "OVERWRITE");
|
|
167
197
|
}
|
|
168
198
|
}
|
|
169
199
|
console.log();
|
|
170
200
|
}
|
|
171
201
|
}
|
|
172
202
|
|
|
203
|
+
function processSyncToIndividual(dryRun, sourceFiles, targetConfigIndividual, configPath, overwrite) {
|
|
204
|
+
console.log();
|
|
205
|
+
let changed = 0;
|
|
206
|
+
|
|
207
|
+
sourceFiles.forEach(sourceFile => {
|
|
208
|
+
let shouldCopy = false;
|
|
209
|
+
|
|
210
|
+
let destPath = path.join(targetConfigIndividual, sourceFile);
|
|
211
|
+
let sourcePath = path.join(configPath, sourceFile);
|
|
212
|
+
|
|
213
|
+
if (overwrite || !existsSync(destPath)) {
|
|
214
|
+
shouldCopy = true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let shouldCreateDirectory = shouldCopy && !existsSync(path.dirname(destPath));
|
|
218
|
+
|
|
219
|
+
if (shouldCopy) {
|
|
220
|
+
if (dryRun) {
|
|
221
|
+
if(shouldCreateDirectory) {
|
|
222
|
+
console.log(chalk.blue(`[DryRun] Would create directory: ${path.dirname(destPath)}`));
|
|
223
|
+
}
|
|
224
|
+
console.log(chalk.blue(`[DryRun] Would ${overwrite ? "overwrite" : "create"} file: ${destPath}`));
|
|
225
|
+
} else {
|
|
226
|
+
if(shouldCreateDirectory){
|
|
227
|
+
mkdirSync(path.dirname(destPath));
|
|
228
|
+
}
|
|
229
|
+
copyFileSync(sourcePath, destPath);
|
|
230
|
+
}
|
|
231
|
+
changed++;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
console.log();
|
|
236
|
+
console.log(chalk.green(`Summary: Changed ${changed} items.`));
|
|
237
|
+
|
|
238
|
+
console.log();
|
|
239
|
+
if (dryRun) {
|
|
240
|
+
console.log(chalk.yellow("Dry run complete — no changes were made."));
|
|
241
|
+
} else {
|
|
242
|
+
console.log(chalk.green("Config sync completed successfully."));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
173
245
|
|
|
174
246
|
function processMultiple(responses, dryRun, sourcePaths, destPaths) {
|
|
175
247
|
console.log();
|
package/src/ressources/cmds.json
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"config_rewrite": {
|
|
9
9
|
"desc": "Rewrites WEGAS-Config"
|
|
10
10
|
},
|
|
11
|
+
"config_mutation": {
|
|
12
|
+
"desc": "Synchronize Configs with new/changed properties"
|
|
13
|
+
},
|
|
11
14
|
"sync_config": {
|
|
12
15
|
"desc": "Synchronize Config/ConfigIndividual-Folders between Work and Repository (Eclipse-Repo)"
|
|
13
16
|
},
|
package/src/utils/fs/FS.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { statSync, mkdirSync, existsSync, copyFileSync, readdirSync } from "fs";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
3
|
+
import path, { relative } from "path";
|
|
4
|
+
import prompts from "prompts";
|
|
5
5
|
|
|
6
6
|
export class FS {
|
|
7
|
-
static copyUpdatedFiles(sourceDir, destDir, dryRun = false, stats = { added: 0, updated: 0 }, filenameBlacklist = []) {
|
|
7
|
+
static copyUpdatedFiles(sourceDir, destDir, dryRun = false, stats = { added: 0, updated: 0 }, filenameBlacklist = [], onlyCopyNonExistant = false) {
|
|
8
8
|
if (!existsSync(destDir)) {
|
|
9
9
|
if (dryRun) {
|
|
10
10
|
console.log(chalk.gray(`[DryRun] Would create directory: ${destDir}`));
|
|
@@ -30,7 +30,7 @@ export class FS {
|
|
|
30
30
|
|
|
31
31
|
if (!existsSync(destPath)) {
|
|
32
32
|
shouldCopy = true;
|
|
33
|
-
} else {
|
|
33
|
+
} else if(!onlyCopyNonExistant) {
|
|
34
34
|
const sourceStat = statSync(sourcePath);
|
|
35
35
|
const destStat = statSync(destPath);
|
|
36
36
|
|
|
@@ -84,5 +84,61 @@ export class FS {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
|
|
88
|
+
static async filePicker(startDir = process.cwd(), navRootDir = process.cwd()) {
|
|
89
|
+
let current = startDir;
|
|
90
|
+
|
|
91
|
+
while(true) {
|
|
92
|
+
const entries = readdirSync(current, { withFileTypes: true });
|
|
93
|
+
|
|
94
|
+
const choices = [
|
|
95
|
+
{ title: '[Dir] . (Select Current Directory)', value: '.', isDirectory: true },
|
|
96
|
+
...entries.map(e => ({
|
|
97
|
+
title: e.isDirectory() ? `${e.name} [Dir]` : e.name,
|
|
98
|
+
value: e.name,
|
|
99
|
+
isDirectory: e.isDirectory()
|
|
100
|
+
})).sort((a, b) => a.isDirectory && !b.isDirectory ? -1 : (!a.isDirectory && b.isDirectory ? 1 : a.value.localeCompare(b.value)))
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
if(path.relative(navRootDir, current) !== "") {
|
|
104
|
+
choices.unshift({ title: '[Dir] .. (Go Up one Directory)', value: '..', isDirectory: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { file } = await prompts({
|
|
108
|
+
type: 'autocomplete',
|
|
109
|
+
name: "file",
|
|
110
|
+
message: `Pick Files: ${current}`,
|
|
111
|
+
choices
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const picked = choices.find(c => c.value === file);
|
|
115
|
+
|
|
116
|
+
if(picked.isDirectory) {
|
|
117
|
+
const dir = picked.value;
|
|
118
|
+
if(dir === ".") return FS.getAllFiles(current).map(e => path.relative(navRootDir, e));
|
|
119
|
+
current = dir === ".." ? path.dirname(current) : path.join(current, dir);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
return [path.relative(navRootDir, path.join(current, picked.value))];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static getAllFiles(dir) {
|
|
128
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
129
|
+
const files = [];
|
|
130
|
+
|
|
131
|
+
for(const entry of entries) {
|
|
132
|
+
const fullPath = path.join(dir, entry.name);
|
|
87
133
|
|
|
134
|
+
if(entry.isDirectory()) {
|
|
135
|
+
files.push(...FS.getAllFiles(fullPath));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
files.push(fullPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return files;
|
|
143
|
+
}
|
|
88
144
|
}
|