prettify-bru 1.0.2 → 1.2.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/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # Prettify Bruno Bru Files
2
2
 
3
- A simple CLI tool to prettify the contents of `.bru` files.
3
+ A simple CLI tool to prettify the contents of [Bruno](https://www.usebruno.com/) `.bru` files. It uses [Prettier](https://prettier.io/) to consistently impose a standardised format to both JSON and JavaScript code, in all Bruno files in your project.
4
+
5
+ ## Why use this?
6
+
7
+ If you use Git (or similar) to track your Bruno collections and share them with your team, nobody wants to waste time discussing cosmetic formatting! Your energy is better spent thinking about the actual tests. If one person happens to be in the habit of using 4-space indentation and adding semi-colons on the end of lines in JavaScript, but another person tends to use 2-space indentation and omits the semi-colons, there is a chance that PRs descend into a tug of war, each pull request containing a bunch of unnecessary cosmetic changes that draw the reviewer's attention away from the important changes that actually impact what is being tested.
8
+
9
+ Thankfully, Bruno is already somewhat opinionated about code style so this project can follow its lead. In the desktop app, when editing a JSON body on a request there is a "Prettify" button which always reformats JSON with 2-space indentation. There is no adjascent option to reformat the "script" or "tests" tabs, so for JavaScript I've had to pick a set of style rules which I think most closely fit with what a QAer would want... familiar and short. I've gone with the same 2-space indentation as JSON, no semi-colons at the end of lines for cleanliness and double-quotes for strings so you can copy and paste chunks between JavaScript and JSON.
10
+
11
+ This package is fairly young, I'm developing it in my free time to help the thousands of QA people living through the industry transition from being manual testers to becoming _test engineers_. They now need a lot of the same tools developers use to implement consistent style but they don't have the background of being developers.
12
+
13
+ Any feedback or bug reports are welcome, the Issues tab is open on the Git repo https://github.com/martinjoiner/prettify-bru
4
14
 
5
15
  ## Installation
6
16
 
@@ -22,7 +32,7 @@ Get a non-destructive report of all files that could potentially be re-formatted
22
32
  npx prettify-bru
23
33
  ```
24
34
 
25
- The above command will walk all subdirectories finding `.bru` files.
35
+ The above command will walk all subdirectories finding `.bru` files.
26
36
  With each file it will assess the formatting of the JSON/JavaScript inside the following types of block:
27
37
 
28
38
  - `body:json` will be parsed with the JSON parser
@@ -36,10 +46,20 @@ To actually **modify** the files, I recommend committing your changes before doi
36
46
  npx prettify-bru --write
37
47
  ```
38
48
 
39
- ⚠️ This will modify the files in place, use with caution.
49
+ ⚠️ Including the `--write` option will modify the files in place, use with caution.
40
50
 
41
51
  To just do a single subdirectory, provide the path as an argument, so search the folder names "speed-tests":
42
52
 
43
53
  ```
44
54
  npx prettify-bru speed-tests
45
55
  ```
56
+
57
+ To just do 1 block type (for example just `body:json`), use the `--only` option:
58
+
59
+ ```
60
+ npx prettify-bru --only body:json
61
+ ```
62
+
63
+ ## CI Pipelines and Workflows
64
+
65
+ You may want to configure your CI Pipeline to run this command when a pull-request is raised. The `prettify-bru` command returns exit code 1 if it finds any files that contain an error or require reformatting. Otherwise it will return exit code 0.
package/cli.js CHANGED
@@ -5,7 +5,7 @@ import {hideBin} from 'yargs/helpers';
5
5
  import {main} from './lib/main.mjs';
6
6
 
7
7
  const argv = yargs(hideBin(process.argv))
8
- .command('$0 [path] [-w|--write]', `Formats all .bru files (including subdirectories)`, (yargs) => {
8
+ .command('$0 [path] [-w|--write] [--only "..."]', `Formats all .bru files (including subdirectories)`, (yargs) => {
9
9
  return yargs.positional('path', {
10
10
  describe: 'The root path to search from',
11
11
  type: 'string',
@@ -15,16 +15,18 @@ const argv = yargs(hideBin(process.argv))
15
15
  })
16
16
  })
17
17
  .options({
18
+ only: {
19
+ describe: 'Limit to only 1 block type',
20
+ type: 'string',
21
+ choices: ['body:json', 'json', 'script:pre-request', 'pre-request', 'script:post-request', 'post-request', 'tests']
22
+ },
18
23
  w: {
24
+ describe: 'Write mode (Formats files in place, overwriting contents)',
19
25
  alias: 'write',
20
26
  type: 'boolean',
21
27
  default: false,
22
28
  },
23
29
  })
24
- .describe({
25
- w: 'Write mode (Formats files in place, overwriting contents)',
26
- h: 'Display the help message',
27
- })
28
30
  .boolean(['w', 'h'])
29
31
  .alias('h', 'help')
30
32
  .parse();
@@ -32,11 +34,22 @@ const argv = yargs(hideBin(process.argv))
32
34
  if (argv.h) {
33
35
  yargs.showHelp();
34
36
  } else {
35
- go(argv.path, argv.w);
37
+ go(argv.path, argv.w, argv.only ?? null);
36
38
  }
37
39
 
38
- function go(path, write) {
39
- main(process.cwd(), path, write).catch((err) => {
40
+ /**
41
+ * @param {string} path
42
+ * @param {boolean} write Whether to actually modify the files or not
43
+ * @param {?string} only Limit to only the block type with a name containing value
44
+ */
45
+ function go(path, write, only) {
46
+ main(console, process.cwd(), path, write, only)
47
+ .then(changesRequired => {
48
+ if (changesRequired) {
49
+ process.exitCode = 1
50
+ }
51
+ })
52
+ .catch((err) => {
40
53
  console.error(err);
41
54
  process.exitCode = 1;
42
55
  });
package/lib/files.mjs ADDED
@@ -0,0 +1,57 @@
1
+ import fs from 'fs';
2
+ import path from 'path'
3
+
4
+ /**
5
+ * @param {string} filePath
6
+ * @returns {string}
7
+ */
8
+ export function readFile(filePath) {
9
+ return fs.readFileSync(filePath, "utf8")
10
+ }
11
+
12
+ /**
13
+ * @param {string} filePath
14
+ * @param {string} contents
15
+ * @returns void
16
+ */
17
+ export function writeFile(filePath, contents) {
18
+ fs.writeFileSync(filePath, contents, "utf8")
19
+ }
20
+
21
+ export function findFiles(path) {
22
+ const files = [];
23
+
24
+ walkDir(path, (p) => {
25
+ if (p.endsWith(".bru")) files.push(p);
26
+ });
27
+ return files;
28
+ }
29
+
30
+ /**
31
+ * @callback fileCallback
32
+ * @param {string} fullPath
33
+ */
34
+
35
+ /**
36
+ * @param {string} dir
37
+ * @param {fileCallback} onFile
38
+ */
39
+ function walkDir(dir, onFile) {
40
+ // Skip node_modules by default
41
+ const skip = new Set(["node_modules", ".git"]);
42
+ let entries;
43
+ try {
44
+ entries = fs.readdirSync(dir, {withFileTypes: true});
45
+ } catch (e) {
46
+ return;
47
+ }
48
+ for (const entry of entries) {
49
+ const full = path.join(dir, entry.name);
50
+ if (entry.isDirectory()) {
51
+ if (skip.has(entry.name)) continue;
52
+ walkDir(full, onFile);
53
+ } else if (entry.isFile()) {
54
+ onFile(full);
55
+ }
56
+ }
57
+ }
package/lib/format.mjs CHANGED
@@ -18,33 +18,49 @@ const formattableBlocks = [
18
18
  ['tests', 'babel'],
19
19
  ]
20
20
 
21
-
22
- /** Uses Prettier to format blocks of JSON/JavaScript */
23
- export async function formatBlocks(originalContents) {
21
+ /**
22
+ * Uses Prettier to format blocks of JSON/JavaScript
23
+ *
24
+ * @param {string} originalContents The file contents as loaded from file system
25
+ * @param {?string} only Limit to only the block type with a name containing value
26
+ * @returns {Promise<{newContents: string, blocksSearchedFor: number, changeable: number, error_messages: string[]}>}
27
+ */
28
+ export async function formatBlocks(originalContents, only = null) {
24
29
 
25
30
  let fileOutcome = {
26
31
  newContents: originalContents.replace(/\r\n/g, "\n"),
27
- changeable: false,
32
+ blocksSearchedFor: 0,
33
+ changeable: 0,
28
34
  error_messages: []
29
35
  }
30
36
 
31
37
  let i
32
38
  for (i in formattableBlocks) {
33
- const blockOutcome = await formatBlock(fileOutcome.newContents, ...formattableBlocks[i])
39
+ let [blockName, parser] = formattableBlocks[i]
40
+ if (only !== null && !blockName.includes(only)) continue;
41
+
42
+ const blockOutcome = await formatBlock(fileOutcome.newContents, blockName, parser)
43
+ fileOutcome.blocksSearchedFor++
34
44
  if (blockOutcome.error_message !== null) {
35
45
  fileOutcome.error_messages.push(blockOutcome.error_message)
36
46
  } else if (blockOutcome.changeable) {
37
- fileOutcome.changeable = true
38
- fileOutcome.newContents = blockOutcome.fileContents;
47
+ fileOutcome.changeable++
48
+ fileOutcome.newContents = blockOutcome.fileContents
39
49
  }
40
50
  }
41
51
 
42
52
  return fileOutcome
43
53
  }
44
54
 
55
+ /**
56
+ * @param {string} fileContents
57
+ * @param {string} blockName
58
+ * @param {string} parser
59
+ * @returns {Promise<{fileContents: string, changeable: boolean, error_message: ?string}>}
60
+ */
45
61
  async function formatBlock(fileContents, blockName, parser) {
46
62
 
47
- let outcome = {fileContents, changeable: false, error_message: null};
63
+ let outcome = {fileContents, changeable: false, error_message: null}
48
64
 
49
65
  const blockBodyRegex = new RegExp('\n' + blockName + ' [{]\\n(.+?)\\n}\\n', 's')
50
66
  const match = fileContents.match(blockBodyRegex)
package/lib/main.mjs CHANGED
@@ -1,16 +1,17 @@
1
- import fs from 'fs'
2
- import path from 'path'
1
+ import {findFiles, readFile, writeFile} from './files.mjs';
3
2
  import {formatBlocks} from './format.mjs'
4
3
 
5
4
  /**
6
5
  * Finds all .bru files and formats contents
7
6
  *
8
- * @param cwd {String} Current working directory
9
- * @param path {String}
10
- * @param write {Boolean}
11
- * @returns {Promise<void>}
7
+ * @param {object} console The console to be used for outputting messages
8
+ * @param {string} cwd Current working directory
9
+ * @param {string} path
10
+ * @param {boolean} write
11
+ * @param {?string} only Limit to only the block type with a name containing value
12
+ * @returns {Promise<boolean>} True means some files contained errors or needed reformatting
12
13
  */
13
- export async function main(cwd, path, write) {
14
+ export async function main(console, cwd, path, write, only = null) {
14
15
 
15
16
  if (path === '') {
16
17
  path = cwd
@@ -19,11 +20,7 @@ export async function main(cwd, path, write) {
19
20
  path = cwd + '/' + path
20
21
  }
21
22
 
22
- const files = [];
23
-
24
- walkDir(path, (p) => {
25
- if (p.endsWith(".bru")) files.push(p);
26
- });
23
+ const files = findFiles(path);
27
24
 
28
25
  if (files.length === 0) {
29
26
  console.log("No .bru files found.");
@@ -36,7 +33,7 @@ export async function main(cwd, path, write) {
36
33
  let erroredFiles = [];
37
34
 
38
35
  for (const filePath of files) {
39
- const outcome = await processFile(filePath, write);
36
+ const outcome = await processFile(filePath, write, only);
40
37
 
41
38
  let displayFilePath = filePath.replace(new RegExp('^' + cwd + '/'), "");
42
39
 
@@ -69,37 +66,25 @@ export async function main(cwd, path, write) {
69
66
  const requireNothing = files.length - changeableFiles.length - erroredFiles.length;
70
67
  console.log(
71
68
  `\x1b[35mProcessed ${files.length} .bru file(s):\x1b[0m ${changeablePrefix} in ${changeableFiles.length}. `
72
- + `Encountered errors in ${erroredFiles.length}. ${requireNothing} file(s) did not require any changes.`
69
+ + `Encountered errors in ${erroredFiles.length}. ${requireNothing} file(s) did not require any changes.`
73
70
  );
74
- }
75
71
 
76
- function walkDir(dir, onFile) {
77
- // Skip node_modules by default
78
- const skip = new Set(["node_modules", ".git"]);
79
- let entries;
80
- try {
81
- entries = fs.readdirSync(dir, {withFileTypes: true});
82
- } catch (e) {
83
- return;
84
- }
85
- for (const entry of entries) {
86
- const full = path.join(dir, entry.name);
87
- if (entry.isDirectory()) {
88
- if (skip.has(entry.name)) continue;
89
- walkDir(full, onFile);
90
- } else if (entry.isFile()) {
91
- onFile(full);
92
- }
93
- }
72
+ return (erroredFiles.length > 0 || changeableFiles.length > 0)
94
73
  }
95
74
 
96
- async function processFile(filePath, write) {
97
- const original = fs.readFileSync(filePath, "utf8");
75
+ /**
76
+ * @param {string} filePath
77
+ * @param {boolean} write
78
+ * @param {?string} only Limit to only the block type with a name containing value
79
+ * @returns {Promise<void>}
80
+ */
81
+ async function processFile(filePath, write, only) {
82
+ const original = readFile(filePath);
98
83
 
99
- const fileOutcome = await formatBlocks(original);
84
+ const fileOutcome = await formatBlocks(original, only);
100
85
 
101
- if (write && fileOutcome.changeable) {
102
- fs.writeFileSync(filePath, fileOutcome.newContents, "utf8");
86
+ if (write && fileOutcome.changeable > 0) {
87
+ writeFile(filePath, fileOutcome.newContents);
103
88
  }
104
89
 
105
90
  return fileOutcome;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prettify-bru",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "Prettifies JSON and JavaScript blocks in Bruno .bru files",
5
5
  "keywords": [
6
6
  "bruno",
@@ -32,7 +32,7 @@
32
32
  "yargs": "18.0.0"
33
33
  },
34
34
  "scripts": {
35
- "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
35
+ "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --verbose"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@jest/globals": "^30.2.0",