yamlock 0.2.0 → 0.2.3

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
@@ -47,10 +47,22 @@ YAMLOCK_KEY="super-secret" yamlock encrypt config.yaml
47
47
 
48
48
  # Decrypt values in place using explicit key/algorithm flags
49
49
  yamlock decrypt settings.json --key "super-secret" --algorithm aes-256-cbc
50
+
51
+ # Encrypt only selected fields into a new file
52
+ yamlock encrypt config.json --key "$YAMLOCK_KEY" --paths "db.password,api.token" --output config.secure.json
53
+
54
+ # Inspect CLI metadata
55
+ yamlock version
56
+ yamlock algorithms
50
57
  ```
51
58
 
52
59
  The CLI detects YAML (`.yaml`/`.yml`) and JSON extensions automatically and writes the file back in the same format.
53
60
 
61
+ Options of note:
62
+ - `--output <file>` writes the result to a separate file instead of overwriting the input.
63
+ - `--paths <path1,path2>` targets only the specified fields (dot/bracket notation like `db.password` or `users[0].token`).
64
+ - Commands `version` and `algorithms` print the installed CLI version and the list of supported ciphers respectively.
65
+
54
66
  ### Node.js API
55
67
 
56
68
  ```js
package/dist/cli/cli.js CHANGED
@@ -2,10 +2,15 @@
2
2
  import { readFileSync, writeFileSync } from 'node:fs';
3
3
  import { resolve, extname } from 'node:path';
4
4
  import { exit } from 'node:process';
5
+ import { createRequire } from 'node:module';
5
6
 
6
7
  import yaml from 'js-yaml';
7
8
 
8
9
  import { processConfig } from '../utils/config.js';
10
+ import { listSupportedAlgorithms } from '../crypto/utils.js';
11
+
12
+ const require = createRequire(import.meta.url);
13
+ const packageJson = require('../../package.json');
9
14
 
10
15
  const HELP_TEXT = `
11
16
  ░█░█░█▀█░█▄░▄█░█░░░█▀█░█▀▀░█░█░
@@ -16,12 +21,16 @@ Usage:
16
21
  yamlock <command> [options]
17
22
 
18
23
  Commands:
19
- encrypt <file> Encrypt string values in the given YAML/JSON file.
20
- decrypt <file> Decrypt string values in the given YAML/JSON file.
24
+ encrypt <file> Encrypt string values in the given YAML/JSON file.
25
+ decrypt <file> Decrypt string values in the given YAML/JSON file.
26
+ version Print the yamlock CLI version.
27
+ algorithms Print the list of supported cipher algorithms.
21
28
 
22
29
  Options:
23
30
  -k, --key <value> Encryption key (or use YAMLOCK_KEY env).
24
31
  -a, --algorithm <value> Cipher algorithm (default: aes-256-cbc).
32
+ -o, --output <file> Write the result to a different file (otherwise overwrites the input file).
33
+ -p, --paths <p1,p2> Comma-separated list of field paths to process (dot/bracket notation).
25
34
  `;
26
35
 
