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 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
- let repo = await getCustomizationRepo(filter,args);
21
- if (!args.local) await getCustomizationFiles(repo);
22
- await applyCustomization(repo);
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 cacheCustomizations = path.resolve(xdgData,"cob-cli","customizations.json")
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(cacheCustomizations))
93
+ customizationRepos = JSON.parse(fs.readFileSync(cacheCustomizationsFile))
41
94
  if (filter) {
42
- customizationRepos = customizationRepos.filter(repo => repo.name.indexOf(filter) != -1)
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
- let response = await axios.get(
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
- // Check if there's at least one customization
55
- if (customizationRepos.length == 0) throw new Error("\nError: ".red + " no customization found\n");
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((repo) => repo.name).sort(),
118
+ choices: customizationRepos.map((customizationRepo) => customizationRepo.name).sort(),
79
119
  },
80
120
  ]);
81
121
 
82
- //Return the full repo info
83
- return customizationRepos.find((repo) => repo.name == answer.customization);
122
+ //Return the full customizationRepo info
123
+ return customizationRepos.find((customizationRepo) => customizationRepo.name == answer.customization);
84
124
  }
85
125
 
86
126
  /* ************************************************************************ */
87
- async function getCustomizationFiles(repo) {
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,repo.name))) {
95
- console.log(" git " + repo.ssh_url);
96
- await git().clone(repo.ssh_url);
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 " + repo.ssh_url);
99
- process.chdir(repo.name);
100
- await git().pull();
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(repo) {
107
- console.log("\nApplying " + repo.name + " customization ...");
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", repo.name);
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(repo.name, answers, copy, mergeFiles);
170
+ customization.actions(customizationRepo.name, answers, copyAndMerge);
127
171
  } else {
128
172
  // Default actions
129
- await copy(repo.name, process.cwd(), answers);
130
- await mergeFiles(repo.name);
173
+ await copyAndMerge(customizationRepo.name, answers);
131
174
  }
132
175
 
133
176
  // Update customizations.json file
134
- updateCustomizationsVersions(repo.name, customization.version);
177
+ updateCustomizationsVersions(customizationRepo.name, customization.version);
135
178
  }
136
179
 
137
180
  /* ************************************************************************ */
138
- async function copy(repoName, target, substitutions = {}) {
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(repoName.indexOf("/") == 0) {
145
- // Based of repoName starting with "/" we assume this is already a fullpath
146
- source = repoName;
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 repoName on standard cob-cli xdgData path
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", repoName);
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 repo and Destination on the server repo
168
- await ncp(
169
- source,
170
- target,
171
- {
172
- clobber: true,
173
- filter: (src) => src.match(excludedFiles) == null,
174
- // TODO: comentado porque não funciona a copiar os ficheiros binários (em concreto as font no template/dashboards/dash)
175
- // transform(read, write) {
176
- // const replaceVars = new Transform({
177
- // transform: (chunk, encoding, done) => done(null,chunk.toString().replace(/__.+__/g, m => substitutions[m]))
178
- // })
179
- // read.pipe(replaceVars).pipe(write)
180
- // }
181
- },
182
- (error) => error && error.map((e) => e.message).join("\n")
183
- );
184
-
185
- const dryrun = await fg(["**/*__*__*"], { onlyFiles: false, dot: true }); //Just to give time for OS to stabilize
186
- const files = await fg(["**/*__*__*"], { onlyFiles: false, dot: true });
187
- for(let file of files.filter(name => name.indexOf("node_modules") == -1)) {
188
- if (file.match(/__MERGE__/)) return;
189
- fs.renameSync( file, file.replace(/__(.+)__/g, (match,g1) => substitutions[g1]) );
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
- const customizationsVersionsFile = "customizations.json";
238
- let customizationsVersions;
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] = version;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cob-cli",
3
- "version": "2.28.0",
3
+ "version": "2.30.0",
4
4
  "description": "A command line utility to help Cult of Bits partners develop with higher speed and reusing common code and best practices.",
5
5
  "preferGlobal": true,
6
6
  "repository": {