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 +23 -3
- package/cli.js +21 -8
- package/lib/files.mjs +57 -0
- package/lib/format.mjs +24 -8
- package/lib/main.mjs +23 -38
- package/package.json +2 -2
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
|
-
⚠️
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
9
|
-
* @param
|
|
10
|
-
* @param
|
|
11
|
-
* @
|
|
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
|
-
|
|
69
|
+
+ `Encountered errors in ${erroredFiles.length}. ${requireNothing} file(s) did not require any changes.`
|
|
73
70
|
);
|
|
74
|
-
}
|
|
75
71
|
|
|
76
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
|
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",
|