27
36
  function print(message) {
@@ -63,6 +72,17 @@ function writeConfigFile(filePath, format, data) {
63
72
  writeFileSync(filePath, `${serialized}\n`, 'utf8');
64
73
  }
65
74
 
75
+ function parsePaths(value) {
76
+ if (!value) {
77
+ return [];
78
+ }
79
+
80
+ return String(value)
81
+ .split(',')
82
+ .map((segment) => segment.trim())
83
+ .filter((segment) => segment.length > 0);
84
+ }
85
+
66
86
  function parseArgs(argv) {
67
87
  const args = argv.slice(2);
68
88
  const result = {
@@ -81,6 +101,12 @@ function parseArgs(argv) {
81
101
  } else if (arg === '-a' || arg === '--algorithm') {
82
102
  result.options.algorithm = next;
83
103
  i += 1;
104
+ } else if (arg === '-o' || arg === '--output') {
105
+ result.options.output = next;
106
+ i += 1;
107
+ } else if (arg === '-p' || arg === '--paths') {
108
+ result.options.paths = parsePaths(next);
109
+ i += 1;
84
110
  }
85
111
  }
86
112
 
@@ -90,7 +116,25 @@ function parseArgs(argv) {
90
116
  export async function runCli(argv = process.argv) {
91
117
  const { command, file, options } = parseArgs(argv);
92
118
 
93
- if (!command || !file) {
119
+ if (!command) {
120
+ print(HELP_TEXT.trim());
121
+ return exit(1);
122
+ }
123
+
124
+ if (command === 'version') {
125
+ print(`yamlock ${packageJson.version}`);
126
+ return exit(0);
127
+ }
128
+
129
+ if (command === 'algorithms') {
130
+ const algorithms = listSupportedAlgorithms();
131
+ print('Supported algorithms:');
132
+ algorithms.forEach((name) => print(`- ${name}`));
133
+ return exit(0);
134
+ }
135
+
136
+ if (!file) {
137
+ printError('A file path is required for this command.');
94
138
  print(HELP_TEXT.trim());
95
139
  return exit(1);
96
140
  }
@@ -110,18 +154,32 @@ export async function runCli(argv = process.argv) {
110
154
  return exit(1);
111
155
  }
112
156
 
157
+ const outputPath = options.output
158
+ ? resolve(process.cwd(), options.output)
159
+ : absolutePath;
160
+
113
161
  try {
114
162
  if (command === 'encrypt') {
115
- const result = processConfig(config.data, { mode: 'encrypt', key, algorithm: options.algorithm });
116
- writeConfigFile(absolutePath, config.format, result);
117
- print(`Encrypted values in ${file}`);
163
+ const result = processConfig(config.data, {
164
+ mode: 'encrypt',
165
+ key,
166
+ algorithm: options.algorithm,
167
+ paths: options.paths
168
+ });
169
+ writeConfigFile(outputPath, config.format, result);
170
+ print(`Encrypted values in ${outputPath === absolutePath ? file : options.output}`);
118
171
  return exit(0);
119
172
  }
120
173
 
121
174
  if (command === 'decrypt') {
122
- const result = processConfig(config.data, { mode: 'decrypt', key });
123
- writeConfigFile(absolutePath, config.format, result);
124
- print(`Decrypted values in ${file}`);
175
+ const result = processConfig(config.data, {
176
+ mode: 'decrypt',
177
+ key,
178
+ algorithm: options.algorithm,
179
+ paths: options.paths
180
+ });
181
+ writeConfigFile(outputPath, config.format, result);
182
+ print(`Decrypted values in ${outputPath === absolutePath ? file : options.output}`);
125
183
  return exit(0);
126
184
  }
127
185
 
@@ -15,18 +15,33 @@ const MODES = {
15
15
  * @param {string|Buffer} options.key
16
16
  * @param {string|object} [options.algorithm]
17
17
  * @param {object} [options.algorithmOptions]
18
+ * @param {string[]} [options.paths]
18
19
  * @param {Array<string|number>} [options.parentPath]
19
20
  * @returns {Object|Array}
20
21
  */
21
- export function processConfig(node, { mode, key, algorithm, algorithmOptions, parentPath = [] }) {
22
+ export function processConfig(node, options) {
22
23
  if (typeof node !== 'object' || node === null) {
23
24
  throw new Error('processConfig expects a non-null object or array.');
24
25
  }
25
26
 
27
+ const mode = options.mode;
26
28
  if (mode !== MODES.ENCRYPT && mode !== MODES.DECRYPT) {
27
29
  throw new Error(`Unknown processConfig mode: ${mode}`);
28
30
  }
29
31
 
32
+ const normalizedPaths = Array.isArray(options.paths) && options.paths.length > 0
33
+ ? new Set(options.paths.map((path) => String(path).trim()).filter(Boolean))
34
+ : null;
35
+
36
+ return traverseConfig(node, {
37
+ ...options,
38
+ mode,
39
+ parentPath: options.parentPath ?? [],
40
+ normalizedPaths
41
+ });
42
+ }
43
+
44
+ function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPath, normalizedPaths }) {
30
45
  const isArrayNode = Array.isArray(node);
31
46
  const result = isArrayNode ? [] : {};
32
47
  const cryptoOptions = algorithmOptions ?? algorithm;
@@ -37,12 +52,13 @@ export function processConfig(node, { mode, key, algorithm, algorithmOptions, pa
37
52
  const currentPath = buildPath(parentPath, segment);
38
53
 
39
54
  if (value !== null && typeof value === 'object') {
40
- result[targetKey] = processConfig(value, {
55
+ result[targetKey] = traverseConfig(value, {
41
56
  mode,
42
57
  key,
43
58
  algorithm: cryptoOptions,
44
59
  algorithmOptions: cryptoOptions,
45
- parentPath: [...parentPath, segment]
60
+ parentPath: [...parentPath, segment],
61
+ normalizedPaths
46
62
  });
47
63
  return;
48
64
  }
@@ -52,6 +68,12 @@ export function processConfig(node, { mode, key, algorithm, algorithmOptions, pa
52
68
  return;
53
69
  }
54
70
 
71
+ const shouldProcess = !normalizedPaths || normalizedPaths.has(currentPath);
72
+ if (!shouldProcess) {
73
+ result[targetKey] = value;
74
+ return;
75
+ }
76
+
55
77
  if (mode === MODES.ENCRYPT) {
56
78
  result[targetKey] = encryptValue(value, key, currentPath, cryptoOptions);
57
79
  } else {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yamlock",
3
- "version": "0.2.0",
4
- "author": "PAVEL TKACHEV",
3
+ "version": "0.2.3",
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",
7
7
  "type": "module",