cob-cli 2.28.0 → 2.30.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/bin/cob-cli.js +1 -0
- package/lib/commands/customize.js +155 -85
- package/package.json +1 -1
package/bin/cob-cli.js
CHANGED
|
@@ -36,6 +36,7 @@ program
|
|
|
36
36
|
.option('-f --force', 'skips comparisons')
|
|
37
37
|
.option('-c --cache', 'cache all customizations')
|
|
38
38
|
.option('-l --local', 'use local files')
|
|
39
|
+
.option('-u --update', 'update all previsously added customizations')
|
|
39
40
|
.description('Interactive prompt to customize an aspect of the server')
|
|
40
41
|
.action( customize );
|
|
41
42
|
|
|
@@ -10,105 +10,149 @@ const { Transform } = require("stream");
|
|
|
10
10
|
const fg = require("fast-glob");
|
|
11
11
|
const fs = require("fs-extra");
|
|
12
12
|
|
|
13
|
+
const customizationsVersionsFile = "customizations.json";
|
|
14
|
+
|
|
13
15
|
/* ************************************************************************ */
|
|
14
16
|
async function customize(filter, args) {
|
|
15
17
|
try {
|
|
16
18
|
console.log("Customize...");
|
|
17
|
-
checkVersion();
|
|
18
|
-
if (!args.force) await checkWorkingCopyCleanliness();
|
|
19
|
+
if (!args.cache) checkVersion();
|
|
20
|
+
if (!args.force && !args.cache) await checkWorkingCopyCleanliness();
|
|
21
|
+
|
|
22
|
+
if (args.cache) {
|
|
23
|
+
await cacheAllCustomizations(filter, args);
|
|
24
|
+
|
|
25
|
+
} else if (args.update) {
|
|
26
|
+
await updatePreviouslyAppliedCostumizations(filter, args);
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
} else {
|
|
29
|
+
const customizationRepo = await getCustomizationRepo(filter, args);
|
|
30
|
+
await applyCustomization(customizationRepo, args);
|
|
31
|
+
|
|
32
|
+
console.log( colors.green("\nDone"), "\nCheck changes to your git working tree and try:" );
|
|
33
|
+
console.log( "\tcob-cli test\n" );
|
|
34
|
+
}
|
|
23
35
|
|
|
24
|
-
console.log( colors.green("\nDone"), "\nCheck changes to your git working tree and try:" );
|
|
25
|
-
console.log( "\tcob-cli test\n" );
|
|
26
36
|
} catch (err) {
|
|
27
37
|
console.error("\n", err.message);
|
|
28
38
|
}
|
|
29
39
|
}
|
|
30
40
|
module.exports = customize;
|
|
31
41
|
|
|
42
|
+
/* ************************************************************************ */
|
|
43
|
+
async function updatePreviouslyAppliedCostumizations(filter, args) {
|
|
44
|
+
if (filter || args.cache) {
|
|
45
|
+
throw new Error("\nError: ".red + " incompatible options. Use of --update with filter or --cache.\n");
|
|
46
|
+
}
|
|
47
|
+
console.log("Updating all installed customizations...");
|
|
48
|
+
|
|
49
|
+
let customizationsVersions = {};
|
|
50
|
+
if(fs.existsSync(customizationsVersionsFile)) {
|
|
51
|
+
const customizationsVersionsRawData = fs.readFileSync(customizationsVersionsFile);
|
|
52
|
+
customizationsVersions = JSON.parse(customizationsVersionsRawData);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const customizationRepos = await getCustomizationRepo(null, args);
|
|
56
|
+
for (let customizationRepo of customizationRepos) {
|
|
57
|
+
if(customizationsVersions[customizationRepo.name]) {
|
|
58
|
+
await applyCustomization(customizationRepo, args);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ************************************************************************ */
|
|
64
|
+
async function cacheAllCustomizations(filter, args) {
|
|
65
|
+
if (filter || args.local || args.update) {
|
|
66
|
+
throw new Error("\nError: ".red + " incompatible options. Use --cache as single argument\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log("Caching all customizations...");
|
|
70
|
+
const customizationRepos = await getCustomizationRepo(null, args);
|
|
71
|
+
for (let customizationRepo of customizationRepos) {
|
|
72
|
+
await downloadCustomizationFiles(customizationRepo);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const { xdgData } = await import("xdg-basedir");
|
|
76
|
+
const cacheCustomizationsFile = path.resolve(xdgData, "cob-cli", "customizations.json")
|
|
77
|
+
fs.writeFileSync(cacheCustomizationsFile,
|
|
78
|
+
JSON.stringify(customizationRepos, null, 2),
|
|
79
|
+
(err) => {
|
|
80
|
+
if(err) throw new Error("\nError: ".red + " problem writing " + cacheCustomizationsFile + ":", err.message);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
32
85
|
/* ************************************************************************ */
|
|
33
86
|
async function getCustomizationRepo(filter, args) {
|
|
34
87
|
const { xdgData } = await import("xdg-basedir");
|
|
35
|
-
const
|
|
88
|
+
const cacheCustomizationsFile = path.resolve(xdgData,"cob-cli","customizations.json")
|
|
36
89
|
|
|
37
90
|
let customizationRepos;
|
|
38
91
|
let customizationNameQuery = "customize. " + (filter ? filter : "");
|
|
39
92
|
if (args.local) {
|
|
40
|
-
customizationRepos = JSON.parse(fs.readFileSync(
|
|
93
|
+
customizationRepos = JSON.parse(fs.readFileSync(cacheCustomizationsFile))
|
|
41
94
|
if (filter) {
|
|
42
|
-
customizationRepos = customizationRepos.filter(
|
|
95
|
+
customizationRepos = customizationRepos.filter(customizationRepo => customizationRepo.name.indexOf(filter) != -1)
|
|
43
96
|
}
|
|
44
97
|
} else {
|
|
45
98
|
// Get list of relevant customizations from github
|
|
46
|
-
|
|
47
|
-
"https://api.github.com/search/repositories?q=" +
|
|
48
|
-
customizationNameQuery + "+in:name+org:cob",
|
|
49
|
-
{ headers: { Accept: "application/json", "Accept-Encoding": "identity" } }
|
|
50
|
-
)
|
|
99
|
+
const response = await axios.get( "https://api.github.com/search/repositories?q=" + customizationNameQuery + "+in:name+org:cob", { headers: { Accept: "application/json", "Accept-Encoding": "identity" } } )
|
|
51
100
|
customizationRepos = response.data.items
|
|
52
101
|
}
|
|
53
102
|
|
|
54
|
-
//
|
|
55
|
-
if (
|
|
103
|
+
//If the goal is to cache or update customizations just return all customizations
|
|
104
|
+
if (args.cache || args.update) {
|
|
105
|
+
return customizationRepos;
|
|
106
|
+
}
|
|
56
107
|
|
|
57
|
-
// Asks for customization to apply
|
|
58
|
-
if (args.cache && args.local) {
|
|
59
|
-
throw new Error("\nError: ".red + " incompatible options, --local AND --cache\n");
|
|
60
|
-
} else if(args.cache) {
|
|
61
|
-
console.log("Caching all customizations...");
|
|
62
|
-
for( let repo of customizationRepos) {
|
|
63
|
-
await getCustomizationFiles(repo);
|
|
64
|
-
}
|
|
65
|
-
fs.writeFileSync( cacheCustomizations,
|
|
66
|
-
JSON.stringify(customizationRepos, null, 2),
|
|
67
|
-
(err) => {
|
|
68
|
-
if (err) console.log( "Error writing " + cacheCustomizations + ":", err.message);
|
|
69
|
-
}
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
108
|
|
|
109
|
+
// Throw error if there's no customization
|
|
110
|
+
if (customizationRepos.length == 0) throw new Error("\nError: ".red + " no customization found\n");
|
|
111
|
+
|
|
112
|
+
// Otherwise asks for customization to apply
|
|
73
113
|
let answer = await inquirer.prompt([
|
|
74
114
|
{
|
|
75
115
|
type: "list",
|
|
76
116
|
name: "customization",
|
|
77
117
|
message: "What customization do you want to apply?",
|
|
78
|
-
choices: customizationRepos.map((
|
|
118
|
+
choices: customizationRepos.map((customizationRepo) => customizationRepo.name).sort(),
|
|
79
119
|
},
|
|
80
120
|
]);
|
|
81
121
|
|
|
82
|
-
//Return the full
|
|
83
|
-
return customizationRepos.find((
|
|
122
|
+
//Return the full customizationRepo info
|
|
123
|
+
return customizationRepos.find((customizationRepo) => customizationRepo.name == answer.customization);
|
|
84
124
|
}
|
|
85
125
|
|
|
86
126
|
/* ************************************************************************ */
|
|
87
|
-
async function
|
|
127
|
+
async function downloadCustomizationFiles(customizationRepo) {
|
|
88
128
|
const { xdgData } = await import("xdg-basedir");
|
|
89
129
|
const baseDir = process.cwd();
|
|
90
130
|
const cacheDir = path.resolve(xdgData,"cob-cli")
|
|
91
131
|
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
92
132
|
|
|
93
133
|
process.chdir(cacheDir);
|
|
94
|
-
if (!fs.existsSync(path.resolve(cacheDir,
|
|
95
|
-
console.log(" git " +
|
|
96
|
-
await git().clone(
|
|
134
|
+
if (!fs.existsSync(path.resolve(cacheDir,customizationRepo.name))) {
|
|
135
|
+
console.log(" git " + customizationRepo.ssh_url);
|
|
136
|
+
await git().clone(customizationRepo.ssh_url);
|
|
97
137
|
} else {
|
|
98
|
-
console.log(" git pull " +
|
|
99
|
-
process.chdir(
|
|
100
|
-
|
|
138
|
+
console.log(" git pull " + customizationRepo.ssh_url);
|
|
139
|
+
process.chdir(customizationRepo.name);
|
|
140
|
+
try {
|
|
141
|
+
await git().pull();
|
|
142
|
+
} catch (error) { }
|
|
101
143
|
}
|
|
102
144
|
process.chdir(baseDir);
|
|
103
145
|
}
|
|
104
146
|
|
|
105
147
|
/* ************************************************************************ */
|
|
106
|
-
async function applyCustomization(
|
|
107
|
-
console.log("\nApplying " +
|
|
148
|
+
async function applyCustomization(customizationRepo, args) {
|
|
149
|
+
console.log("\nApplying " + customizationRepo.name + " customization ...");
|
|
108
150
|
|
|
109
151
|
// Get customization info from convention defined file (customize.js)
|
|
152
|
+
if (!args.local) await downloadCustomizationFiles(customizationRepo);
|
|
153
|
+
|
|
110
154
|
const { xdgData } = await import("xdg-basedir");
|
|
111
|
-
const customizationPath = path.resolve( xdgData, "cob-cli",
|
|
155
|
+
const customizationPath = path.resolve( xdgData, "cob-cli", customizationRepo.name);
|
|
112
156
|
const customizationFile = path.resolve( customizationPath, "customize.js");
|
|
113
157
|
if (!fs.existsSync(customizationFile))
|
|
114
158
|
throw new Error("\nError: ".red + " no customize.js file found\n");
|
|
@@ -123,31 +167,30 @@ async function applyCustomization(repo) {
|
|
|
123
167
|
|
|
124
168
|
// Apply specific customization, if it exists, otherwise use default actions
|
|
125
169
|
if (customization.actions) {
|
|
126
|
-
customization.actions(
|
|
170
|
+
customization.actions(customizationRepo.name, answers, copyAndMerge);
|
|
127
171
|
} else {
|
|
128
172
|
// Default actions
|
|
129
|
-
await
|
|
130
|
-
await mergeFiles(repo.name);
|
|
173
|
+
await copyAndMerge(customizationRepo.name, answers);
|
|
131
174
|
}
|
|
132
175
|
|
|
133
176
|
// Update customizations.json file
|
|
134
|
-
updateCustomizationsVersions(
|
|
177
|
+
updateCustomizationsVersions(customizationRepo.name, customization.version);
|
|
135
178
|
}
|
|
136
179
|
|
|
137
180
|
/* ************************************************************************ */
|
|
138
|
-
async function
|
|
181
|
+
async function copyAndMerge(customizationRepoName, substitutions = {}) {
|
|
139
182
|
// https://www.npmtrends.com/copyfiles-vs-cpx-vs-ncp-vs-npm-build-tools
|
|
140
183
|
// https://www.npmjs.com/package/ncp
|
|
141
184
|
// https://www.npmjs.com/package/copyfiles
|
|
142
185
|
|
|
143
186
|
let source
|
|
144
|
-
if(
|
|
145
|
-
// Based of
|
|
146
|
-
source =
|
|
187
|
+
if(customizationRepoName.indexOf("/") == 0) {
|
|
188
|
+
// Based of customizationRepoName starting with "/" we assume this is already a fullpath
|
|
189
|
+
source = customizationRepoName;
|
|
147
190
|
} else {
|
|
148
|
-
// Otherwise assume
|
|
191
|
+
// Otherwise assume customizationRepoName on standard cob-cli xdgData path
|
|
149
192
|
const { xdgData } = await import("xdg-basedir");
|
|
150
|
-
source = path.resolve( xdgData, "cob-cli",
|
|
193
|
+
source = path.resolve( xdgData, "cob-cli", customizationRepoName);
|
|
151
194
|
}
|
|
152
195
|
|
|
153
196
|
console.log(" Copying template files ...");
|
|
@@ -164,30 +207,56 @@ async function copy(repoName, target, substitutions = {}) {
|
|
|
164
207
|
")"
|
|
165
208
|
);
|
|
166
209
|
|
|
167
|
-
// Source is on cob-cli
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
210
|
+
// Source is on cob-cli customizationRepo and Destination on the server repo
|
|
211
|
+
const target = process.cwd() // Always copy to directory where command is being executed, ie, the root directory of the server repo
|
|
212
|
+
const substitutionRegex = /__(((?!word).)*)__/g;
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
ncp(
|
|
215
|
+
source,
|
|
216
|
+
target,
|
|
217
|
+
{
|
|
218
|
+
clobber: true,
|
|
219
|
+
filter: (src) => src.match(excludedFiles) == null,
|
|
220
|
+
rename: function(target) {
|
|
221
|
+
// Don't rename __MERGE__ templates, they will be handled by the merge method
|
|
222
|
+
if (target.match(/__MERGE__/)) return target;
|
|
223
|
+
|
|
224
|
+
//get finalTarget from target with any existing substitution
|
|
225
|
+
const finalTarget = target.replace(substitutionRegex, (match,g1) => substitutions[g1] ? substitutions[g1] : match);
|
|
226
|
+
|
|
227
|
+
//if the directory of finalTarget doesn't exists it means that a replacement ocurred on the dirpath (ie, there was a /__.+__/ was on the dirpath), in which case we need to create the desired directory and remove the one just created by ncp
|
|
228
|
+
if (!fs.existsSync(path.dirname(finalTarget))) {
|
|
229
|
+
fs.mkdirSync(path.dirname(finalTarget), { recursive: true })
|
|
230
|
+
fs.rmdirSync(target.substring(0,target.lastIndexOf("__")+2), { recursive: true,force: false }) //NOTE: won't handle more than 1 substitution on the same dirpath
|
|
231
|
+
}
|
|
232
|
+
return finalTarget;
|
|
233
|
+
},
|
|
234
|
+
transform(read, write) {
|
|
235
|
+
const replaceVarsTransformFunction = new Transform({
|
|
236
|
+
transform: (chunk, encoding, done) => {
|
|
237
|
+
if(/\ufffd/.test(chunk) === true) {
|
|
238
|
+
// If chunk is binary don't change anything
|
|
239
|
+
done(null, chunk)
|
|
240
|
+
} else {
|
|
241
|
+
// Otherwise change any existing substitution
|
|
242
|
+
done(null,chunk.toString().replace(substitutionRegex, (match,g1) => substitutions[g1] ? substitutions[g1] : match))
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
read.pipe(replaceVarsTransformFunction).pipe(write)
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
async (error) => {
|
|
250
|
+
// If no error occurred then proced with merging files
|
|
251
|
+
if(!error) {
|
|
252
|
+
await mergeFiles(customizationRepoName);
|
|
253
|
+
resolve();
|
|
254
|
+
} else {
|
|
255
|
+
reject(error.map((e) => e.message).join("\n"))
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
});
|
|
191
260
|
}
|
|
192
261
|
|
|
193
262
|
/* ************************************************************************ */
|
|
@@ -234,15 +303,16 @@ async function mergeFiles(block) {
|
|
|
234
303
|
|
|
235
304
|
/* ************************************************************************ */
|
|
236
305
|
function updateCustomizationsVersions(customizationKey, version) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
try {
|
|
306
|
+
let customizationsVersions = {};
|
|
307
|
+
if(fs.existsSync(customizationsVersionsFile)) {
|
|
240
308
|
const customizationsVersionsRawData = fs.readFileSync(customizationsVersionsFile);
|
|
241
309
|
customizationsVersions = JSON.parse(customizationsVersionsRawData);
|
|
242
|
-
} catch (err) {
|
|
243
|
-
customizationsVersions = {};
|
|
244
310
|
}
|
|
245
|
-
customizationsVersions[customizationKey]
|
|
311
|
+
if(customizationsVersions[customizationKey]) {
|
|
312
|
+
customizationsVersions[customizationKey] = version
|
|
313
|
+
} else {
|
|
314
|
+
customizationsVersions = {[customizationKey]: version, ...customizationsVersions}
|
|
315
|
+
}
|
|
246
316
|
|
|
247
317
|
fs.writeFileSync(
|
|
248
318
|
customizationsVersionsFile,
|
package/package.json
CHANGED