Skip to content

Commit

Permalink
Merge pull request #14 from dalbitresb12/json5
Browse files Browse the repository at this point in the history
Added support for JSON5 files
  • Loading branch information
matthewdevenny authored Jun 25, 2024
2 parents d08fef6 + 96cd9d7 commit 6fa0c4e
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 20 deletions.
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

![test](https://github.com/boxboat/config-merge/workflows/test/badge.svg)

Tool for merging JSON/TOML/YAML files and performing environment variable substitution. Runs in a Docker Container.
Tool for merging JSON/JSON5/TOML/YAML files and performing environment variable substitution. Runs in a Docker Container.

## Usage

`config-merge` is released on [DockerHub](https://hub.docker.com/r/boxboat/config-merge/). Usage can be seen by running with the `-h` flag

```
docker pull boxboat/config-merge
docker run --rm boxboat/config-merge -h
```bash
$ docker pull boxboat/config-merge
$ docker run --rm boxboat/config-merge -h

boxboat/config-merge [-fnh] file1 [file2] ... [fileN]
-a, --array merge|overwrite|concat whether to merge, overwrite, or concatenate arrays. defaults to merge
-f, --format json|toml|yaml whether to output json, toml, or yaml. defaults to yaml
-h --help print the help message
boxboat/config-merge [flags] file1 [file2] ... [fileN]
-a, --array merge|overwrite|concat whether to merge, overwrite, or concatenate arrays. defaults to merge
-f, --format json|json5|toml|yaml whether to output json, json5, toml, or yaml. defaults to yaml
-h --help print the help message
--no-envsubst disable substituting env vars
files ending in .env and .sh will be sourced and used for environment variable substitution
files ending in .json, .js, .toml, .yaml, and .yml will be merged
files ending in .patch.json, .patch.js, .patch.toml, .patch.yaml, and .patch.yml will be applied as JSONPatch
files ending in .json, .js, .json5, .toml, .yaml, and .yml will be merged
files ending in .patch.json, .patch.js, .patch.json5, .patch.toml, .patch.yaml, and .patch.yml will be applied as JSONPatch
```

The working directory of the container is `/home/node` and files/directories that get merged should be mounted into this directory.
Expand All @@ -27,7 +28,7 @@ The working directory of the container is `/home/node` and files/directories tha

This example considers building a Docker Compose file that can be used for production, but also tested locally. The production compose file contains an extra network that is not available to the local developer. We are able to use the patching feature of `config-merge` to remove the unneeded network, and use the environment variable substitution feature to add a custom network.

```
```bash
docker_compose_config=$(
docker run --rm \
-v "$(pwd)/test/docker-compose/:/home/node/" \
Expand All @@ -48,7 +49,7 @@ EOF

Globbing is supported, but should be escaped in the `docker run` script so that expansion will occur inside of the container:

```
```bash
docker_compose_config=$(
docker run --rm \
-v "$(pwd)/test/docker-compose/:/home/node/" \
Expand All @@ -59,15 +60,15 @@ docker_compose_config=$(

## Merging

Files ending in `.json`, `.js`, `.toml`, `.yaml`, and `.yml` are merged together. The merging algorithm uses the [lodash merge](https://lodash.com/docs/4.17.4#merge) function and operates:
Files ending in `.json`, `.js`, `.json5`, `.toml`, `.yaml`, and `.yml` are merged together. The merging algorithm uses the [lodash merge](https://lodash.com/docs/4.17.4#merge) function and operates:

> Source properties that resolve to undefined are skipped if a destination value exists. Array and plain object properties are merged recursively. Other objects and value types are overridden by assignment. Source objects are applied from left to right. Subsequent sources overwrite property assignments of previous sources.
Use the `-a`/`--array` argument to configure the merging behavior for arrays.

## Patching

Files ending in `.patch.json`, `.patch.js`, `.patch.toml`, `.patch.yaml`, and `.patch.yml` are applied as [JSON Patch](http://jsonpatch.com/)
Files ending in `.patch.json`, `.patch.js`, `.patch.json5`, `.patch.toml`, `.patch.yaml`, and `.patch.yml` are applied as [JSON Patch](http://jsonpatch.com/)

## Environment Variable Substitution

Expand Down
29 changes: 23 additions & 6 deletions config-merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const fs = require('fs')
const glob = require('glob')
const mergeWith = require('lodash.mergewith')
const path = require('path')
const JSON5 = require('json5')
const YAML = require('yaml')
const toml = require('toml-j0.4')
const tomlify = require('tomlify-j0.4')
Expand All @@ -11,9 +12,11 @@ const { execFileSync } = require('child_process')
// constants
const envRe = new RegExp(/\.(?:env|sh)$/, "i")
const mergeJsonRe = new RegExp(/\.(?:json|js)$/, "i")
const mergeJson5Re = new RegExp(/\.json5$/, "i")
const mergeYamlRe = new RegExp(/\.(?:yaml|yml)$/, "i")
const mergeTomlRe = new RegExp(/\.toml$/, "i")
const patchJsonRe = new RegExp(/\.patch\.(?:json|js)$/, "i")
const patchJson5Re = new RegExp(/\.patch\.json5$/, "i")
const patchYamlRe = new RegExp(/\.patch\.(?:yaml|yml)$/, "i")
const patchTomlRe = new RegExp(/\.patch\.toml$/, "i")
const envReserved = new Set(["_", "SHLVL"])
Expand All @@ -31,12 +34,12 @@ let obj = {}
function printHelp() {
console.error("boxboat/config-merge [flags] file1 [file2] ... [fileN]")
console.error("-a, --array merge|overwrite|concat whether to merge, overwrite, or concatenate arrays. defaults to merge")
console.error("-f, --format json|toml|yaml whether to output json, toml, or yaml. defaults to yaml")
console.error("-f, --format json|json5|toml|yaml whether to output json, json5, toml, or yaml. defaults to yaml")
console.error("-h --help print the help message")
console.error(" --no-envsubst disable substituting env vars")
console.error(" files ending in .env and .sh will be sourced and used for environment variable substitution")
console.error(" files ending in .json, .js, .toml, .yaml, and .yml will be merged")
console.error(" files ending in .patch.json, .patch.js, .patch.toml, .patch.yaml, and .patch.yml will be applied as JSONPatch")
console.error(" files ending in .json, .js, .json5, .toml, .yaml, and .yml will be merged")
console.error(" files ending in .patch.json, .patch.js, .patch.json5, .patch.toml, .patch.yaml, and .patch.yml will be applied as JSONPatch")
}

// loads a .env file into the current environment
Expand Down Expand Up @@ -93,11 +96,11 @@ for (let arg of args) {
process.exit(1)
}
} else if (setFlag == "f") {
if (arg == "json" || arg == "toml" || arg == "yaml") {
if (arg == "json" || arg == "json5" || arg == "toml" || arg == "yaml") {
format = arg
setFlag = null
} else {
console.error(`Format should be "json", "toml", or "yaml", invalid: ${arg}`)
console.error(`Format should be "json", "json5", "toml", or "yaml", invalid: ${arg}`)
printHelp()
process.exit(1)
}
Expand Down Expand Up @@ -168,6 +171,15 @@ for (arg of globArgs) {
process.exit(1)
}
}
else if (arg.match(patchJson5Re)) {
let json5Patch = JSON5.parse(readFileAndSubEnv(arg))
if (Array.isArray(json5Patch)) {
applyPatch(obj, json5Patch)
} else {
console.error(`JSON5 patch file '${arg}' must a top-level array of patches`)
process.exit(1)
}
}
else if (arg.match(patchTomlRe)) {
let tomlPatch = toml.parse(readFileAndSubEnv(arg))
if (tomlPatch.patch) {
Expand All @@ -189,6 +201,9 @@ for (arg of globArgs) {
else if (arg.match(mergeJsonRe)) {
mergeWith(obj, JSON.parse(readFileAndSubEnv(arg)), customizer)
}
else if (arg.match(mergeJson5Re)) {
mergeWith(obj, JSON5.parse(readFileAndSubEnv(arg)), customizer)
}
else if (arg.match(mergeTomlRe)) {
mergeWith(obj, toml.parse(readFileAndSubEnv(arg)), customizer)
}
Expand All @@ -210,6 +225,8 @@ for (arg of globArgs) {
let serialized
if (format == "json") {
serialized = JSON.stringify(obj, null, " ")
} else if (format == "json5") {
serialized = JSON5.stringify(obj, null, " ")
} else if (format == "toml") {
serialized = tomlify.toToml(obj, {
space: 2,
Expand All @@ -224,7 +241,7 @@ if (format == "json") {
serialized = YAML.stringify(obj, options={simpleKeys: true})
}

if (format == "json" || format == "toml") {
if (format == "json" || format == "json5" || format == "toml") {
// json and toml do not print a newline by default
console.log(serialized)
} else {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"fast-json-patch": "3.1.1",
"glob": "7.1.2",
"json5": "2.2.3",
"lodash.mergewith": "4.6.2",
"toml-j0.4": "1.1.1",
"tomlify-j0.4": "3.0.0",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ inherits@2:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==

[email protected]:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==

[email protected]:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
Expand Down

0 comments on commit 6fa0c4e

Please sign in to comment.