yamlock 0.2.9 → 0.3.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
@@ -60,6 +60,9 @@ yamlock algorithms
60
60
 
61
61
  # Generate a random key for YAMLOCK_KEY
62
62
  yamlock keygen --length 64 --format base64
63
+
64
+ # Preview changes without touching files
65
+ yamlock encrypt config.yml -o config.enc.yml -p db.password -k "my-secret-key" -d
63
66
  ```
64
67
 
65
68
  The CLI detects YAML (`.yaml`/`.yml`) and JSON extensions automatically and writes the file back in the same format.
@@ -117,11 +120,36 @@ const restored = processConfig(processed, {
117
120
  key: KEY,
118
121
  algorithm: { algorithm: 'aes-192-cbc', ivLength: 24 }
119
122
  });
123
+
124
+ // Control what happens when encountering non-string values and customize path IDs
125
+ const mixedConfig = { db: { password: 'secret', retries: 3 } };
126
+ const lockedMixed = processConfig(mixedConfig, {
127
+ mode: 'encrypt',
128
+ key: KEY,
129
+ nonStringPolicy: 'stringify', // stringifies numbers/objects before encrypting
130
+ pathSerializer: (segments) => segments.join('/') // custom path naming (db/password instead of dot notation)
131
+ });
132
+
133
+ // Example of a path serializer that includes array indexes explicitly
134
+ const lockedUsers = processConfig(
135
+ { users: [{ tokens: ['abc'] }] },
136
+ {
137
+ mode: 'encrypt',
138
+ key: KEY,
139
+ pathSerializer: (segments) =>
140
+ segments
141
+ .map((segment, index) =>
142
+ typeof segment === 'number' ? `[${segment}]` : index === 0 ? segment : `/${segment}`
143
+ )
144
+ .join('')
145
+ }
146
+ );
120
147
  ```
121
148
 
122
149
  ## Advanced usage
123
150
 
124
151
  - **Selective encryption**: combine `--paths` on the CLI or `paths: []` in `processConfig` to encrypt only sensitive sections of a config file.
152
+ - **Non-string handling**: use `nonStringPolicy: 'ignore' | 'stringify' | 'error'` to control how numbers/objects are treated, and `pathSerializer` to change how traversal paths are represented (e.g., `db/password` instead of dot notation).
125
153
  - **CI/CD flows**: see [examples/docs/ci-cd.md](examples/docs/ci-cd.md) for a GitHub Actions job that decrypts configs for builds and re-encrypts them before publishing artifacts.
126
154
  - **Key rotation**: follow [examples/docs/key-rotation.md](examples/docs/key-rotation.md) for a step-by-step process, including scripting tips for large repos.
127
155
 
@@ -15,6 +15,8 @@ const MODES = {
15
15
  * @param {string|Buffer} options.key
16
16
  * @param {string|object} [options.algorithm]
17
17
  * @param {object} [options.algorithmOptions]
18
+ * @param {"ignore"|"stringify"|"error"} [options.nonStringPolicy]
19
+ * @param {(segments: Array<string|number>) => string} [options.pathSerializer]
18
20
  * @param {string[]} [options.paths]
19
21
  * @param {Array<string|number>} [options.parentPath]
20
22
  * @returns {Object|Array}
@@ -37,11 +39,13 @@ export function processConfig(node, options) {
37
39
  ...options,
38
40
  mode,
39
41
  parentPath: options.parentPath ?? [],
40
- normalizedPaths
42
+ normalizedPaths,
43
+ nonStringPolicy: options.nonStringPolicy ?? 'ignore',
44
+ pathSerializer: options.pathSerializer
41
45
  });
42
46
  }
43
47
 
44
- function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPath, normalizedPaths }) {
48
+ function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPath, normalizedPaths, nonStringPolicy, pathSerializer }) {
45
49
  const isArrayNode = Array.isArray(node);
46
50
  const result = isArrayNode ? [] : {};
47
51
  const cryptoOptions = algorithmOptions ?? algorithm;
@@ -49,7 +53,9 @@ function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPa
49
53
  Object.entries(node).forEach(([rawKey, value]) => {
50
54
  const segment = isArrayNode ? Number(rawKey) : rawKey;
51
55
  const targetKey = isArrayNode ? segment : rawKey;
52
- const currentPath = buildPath(parentPath, segment);
56
+ const currentPath = pathSerializer
57
+ ? pathSerializer([...parentPath, segment])
58
+ : buildPath(parentPath, segment);
53
59
 
54
60
  if (value !== null && typeof value === 'object') {
55
61
  result[targetKey] = traverseConfig(value, {
@@ -58,14 +64,22 @@ function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPa
58
64
  algorithm: cryptoOptions,
59
65
  algorithmOptions: cryptoOptions,
60
66
  parentPath: [...parentPath, segment],
61
- normalizedPaths
67
+ normalizedPaths,
68
+ nonStringPolicy,
69
+ pathSerializer
62
70
  });
63
71
  return;
64
72
  }
65
73
 
66
74
  if (typeof value !== 'string') {
67
- result[targetKey] = value;
68
- return;
75
+ if (nonStringPolicy === 'stringify') {
76
+ value = JSON.stringify(value);
77
+ } else if (nonStringPolicy === 'error') {
78
+ throw new Error(`Non-string value encountered at ${currentPath}`);
79
+ } else {
80
+ result[targetKey] = value;
81
+ return;
82
+ }
69
83
  }
70
84
 
71
85
  const shouldProcess = !normalizedPaths || normalizedPaths.has(currentPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamlock",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "author": "PAVEL TKACHEV (phoenixweiss) <mail@phoenixweiss.me>",
5
5
  "description": "Value-level encryption for YAML/JSON configuration files with CLI + Node.js APIs.",
6
6
  "license": "MIT",