configre 1.2.2 → 1.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 +15 -3
- package/demo/config/hostname.cjs +0 -0
- package/demo/config/index.cjs +0 -0
- package/demo/module/demo.js +2 -6
- package/index.js +6 -2
- package/merge.js +81 -0
- package/package.json +6 -4
- package/skills/configre/SKILL.md +7 -3
- package/test/index.test.js +28 -0
package/README.md
CHANGED
|
@@ -38,13 +38,25 @@ To get started with Configre, follow these steps:
|
|
|
38
38
|
|
|
39
39
|
3. **Use Configre in Your Project**
|
|
40
40
|
|
|
41
|
-
Import and use Configre to load your configurations:
|
|
41
|
+
Import and use Configre to load your configurations. The configuration path is required:
|
|
42
42
|
|
|
43
43
|
```javascript
|
|
44
|
-
const
|
|
44
|
+
const path = require("path");
|
|
45
|
+
const cfg = require("configre")(path.join(__dirname, "config"));
|
|
45
46
|
console.log(cfg.db); // Access your db configuration
|
|
46
47
|
```
|
|
47
48
|
|
|
49
|
+
Prefer an absolute path anchored to the module, as above. Avoid deriving it from `process.cwd()` (for example, `path.join(process.cwd(), "config")`), because that can point to a different location depending on where the process is started.
|
|
50
|
+
|
|
51
|
+
In ESM, anchor the path to `import.meta.dirname`:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
import Configre from "configre";
|
|
55
|
+
import { join } from "node:path";
|
|
56
|
+
|
|
57
|
+
const cfg = Configre(join(import.meta.dirname, "config"));
|
|
58
|
+
```
|
|
59
|
+
|
|
48
60
|
## 📚 Example
|
|
49
61
|
|
|
50
62
|
Imagine you have the following structure in your `config` directory:
|
|
@@ -114,4 +126,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
|
114
126
|
|
|
115
127
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
116
128
|
|
|
117
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
129
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/demo/config/hostname.cjs
CHANGED
|
File without changes
|
package/demo/config/index.cjs
CHANGED
|
File without changes
|
package/demo/module/demo.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import Configre from "../../index.js";
|
|
2
2
|
import lemonlog from "lemonlog";
|
|
3
|
-
import {
|
|
4
|
-
import { dirname, join } from "path";
|
|
3
|
+
import { join } from "node:path";
|
|
5
4
|
|
|
6
5
|
const log = lemonlog("Configre");
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = dirname(__filename);
|
|
9
6
|
|
|
10
|
-
const cfg = Configre(join(
|
|
7
|
+
const cfg = Configre(join(import.meta.dirname, "config"));
|
|
11
8
|
log.info(cfg.db);
|
|
12
|
-
|
package/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const os = require("os");
|
|
4
|
-
const obj = require("
|
|
4
|
+
const obj = require("./merge");
|
|
5
5
|
const log = require("lemonlog")("Configre");
|
|
6
6
|
|
|
7
7
|
class ConfigreClass {
|
|
8
|
-
constructor(pathOrDir
|
|
8
|
+
constructor(pathOrDir) {
|
|
9
|
+
if (typeof pathOrDir !== "string" || pathOrDir.length === 0) {
|
|
10
|
+
throw new TypeError("Configre path must be a non-empty string");
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
const dir = path.join(pathOrDir);
|
|
10
14
|
const isNested = (ConfigreClass._nesting || 0) > 0;
|
|
11
15
|
ConfigreClass._nesting = (ConfigreClass._nesting || 0) + 1;
|
package/merge.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
if (value === null || typeof value !== "object") {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
const proto = Object.getPrototypeOf(value);
|
|
6
|
+
return proto === Object.prototype || proto === null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isUnsafeKey(key) {
|
|
10
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function mergeArray(target, source) {
|
|
14
|
+
const result = target.slice();
|
|
15
|
+
|
|
16
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
17
|
+
const srcValue = source[index];
|
|
18
|
+
const objValue = result[index];
|
|
19
|
+
|
|
20
|
+
if (Array.isArray(srcValue)) {
|
|
21
|
+
result[index] = mergeArray(Array.isArray(objValue) ? objValue : [], srcValue);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (isPlainObject(srcValue)) {
|
|
26
|
+
result[index] = baseMerge(isPlainObject(objValue) ? objValue : {}, srcValue);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (srcValue !== undefined || result[index] === undefined) {
|
|
31
|
+
result[index] = srcValue;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function baseMerge(object, source) {
|
|
39
|
+
if (object === source || source === null || typeof source !== "object") {
|
|
40
|
+
return object;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const key in source) {
|
|
44
|
+
if (isUnsafeKey(key)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const srcValue = source[key];
|
|
49
|
+
const objValue = object[key];
|
|
50
|
+
|
|
51
|
+
if (Array.isArray(srcValue)) {
|
|
52
|
+
object[key] = mergeArray(Array.isArray(objValue) ? objValue : [], srcValue);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (isPlainObject(srcValue)) {
|
|
57
|
+
object[key] = baseMerge(isPlainObject(objValue) ? objValue : {}, srcValue);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (srcValue !== undefined || object[key] === undefined) {
|
|
62
|
+
object[key] = srcValue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return object;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function merge(object, ...sources) {
|
|
70
|
+
const output = (object !== null && typeof object === "object") ? object : {};
|
|
71
|
+
|
|
72
|
+
for (const source of sources) {
|
|
73
|
+
baseMerge(output, source);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return output;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
merge
|
|
81
|
+
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configre",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "🔧 Effortlessly Tailor Your Settings",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"lemonlog": "^1.
|
|
7
|
-
"lodash": "^4.17.21"
|
|
6
|
+
"lemonlog": "^1.2.2"
|
|
8
7
|
},
|
|
9
8
|
"main": "index.js",
|
|
10
9
|
"keywords": [
|
|
@@ -33,5 +32,8 @@
|
|
|
33
32
|
"bugs": {
|
|
34
33
|
"url": "https://github.com/clasen/Configre/issues"
|
|
35
34
|
},
|
|
36
|
-
"homepage": "https://github.com/clasen/Configre#readme"
|
|
35
|
+
"homepage": "https://github.com/clasen/Configre#readme",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node --test"
|
|
38
|
+
}
|
|
37
39
|
}
|
package/skills/configre/SKILL.md
CHANGED
|
@@ -63,13 +63,16 @@ module.exports = {
|
|
|
63
63
|
### Step 5: Load the configuration
|
|
64
64
|
|
|
65
65
|
```javascript
|
|
66
|
-
const
|
|
66
|
+
const path = require("path");
|
|
67
|
+
const cfg = require("configre")(path.join(__dirname, "config"));
|
|
67
68
|
|
|
68
69
|
console.log(cfg.db.host); // from default
|
|
69
70
|
console.log(cfg.db.user); // from host override
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
The path argument is required. Prefer an absolute path anchored to the module. Do not recommend paths derived from `process.cwd()`, such as `path.join(process.cwd(), "config")`: they can point somewhere else when the process is launched from a different directory.
|
|
74
|
+
|
|
75
|
+
To use a different config directory:
|
|
73
76
|
|
|
74
77
|
```javascript
|
|
75
78
|
const cfg = require("configre")(__dirname + "/settings");
|
|
@@ -96,7 +99,7 @@ Using `--config=` (instead of a positional argument) avoids conflicts with other
|
|
|
96
99
|
|
|
97
100
|
**Example 1: Basic setup**
|
|
98
101
|
User says: "Add configuration management to my Node.js project"
|
|
99
|
-
Actions: install configre, create `config/index.cjs` with project defaults, load with `require("configre")()`
|
|
102
|
+
Actions: install configre, create `config/index.cjs` with project defaults, load with `require("configre")(path.join(__dirname, "config"))`
|
|
100
103
|
Result: merged config object ready to use
|
|
101
104
|
|
|
102
105
|
**Example 2: Multi-environment**
|
|
@@ -113,4 +116,5 @@ Result: staging overrides are merged over defaults, without conflicting with oth
|
|
|
113
116
|
|
|
114
117
|
- **Deep merge**: nested objects merge recursively via lodash `_.merge`
|
|
115
118
|
- **Config files must use the `.cjs` extension** (`.js` is not accepted; works in both CommonJS and ESM projects)
|
|
119
|
+
- **Required path**: always pass the config directory or file path; prefer an absolute module-relative path over a process-relative path
|
|
116
120
|
- **Function vs constructor**: `Configre(path)` returns the merged config directly; `new Configre(path)` returns the instance (use `.get()` to retrieve config)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const test = require("node:test");
|
|
4
|
+
const Configre = require("../index");
|
|
5
|
+
|
|
6
|
+
test("requires a config path", () => {
|
|
7
|
+
assert.throws(
|
|
8
|
+
() => Configre(),
|
|
9
|
+
{
|
|
10
|
+
name: "TypeError",
|
|
11
|
+
message: "Configre path must be a non-empty string"
|
|
12
|
+
}
|
|
13
|
+
);
|
|
14
|
+
assert.throws(
|
|
15
|
+
() => new Configre(""),
|
|
16
|
+
{
|
|
17
|
+
name: "TypeError",
|
|
18
|
+
message: "Configre path must be a non-empty string"
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("loads config from an explicit module-relative path", () => {
|
|
24
|
+
const configPath = path.join(__dirname, "..", "demo", "config");
|
|
25
|
+
const config = Configre(configPath);
|
|
26
|
+
|
|
27
|
+
assert.equal(config.db.host, "localhost");
|
|
28
|
+
});
|