sops-env 1.0.0 → 1.0.1
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 +2 -1
- package/dist/cli.mjs +42 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Print the `env` section of a [SOPS](https://github.com/getsops/sops)-encrypted f
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
12
12
|
Keep the environment variables under the `env` key of a SOPS file; other keys are ignored.
|
|
13
|
+
Name the variables with ASCII letters, digits, and `_`, and do not start a name with a digit.
|
|
13
14
|
An encrypted file `environment.sops.yaml` looks like this:
|
|
14
15
|
|
|
15
16
|
```yaml
|
|
@@ -54,5 +55,5 @@ ssh dokku@example.com config:set --encoded my-app $config
|
|
|
54
55
|
### Values
|
|
55
56
|
|
|
56
57
|
- Strings, numbers, and booleans are printed as text, such as `PORT=3000` and `DEBUG=true`.
|
|
57
|
-
- sops
|
|
58
|
+
- Quote a value to keep its exact text: sops rewrites unquoted numbers and dates, such as `1.50` to `1.5`, `0x10` to `16`, and `2024-01-01` to `2024-01-01T00:00:00Z`.
|
|
58
59
|
- `null`, lists, and maps are rejected: quote a value, such as `EMPTY: ''`, to print it as text.
|
package/dist/cli.mjs
CHANGED
|
@@ -16,11 +16,14 @@ var SopsEnvError = class extends Error {
|
|
|
16
16
|
/**
|
|
17
17
|
* Formats the `env` section of a decrypted document as `KEY=VALUE` lines.
|
|
18
18
|
*
|
|
19
|
+
* Names are written as is and must be shell identifiers, so that no name makes a line ambiguous.
|
|
19
20
|
* Without `base64`, values are written as is, without quoting, so multi-line values are rejected.
|
|
20
21
|
*/
|
|
21
22
|
function formatEnv(document, { base64 = false } = {}) {
|
|
22
23
|
const env = isRecord(document) ? document.env : void 0;
|
|
23
|
-
if (!isRecord(env)) throw new SopsEnvError("The env section is missing or is not a mapping.");
|
|
24
|
+
if (!isRecord(env)) throw new SopsEnvError("The env section is missing or is not a mapping. Put the variables under the env key of a YAML or JSON file; dotenv files are not supported.");
|
|
25
|
+
const invalidNames = Object.keys(env).filter((key) => !isValidName(key));
|
|
26
|
+
if (invalidNames.length) throw new SopsEnvError(`Unsupported variable names: ${invalidNames.map(quoteName).join(", ")}. A name must consist of ASCII letters, digits, and _, and must not start with a digit.`);
|
|
24
27
|
const entries = Object.entries(env).map(([key, value]) => [key, toEnvValue(key, value)]);
|
|
25
28
|
if (!base64) {
|
|
26
29
|
const multiline = entries.filter(([, value]) => value.includes("\n")).map(([key]) => key);
|
|
@@ -28,6 +31,24 @@ function formatEnv(document, { base64 = false } = {}) {
|
|
|
28
31
|
}
|
|
29
32
|
return entries.map(([key, value]) => `${key}=${base64 ? Buffer.from(value).toString("base64") : value}`).join("\n");
|
|
30
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Checks that a variable name is a shell identifier: ASCII letters, digits, and `_`, not starting with a digit.
|
|
36
|
+
*
|
|
37
|
+
* Shells and Dokku accept only such names, and they cannot make a `KEY=VALUE` line ambiguous,
|
|
38
|
+
* unlike names with `=`, line breaks, whitespace, or a leading `#`.
|
|
39
|
+
*/
|
|
40
|
+
function isValidName(name) {
|
|
41
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Quotes a variable name for an error message.
|
|
45
|
+
*
|
|
46
|
+
* Escapes non-space whitespace, control characters, and format characters left literal by `JSON.stringify`,
|
|
47
|
+
* as `\uXXXX` per UTF-16 code unit, so that a character beyond U+FFFF becomes a surrogate pair.
|
|
48
|
+
*/
|
|
49
|
+
function quoteName(name) {
|
|
50
|
+
return JSON.stringify(name).replace(/[^\S ]|[\p{Cc}\p{Cf}]/gu, (char) => char.split("").map((unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`).join(""));
|
|
51
|
+
}
|
|
31
52
|
function isRecord(value) {
|
|
32
53
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
54
|
}
|
|
@@ -83,22 +104,23 @@ Options:
|
|
|
83
104
|
--base64 Encode values in base64; allows multi-line values
|
|
84
105
|
-h, --help Show this help
|
|
85
106
|
`;
|
|
107
|
+
const options = {
|
|
108
|
+
base64: { type: "boolean" },
|
|
109
|
+
help: {
|
|
110
|
+
type: "boolean",
|
|
111
|
+
short: "h"
|
|
112
|
+
}
|
|
113
|
+
};
|
|
86
114
|
function main(args) {
|
|
87
115
|
let parsed;
|
|
88
116
|
try {
|
|
89
117
|
parsed = parseArgs({
|
|
90
118
|
args,
|
|
91
119
|
allowPositionals: true,
|
|
92
|
-
options
|
|
93
|
-
base64: { type: "boolean" },
|
|
94
|
-
help: {
|
|
95
|
-
type: "boolean",
|
|
96
|
-
short: "h"
|
|
97
|
-
}
|
|
98
|
-
}
|
|
120
|
+
options
|
|
99
121
|
});
|
|
100
122
|
} catch (error) {
|
|
101
|
-
throw new SopsEnvError(`${error.message}\n${usage}`);
|
|
123
|
+
throw new SopsEnvError(`${error.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION" ? `Unknown option '${findUnknownOption(args)}'.` : error.message}\n${usage}`);
|
|
102
124
|
}
|
|
103
125
|
const { values, positionals } = parsed;
|
|
104
126
|
if (values.help) {
|
|
@@ -109,6 +131,17 @@ function main(args) {
|
|
|
109
131
|
const output = formatEnv(decryptFile(positionals[0]), { base64: values.base64 });
|
|
110
132
|
if (output) process.stdout.write(`${output}\n`);
|
|
111
133
|
}
|
|
134
|
+
/** Returns the first option in `args` that the CLI does not define, as the user wrote it. */
|
|
135
|
+
function findUnknownOption(args) {
|
|
136
|
+
const { tokens } = parseArgs({
|
|
137
|
+
args,
|
|
138
|
+
allowPositionals: true,
|
|
139
|
+
options,
|
|
140
|
+
strict: false,
|
|
141
|
+
tokens: true
|
|
142
|
+
});
|
|
143
|
+
for (const token of tokens) if (token.kind === "option" && !Object.hasOwn(options, token.name)) return token.rawName;
|
|
144
|
+
}
|
|
112
145
|
try {
|
|
113
146
|
main(process.argv.slice(2));
|
|
114
147
|
} catch (error) {
|