prettify-bru 1.9.0 β†’ 1.9.2

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
@@ -14,7 +14,6 @@ Imposes a standard format on all blocks of JSON and JavaScript code across multi
14
14
  ## Table of contents
15
15
 
16
16
  <!-- TOC -->
17
-
18
17
  - [Why use this?](#why-use-this)
19
18
  - [Style choices](#style-choices)
20
19
  - [Feedback](#feedback)
@@ -24,9 +23,12 @@ Imposes a standard format on all blocks of JSON and JavaScript code across multi
24
23
  - [Fixing the files](#fixing-the-files)
25
24
  - [Limit to one directory](#limit-to-one-directory)
26
25
  - [Limit to one file](#limit-to-one-file)
27
- - [Limit to one block type](#limit-to-one-block-type)
26
+ - [Limit to only a subset of blocks](#limit-to-only-a-subset-of-blocks)
28
27
  - [Complex example](#complex-example)
29
28
  - [Config file](#config-file)
29
+ - [Agnostic File Paths](#agnostic-file-paths)
30
+ - [Shorten Getters](#shorten-getters)
31
+ - [Prettier](#prettier)
30
32
  - [Automatically checking PRs](#automatically-checking-prs)
31
33
  <!-- TOC -->
32
34
 
@@ -104,14 +106,20 @@ Similar to above example, you can also provide a specific filename:
104
106
  npx prettify-bru speed-tests/get-all.bru
105
107
  ```
106
108
 
107
- ### Limit to one block type
109
+ ### Limit to only a subset of blocks
108
110
 
109
- Use the `--only` option to just operate on 1 block type and ignore the rest. For example to only assess the `body:json` blocks do:
111
+ Use the `--only` option to operate on a specific block or subset of blocks. For example to only assess the `body:json` blocks in your files, specify the exact name of the block:
110
112
 
111
113
  ```
112
114
  npx prettify-bru --only body:json
113
115
  ```
114
116
 
117
+ Some values target groups of blocks:
118
+
119
+ - "graphql" will operate on `body:graphql` and `body:graphql:vars`
120
+ - "script" will do both `script:pre-request` and `script:post-response`
121
+ - "body" will target all 3 body blocks `body:json`, `body:graphql` and `body:graphql:vars`
122
+
115
123
  ### Complex example
116
124
 
117
125
  Fix the formatting of just the `body:json` block in 1 specific file:
package/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import yargs from 'yargs'
4
4
  import {hideBin} from 'yargs/helpers'
5
5
  import {main} from './lib/main.mjs'
6
+ import {onlyParamOptions} from './lib/onlyParam.mjs'
6
7
 
7
8
  const argv = yargs(hideBin(process.argv))
8
9
  .command(
@@ -20,17 +21,9 @@ const argv = yargs(hideBin(process.argv))
20
21
  )
21
22
  .options({
22
23
  only: {
23
- describe: 'Limit to only 1 block type',
24
+ describe: 'Limit to only certain block types',
24
25
  type: 'string',
25
- choices: [
26
- 'body:json',
27
- 'json',
28
- 'script:pre-request',
29
- 'pre-request',
30
- 'script:post-response',
31
- 'post-request',
32
- 'tests',
33
- ],
26
+ choices: Object.keys(onlyParamOptions),
34
27
  },
35
28
  w: {
36
29
  describe: 'Write mode (Formats files in place, overwriting contents)',
package/lib/config.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import {readIfExists} from './files.mjs'
2
+ import {styleText} from 'node:util'
2
3
 
3
4
  const configFilename = '.prettifybrurc'
4
5
 
@@ -44,17 +45,19 @@ export function parseFile(console, fileContents) {
44
45
  fileConfig = JSON.parse(fileContents)
45
46
  } catch (e) {
46
47
  console.error(
47
- `\x1b[31mError parsing JSON in ${configFilename} config file:\n${e.message}\x1b[0m\n`
48
+ styleText('red', `Error parsing JSON in ${configFilename} config file:\n${e.message}\n`)
48
49
  )
49
50
  return {}
50
51
  }
51
52
 
52
53
  if (fileConfig instanceof Array || typeof fileConfig !== 'object') {
53
- console.error(`\x1b[31m${configFilename} is not valid, the JSON is not an Object\x1b[0m\n`)
54
+ console.error(
55
+ styleText('red', `${configFilename} is not valid, the JSON is not an Object\n`)
56
+ )
54
57
  return {}
55
58
  }
56
59
 
57
- console.log(`πŸ”§ \x1b[2mUsing config file ${configFilename}\x1b[0m`)
60
+ console.log(`πŸ”§ ${styleText('dim', `Using config file ${configFilename}`)}`)
58
61
 
59
62
  let config = {}
60
63
 
@@ -73,11 +76,11 @@ export function parseFile(console, fileContents) {
73
76
  config[key] = fileConfig[key]
74
77
  } else {
75
78
  console.warn(
76
- `⚠️ \x1b[33m"${key}" is not correct type, it should be ${validType}\x1b[0m`
79
+ `⚠️ ${styleText('yellow', `"${key}" is not correct type, it should be ${validType}`)}`
77
80
  )
78
81
  }
79
82
  } else {
80
- console.warn(`⚠️ \x1b[33mIgnoring unsupported property "${key}"\x1b[0m`)
83
+ console.warn(`⚠️ ${styleText('yellow', `Ignoring unsupported property "${key}"`)}`)
81
84
  }
82
85
  })
83
86
 
package/lib/format.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import prettier from 'prettier'
2
2
  import {defaultConfig} from './config.mjs'
3
+ import {onlyParamOptions, validateOnlyParam} from './onlyParam.mjs'
3
4
  import {format as jsoncFormat, applyEdits} from 'jsonc-parser'
4
5
 
5
6
  // This Prettier config should match what the Bruno GUI implements
@@ -24,6 +25,9 @@ const formattableBlocks = [
24
25
  'tests',
25
26
  ]
26
27
 
28
+ const maskPattern = /("(?:\\.|[^"\\])*")|(\{\{.*?\}\})/g
29
+ const unmaskPattern = /"__β‡Žβ‡ŽSTART__(\{\{.*?\}\})__ENDβ‡Žβ‡Ž__"/g
30
+
27
31
  /**
28
32
  * @typedef {Object} FileOutcome
29
33
  * @property {string} newContents
@@ -42,6 +46,8 @@ const formattableBlocks = [
42
46
  * @returns {Promise<FileOutcome>}
43
47
  */
44
48
  export async function format(originalContents, only = null, configOverrides = {}) {
49
+ validateOnlyParam(only)
50
+
45
51
  /** @type {import('./config.mjs').PrettifyBruConfig} */
46
52
  const config = {
47
53
  ...defaultConfig,
@@ -61,15 +67,7 @@ export async function format(originalContents, only = null, configOverrides = {}
61
67
  }
62
68
 
63
69
  for (const blockName of formattableBlocks) {
64
- if (only !== null) {
65
- // This is non-documented feature, but is covered with tests.
66
- // Specifying only 'json' will match both 'body:json', 'pre-request' will match 'script:pre-request' etc.
67
- // However that is causing issue with `body:graphql` and `body:graphql:vars`,
68
- // as they both start with 'body:graphql'.
69
- // So not to introduce breaking change on non-documented feature, someone may rely on, use this workaround.
70
- if (blockName.startsWith('body:graphql') && blockName !== only) continue
71
- if (!blockName.includes(only)) continue
72
- }
70
+ if (only !== null && !blockName.match(onlyParamOptions[only])) continue
73
71
 
74
72
  const blockOutcome = await formatBlock(fileOutcome.newContents, blockName, config)
75
73
  fileOutcome.blocksSearchedFor++
@@ -145,8 +143,12 @@ async function formatBlock(fileContents, blockName, config) {
145
143
  if (blockName === 'body:graphql') {
146
144
  opts.parser = 'graphql'
147
145
  opts.bracketSpacing = true
146
+ unindented = wrapNonStringPlaceholdersInDelimiters(unindented)
148
147
  }
149
148
  reformatted = await prettier.format(unindented, opts)
149
+ if (blockName === 'body:graphql') {
150
+ reformatted = unwrapDelimitedPlaceholders(reformatted)
151
+ }
150
152
  } catch (e) {
151
153
  outcome.errorMessage = `Prettier could not format ${blockName} because...\n${e.message}`
152
154
  return outcome
@@ -194,23 +196,33 @@ function shortenGetters(blockContents) {
194
196
  }
195
197
 
196
198
  /**
197
- * Turns Bruno variable placeholders into strings with special delimiters, effectively making it valid JSON
199
+ * Turns Bruno variable placeholders into strings with special delimiters, effectively making it valid JSON,
200
+ * and for standard cases also valid GraphQL.
198
201
  *
199
- * @param {string} jsonBlock
202
+ * @param {string} block
200
203
  * @returns {string}
201
204
  */
202
- function wrapNonStringPlaceholdersInDelimiters(jsonBlock) {
203
- return jsonBlock.replace(/(:[^"]*)([{]{2}[^}]+}})([^"β‡Ž])/g, '$1"β‡Ž$2β‡Ž"$3')
205
+ function wrapNonStringPlaceholdersInDelimiters(block) {
206
+ return block.replace(maskPattern, (match, group1, group2) => {
207
+ // If group1 exists, we found a standard JSON string. Return it untouched.
208
+ if (group1) {
209
+ return group1
210
+ }
211
+
212
+ // If group2 exists, we found a placeholder outside a string.
213
+ // Wrap it in a unique dummy string.
214
+ return `"__β‡Žβ‡ŽSTART__${group2}__ENDβ‡Žβ‡Ž__"`
215
+ })
204
216
  }
205
217
 
206
218
  /**
207
219
  * Reverts delimited Bruno variable placeholders back to their original form within a JSON block.
208
220
  *
209
- * @param {string} jsonBlock
221
+ * @param {string} block
210
222
  * @returns {string}
211
223
  */
212
- function unwrapDelimitedPlaceholders(jsonBlock) {
213
- return jsonBlock.replace(/"β‡Ž({{[^}]+}})β‡Ž"/g, '$1')
224
+ function unwrapDelimitedPlaceholders(block) {
225
+ return block.replace(unmaskPattern, '$1')
214
226
  }
215
227
 
216
228
  /**
package/lib/main.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import {findFiles, readFile, writeFile} from './files.mjs'
2
2
  import {format} from './format.mjs'
3
+ import {validateOnlyParam} from './onlyParam.mjs'
3
4
  import {loadConfigFile} from './config.mjs'
5
+ import {styleText} from 'node:util'
4
6
 
5
7
  /**
6
8
  * Finds all .bru files and formats contents
@@ -13,6 +15,8 @@ import {loadConfigFile} from './config.mjs'
13
15
  * @returns {Promise<boolean>} True means some files contained errors or needed reformatting
14
16
  */
15
17
  export async function main(console, cwd, path, write, only = null) {
18
+ validateOnlyParam(only)
19
+
16
20
  if (path === '') {
17
21
  path = cwd
18
22
  } else {
@@ -46,14 +50,14 @@ export async function main(console, cwd, path, write, only = null) {
46
50
 
47
51
  const changeableSuffix = write ? 'reformatted' : 'require reformatting'
48
52
  let changeableReport = null
53
+ const changeableColor = write ? 'green' : 'yellow'
49
54
  if (changeableFiles.length) {
50
- const changeableCol = write ? '\x1b[32m' : '\x1b[33m'
51
55
  const emoji = write ? '✏️' : '⚠️'
52
56
  const changeableFilesDesc = fileDesc(changeableFiles)
53
- changeableReport = `${changeableCol}${changeableFilesDesc} ${changeableSuffix}`
54
- console.log(`\x1b[4m${changeableReport}:\x1b[0m\n`)
57
+ changeableReport = `${changeableFilesDesc} ${changeableSuffix}`
58
+ console.log(styleText([changeableColor, 'underline'], `${changeableReport}:\n`))
55
59
  changeableFiles.forEach(r =>
56
- console.log(`${emoji} ${changeableCol}${r.displayFilePath}\x1b[0m`)
60
+ console.log(`${emoji} ${styleText(changeableColor, r.displayFilePath)}`)
57
61
  )
58
62
  console.log(' ')
59
63
  }
@@ -61,12 +65,12 @@ export async function main(console, cwd, path, write, only = null) {
61
65
  let erroredReport = null
62
66
  if (erroredFiles.length) {
63
67
  const erroredFilesDesc = fileDesc(erroredFiles)
64
- erroredReport = `\x1b[31m${erroredFilesDesc} causing errors`
65
- console.warn(`\x1b[4m${erroredReport}:\x1b[0m\n`)
68
+ erroredReport = `${erroredFilesDesc} causing errors`
69
+ console.warn(styleText(['red', 'underline'], `${erroredReport}:\n`))
66
70
  erroredFiles.forEach((r, i) => {
67
71
  console.warn(`${i + 1}) ${r.displayFilePath}\n`)
68
72
  r.outcome.errorMessages.forEach(err => {
69
- console.warn(`❌ \x1b[31m${err}\x1b[0m\n`)
73
+ console.warn(`❌ ${styleText('red', err)}\n`)
70
74
  })
71
75
  })
72
76
  }
@@ -75,15 +79,17 @@ export async function main(console, cwd, path, write, only = null) {
75
79
  const filesDesc = fileDesc(files)
76
80
  console.log(`Inspected ${filesDesc}:`)
77
81
  if (changeableReport) {
78
- console.log(` ${changeableReport}\x1b[0m`)
82
+ console.log(styleText(changeableColor, ` ${changeableReport}`))
79
83
  }
80
84
  if (erroredReport) {
81
- console.log(` ${erroredReport}\x1b[0m`)
85
+ console.log(styleText('red', ` ${erroredReport}`))
82
86
  }
83
87
  if (requireNothing > 0) {
84
- let requireNothingMessage = requireNothing === files.length ? '\x1b[32m' : '\x1b[2m'
85
- requireNothingMessage += `${requireNothing} file` + (requireNothing > 1 ? 's' : '')
86
- console.log(` ${requireNothingMessage} did not require any changes\x1b[0m`)
88
+ const requireNothingColor = requireNothing === files.length ? 'green' : 'dim'
89
+ const requireNothingMessage = `${requireNothing} file` + (requireNothing > 1 ? 's' : '')
90
+ console.log(
91
+ ` ${styleText(requireNothingColor, `${requireNothingMessage} did not require any changes`)}`
92
+ )
87
93
  }
88
94
 
89
95
  return erroredFiles.length > 0 || changeableFiles.length > 0
@@ -0,0 +1,25 @@
1
+ export const onlyParamOptions = {
2
+ body: /^body:/,
3
+ 'body:json': /^body:json$/,
4
+ json: /json$/,
5
+ 'body:graphql': /^body:graphql$/,
6
+ graphql: /.+graphql/,
7
+ 'body:graphql:vars': /^body:graphql:vars$/,
8
+ script: /^script:/,
9
+ 'script:pre-request': /^script:pre-request$/,
10
+ 'pre-request': /.+:pre-request$/,
11
+ 'script:post-response': /^script:post-response$/,
12
+ 'post-response': /.+:post-response$/,
13
+ tests: /^tests$/,
14
+ }
15
+
16
+ /**
17
+ * @param {?string} only Value to be validated
18
+ * @throws Error
19
+ * @returns void
20
+ */
21
+ export function validateOnlyParam(only) {
22
+ if (only !== null && !Object.hasOwn(onlyParamOptions, only)) {
23
+ throw new Error('Invalid value for only parameter')
24
+ }
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prettify-bru",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "Prettifies JSON, JavaScript and GraphQL blocks in Bruno .bru files",
5
5
  "keywords": [
6
6
  "bruno",