Skip to content

Commit 05434a8

Browse files
committed
feat: introduce comprehensive util module including text styling, inheritance, error handling, and environment variable stringification.
1 parent 05d6b9b commit 05434a8

File tree

3 files changed

+116
-8
lines changed

3 files changed

+116
-8
lines changed

lib/internal/util/stringify_env.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const {
4+
ArrayPrototypePush,
5+
ObjectEntries,
6+
RegExpPrototypeTest,
7+
String,
8+
StringPrototypeReplace,
9+
} = primordials;
10+
11+
const {
12+
validateObject,
13+
} = require('internal/validators');
14+
15+
const kEnvNeedQuoteRegExp = /[\s"'\n#=]/;
16+
const kEnvEscapeRegExp = /(["\\])/g;
17+
const kEnvNewlinesRegExp = /\n/g;
18+
const kEnvReturnRegExp = /\r/g;
19+
20+
function stringifyEnv(env) {
21+
validateObject(env, 'env');
22+
23+
const lines = [];
24+
const entries = ObjectEntries(env);
25+
26+
for (let i = 0; i < entries.length; i++) {
27+
const { 0: key, 1: value } = entries[i];
28+
const strValue = String(value);
29+
30+
// If the value contains characters that need quoting in .env files
31+
// (space, newline, quote, etc.), quoting is safer.
32+
// For simplicity and safety, we quote if it has spaces, quotes, newlines,
33+
// or starts with # (comment).
34+
if (strValue === '' || RegExpPrototypeTest(kEnvNeedQuoteRegExp, strValue)) {
35+
// Escape existing double quotes and newlines
36+
const escaped = StringPrototypeReplace(strValue, kEnvEscapeRegExp, '\\$1')
37+
.replace(kEnvNewlinesRegExp, '\\n')
38+
.replace(kEnvReturnRegExp, '\\r');
39+
ArrayPrototypePush(lines, `${key}="${escaped}"`);
40+
} else {
41+
ArrayPrototypePush(lines, `${key}=${strValue}`);
42+
}
43+
}
44+
45+
return lines.join('\n');
46+
}
47+
48+
module.exports = {
49+
stringifyEnv,
50+
};

lib/util.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ function inherits(ctor, superCtor) {
226226

227227
if (superCtor.prototype === undefined) {
228228
throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',
229-
'Object', superCtor.prototype);
229+
'Object', superCtor.prototype);
230230
}
231231
ObjectDefineProperty(ctor, 'super_', {
232232
__proto__: null,
@@ -288,7 +288,7 @@ function callbackify(original) {
288288
// implications (stack, `uncaughtException`, `async_hooks`)
289289
ReflectApply(original, this, args)
290290
.then((ret) => process.nextTick(cb, null, ret),
291-
(rej) => process.nextTick(callbackifyOnRejected, rej, cb));
291+
(rej) => process.nextTick(callbackifyOnRejected, rej, cb));
292292
}
293293

294294
const descriptors = ObjectGetOwnPropertyDescriptors(original);
@@ -302,8 +302,8 @@ function callbackify(original) {
302302
}
303303
const propertiesValues = ObjectValues(descriptors);
304304
for (let i = 0; i < propertiesValues.length; i++) {
305-
// We want to use null-prototype objects to not rely on globally mutable
306-
// %Object.prototype%.
305+
// We want to use null-prototype objects to not rely on globally mutable
306+
// %Object.prototype%.
307307
ObjectSetPrototypeOf(propertiesValues[i], null);
308308
}
309309
ObjectDefineProperties(callbackified, descriptors);
@@ -470,8 +470,8 @@ module.exports = {
470470
_errnoException,
471471
_exceptionWithHostPort,
472472
_extend: internalDeprecate(_extend,
473-
'The `util._extend` API is deprecated. Please use Object.assign() instead.',
474-
'DEP0060'),
473+
'The `util._extend` API is deprecated. Please use Object.assign() instead.',
474+
'DEP0060'),
475475
callbackify,
476476
convertProcessSignalToExitCode,
477477
debug: debuglog,
@@ -487,8 +487,8 @@ module.exports = {
487487
inherits,
488488
inspect,
489489
isArray: internalDeprecate(ArrayIsArray,
490-
'The `util.isArray` API is deprecated. Please use `Array.isArray()` instead.',
491-
'DEP0044'),
490+
'The `util.isArray` API is deprecated. Please use `Array.isArray()` instead.',
491+
'DEP0044'),
492492
isDeepStrictEqual(a, b, skipPrototype) {
493493
if (internalDeepEqual === undefined) {
494494
internalDeepEqual = require('internal/util/comparisons').isDeepStrictEqual;
@@ -542,3 +542,9 @@ defineLazyProperties(
542542
'internal/util/trace_sigint',
543543
['setTraceSigInt'],
544544
);
545+
546+
defineLazyProperties(
547+
module.exports,
548+
'internal/util/stringify_env',
549+
['stringifyEnv'],
550+
);

test/parallel/test-util-env.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const util = require('util');
6+
7+
// Test util.stringifyEnv
8+
{
9+
const simple = { A: '1', B: '2' };
10+
assert.strictEqual(util.stringifyEnv(simple), 'A=1\nB=2');
11+
}
12+
13+
{
14+
const quotes = { A: '1 "2" 3' };
15+
assert.strictEqual(util.stringifyEnv(quotes), 'A="1 \\"2\\" 3"');
16+
}
17+
18+
{
19+
const newlines = { A: '1\n2' };
20+
assert.strictEqual(util.stringifyEnv(newlines), 'A="1\\n2"');
21+
}
22+
23+
{
24+
const empty = {};
25+
assert.strictEqual(util.stringifyEnv(empty), '');
26+
}
27+
28+
{
29+
const complex = {
30+
A: 'val_a',
31+
B: 'val_b',
32+
C: 'val with spaces',
33+
D: 'val_with_"quotes"',
34+
E: 'val_with_\n_newlines'
35+
};
36+
const expected = 'A=val_a\n' +
37+
'B=val_b\n' +
38+
'C="val with spaces"\n' +
39+
'D="val_with_\\"quotes\\""\n' +
40+
'E="val_with_\\n_newlines"';
41+
assert.strictEqual(util.stringifyEnv(complex), expected);
42+
}
43+
44+
// Test validation
45+
{
46+
assert.throws(() => util.stringifyEnv(null), {
47+
code: 'ERR_INVALID_ARG_TYPE',
48+
});
49+
assert.throws(() => util.stringifyEnv('string'), {
50+
code: 'ERR_INVALID_ARG_TYPE',
51+
});
52+
}

0 commit comments

Comments
 (0)