just-bash-util 0.1.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/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/chunk-DAO5RF73.js +153 -0
- package/dist/chunk-DLIJNNDI.js +221 -0
- package/dist/chunk-OLDCCFR6.js +793 -0
- package/dist/command/index.d.ts +304 -0
- package/dist/command/index.js +22 -0
- package/dist/config/index.d.ts +126 -0
- package/dist/config/index.js +11 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +56 -0
- package/dist/path/index.d.ts +39 -0
- package/dist/path/index.js +28 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 blindmansion
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# just-bash-util
|
|
2
|
+
|
|
3
|
+
CLI command framework, config file discovery, and path utilities for [just-bash](https://www.npmjs.com/package/just-bash).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add just-bash-util just-bash
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Modules
|
|
12
|
+
|
|
13
|
+
### `just-bash-util/command` — CLI framework
|
|
14
|
+
|
|
15
|
+
Type-safe command trees with fluent builders, inherited options, auto-generated help, and typo suggestions.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Bash } from "just-bash";
|
|
19
|
+
import { command, o, f, a } from "just-bash-util/command";
|
|
20
|
+
|
|
21
|
+
const cli = command("mycli", {
|
|
22
|
+
description: "My CLI tool",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const serve = cli.command("serve", {
|
|
26
|
+
description: "Start the dev server",
|
|
27
|
+
options: {
|
|
28
|
+
port: o.number().default(3000).short("p").describe("Port to listen on"),
|
|
29
|
+
host: o.string().describe("Host to bind to"),
|
|
30
|
+
open: f().short("o").describe("Open browser"),
|
|
31
|
+
},
|
|
32
|
+
args: [a.string().name("entry").describe("Entry file")],
|
|
33
|
+
handler: (args, ctx) => {
|
|
34
|
+
// args is fully typed: { port: number; host: string | undefined; open: boolean; entry: string }
|
|
35
|
+
return { stdout: `Listening on :${args.port}`, stderr: "", exitCode: 0 };
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const bash = new Bash({ customCommands: [cli.toCommand()] });
|
|
40
|
+
await bash.exec("mycli serve app.ts -p 8080");
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Commands can also be executed directly without just-bash:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import type { Infer } from "just-bash-util/command";
|
|
47
|
+
|
|
48
|
+
// Extract handler args type externally (like z.infer)
|
|
49
|
+
type ServeArgs = Infer<typeof serve>;
|
|
50
|
+
|
|
51
|
+
// Execute from CLI tokens
|
|
52
|
+
await cli.execute(["serve", "app.ts", "-p", "8080"], ctx);
|
|
53
|
+
|
|
54
|
+
// Or invoke programmatically with typed args
|
|
55
|
+
await serve.invoke({ port: 8080, entry: "app.ts" }, ctx);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Features:**
|
|
59
|
+
|
|
60
|
+
- Subcommand nesting with automatic option inheritance
|
|
61
|
+
- `omitInherited` to exclude parent options from specific subcommands
|
|
62
|
+
- `--help` / `-h` auto-generated at every level
|
|
63
|
+
- `--no-<flag>` negation, `-abc` combined short flags, `--key=value` syntax
|
|
64
|
+
- `--` passthrough separator
|
|
65
|
+
- Environment variable fallbacks for options
|
|
66
|
+
- Levenshtein-based "did you mean?" suggestions for typos
|
|
67
|
+
|
|
68
|
+
### `just-bash-util/config` — Config file discovery
|
|
69
|
+
|
|
70
|
+
Cosmiconfig-style config search that walks up the directory tree, trying conventional filenames at each level. Comments and trailing commas are supported out of the box.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { searchConfig } from "just-bash-util/config";
|
|
74
|
+
|
|
75
|
+
// Walks up from cwd trying: package.json#myapp, .myapprc, .myapprc.json, myapp.config.json
|
|
76
|
+
const result = await searchConfig(ctx, { name: "myapp" });
|
|
77
|
+
if (result) {
|
|
78
|
+
result.config; // parsed config object
|
|
79
|
+
result.filepath; // absolute path to the file that matched
|
|
80
|
+
result.isEmpty; // true if config is null/undefined/empty object
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Customize search places, starting directory, or add custom loaders
|
|
84
|
+
const result2 = await searchConfig(ctx, {
|
|
85
|
+
name: "myapp",
|
|
86
|
+
from: "/specific/start/dir",
|
|
87
|
+
searchPlaces: [".myapprc.json", "myapp.config.json"],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Layered / cascading configs** — pass `merge: true` to collect configs from every directory level and deep-merge them (closest wins):
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const result = await searchConfig(ctx, { name: "myapp", merge: true });
|
|
95
|
+
// e.g. /project/.myapprc.json → { indent: 2, rules: { semi: "error" } }
|
|
96
|
+
// /.myapprc.json → { indent: 4, color: true, rules: { semi: "warn" } }
|
|
97
|
+
// result.config → { indent: 2, color: true, rules: { semi: "error" } }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use `stopWhen` for ESLint-style `root: true` cascading stops:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const result = await searchConfig(ctx, {
|
|
104
|
+
name: "myapp",
|
|
105
|
+
merge: true,
|
|
106
|
+
stopWhen: (cfg) => cfg.root === true,
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Also exports `loadConfig` for loading a known file path directly (e.g. when the user passes `--config ./path`), and `findUp` for locating files by name up the directory tree.
|
|
111
|
+
|
|
112
|
+
### `just-bash-util/path` — Path utilities
|
|
113
|
+
|
|
114
|
+
Pure POSIX path operations with no Node.js dependency.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import {
|
|
118
|
+
join,
|
|
119
|
+
resolve,
|
|
120
|
+
dirname,
|
|
121
|
+
basename,
|
|
122
|
+
extname,
|
|
123
|
+
relative,
|
|
124
|
+
parse,
|
|
125
|
+
normalize,
|
|
126
|
+
} from "just-bash-util/path";
|
|
127
|
+
|
|
128
|
+
join("src", "utils", "index.ts"); // "src/utils/index.ts"
|
|
129
|
+
dirname("/project/src/index.ts"); // "/project/src"
|
|
130
|
+
basename("src/index.ts", ".ts"); // "index"
|
|
131
|
+
relative("/a/b/c", "/a/d"); // "../../d"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Peer dependencies
|
|
135
|
+
|
|
136
|
+
Requires [`just-bash`](https://www.npmjs.com/package/just-bash) ^2.9.6 — provides the `CommandContext` and `ExecResult` types used throughout.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// src/path/index.ts
|
|
2
|
+
var sep = "/";
|
|
3
|
+
var delimiter = ":";
|
|
4
|
+
function isAbsolute(path) {
|
|
5
|
+
return path.charCodeAt(0) === 47;
|
|
6
|
+
}
|
|
7
|
+
function normalize(path) {
|
|
8
|
+
if (path === "") return ".";
|
|
9
|
+
if (path === "/") return "/";
|
|
10
|
+
const isAbs = path.charCodeAt(0) === 47;
|
|
11
|
+
const trailingSlash = path.charCodeAt(path.length - 1) === 47;
|
|
12
|
+
const segments = path.split("/");
|
|
13
|
+
const result = [];
|
|
14
|
+
for (const seg of segments) {
|
|
15
|
+
if (seg === "" || seg === ".") continue;
|
|
16
|
+
if (seg === "..") {
|
|
17
|
+
if (isAbs) {
|
|
18
|
+
result.pop();
|
|
19
|
+
} else if (result.length > 0 && result[result.length - 1] !== "..") {
|
|
20
|
+
result.pop();
|
|
21
|
+
} else {
|
|
22
|
+
result.push("..");
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
result.push(seg);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
let out = result.join("/");
|
|
29
|
+
if (isAbs) {
|
|
30
|
+
out = "/" + out;
|
|
31
|
+
}
|
|
32
|
+
if (trailingSlash && out.length > 1 && !out.endsWith("/")) {
|
|
33
|
+
out += "/";
|
|
34
|
+
}
|
|
35
|
+
return out || (isAbs ? "/" : trailingSlash ? "./" : ".");
|
|
36
|
+
}
|
|
37
|
+
function join(...paths) {
|
|
38
|
+
if (paths.length === 0) return ".";
|
|
39
|
+
const joined = paths.filter((p) => p !== "").join("/");
|
|
40
|
+
if (joined === "") return ".";
|
|
41
|
+
return normalize(joined);
|
|
42
|
+
}
|
|
43
|
+
function resolve(...paths) {
|
|
44
|
+
let resolved = "";
|
|
45
|
+
for (let i = paths.length - 1; i >= 0; i--) {
|
|
46
|
+
const p = paths[i];
|
|
47
|
+
if (!p) continue;
|
|
48
|
+
resolved = resolved ? `${p}/${resolved}` : p;
|
|
49
|
+
if (p.charCodeAt(0) === 47) break;
|
|
50
|
+
}
|
|
51
|
+
return normalize(resolved || ".");
|
|
52
|
+
}
|
|
53
|
+
function dirname(path) {
|
|
54
|
+
if (path === "") return ".";
|
|
55
|
+
if (path === "/") return "/";
|
|
56
|
+
let end = path.length;
|
|
57
|
+
while (end > 1 && path.charCodeAt(end - 1) === 47) end--;
|
|
58
|
+
const trimmed = path.slice(0, end);
|
|
59
|
+
const i = trimmed.lastIndexOf("/");
|
|
60
|
+
if (i === -1) return ".";
|
|
61
|
+
if (i === 0) return "/";
|
|
62
|
+
return trimmed.slice(0, i);
|
|
63
|
+
}
|
|
64
|
+
function basename(path, ext) {
|
|
65
|
+
if (path === "") return "";
|
|
66
|
+
let end = path.length;
|
|
67
|
+
while (end > 1 && path.charCodeAt(end - 1) === 47) end--;
|
|
68
|
+
const trimmed = path.slice(0, end);
|
|
69
|
+
if (trimmed === "/") return "";
|
|
70
|
+
const i = trimmed.lastIndexOf("/");
|
|
71
|
+
const base = i === -1 ? trimmed : trimmed.slice(i + 1);
|
|
72
|
+
if (ext && base.endsWith(ext) && base.length > ext.length) {
|
|
73
|
+
return base.slice(0, base.length - ext.length);
|
|
74
|
+
}
|
|
75
|
+
return base;
|
|
76
|
+
}
|
|
77
|
+
function extname(path) {
|
|
78
|
+
const base = basename(path);
|
|
79
|
+
if (base === "" || base === "." || base === "..") return "";
|
|
80
|
+
const i = base.lastIndexOf(".");
|
|
81
|
+
if (i <= 0) return "";
|
|
82
|
+
return base.slice(i);
|
|
83
|
+
}
|
|
84
|
+
function parse(path) {
|
|
85
|
+
if (path === "") return { root: "", dir: "", base: "", name: "", ext: "" };
|
|
86
|
+
const root = isAbsolute(path) ? "/" : "";
|
|
87
|
+
let end = path.length;
|
|
88
|
+
while (end > 1 && path.charCodeAt(end - 1) === 47) end--;
|
|
89
|
+
const p = path.slice(0, end);
|
|
90
|
+
const lastSlash = p.lastIndexOf("/");
|
|
91
|
+
let dir;
|
|
92
|
+
let base;
|
|
93
|
+
if (lastSlash === -1) {
|
|
94
|
+
dir = "";
|
|
95
|
+
base = p;
|
|
96
|
+
} else if (lastSlash === 0) {
|
|
97
|
+
dir = "/";
|
|
98
|
+
base = p.slice(1);
|
|
99
|
+
} else {
|
|
100
|
+
dir = p.slice(0, lastSlash);
|
|
101
|
+
base = p.slice(lastSlash + 1);
|
|
102
|
+
}
|
|
103
|
+
const ext = extname(base);
|
|
104
|
+
const name = ext ? base.slice(0, base.length - ext.length) : base;
|
|
105
|
+
return { root, dir, base, name, ext };
|
|
106
|
+
}
|
|
107
|
+
function format(pathObject) {
|
|
108
|
+
const { root = "", dir, base, name, ext } = pathObject;
|
|
109
|
+
const resolvedBase = base || `${name || ""}${ext || ""}`;
|
|
110
|
+
if (dir) {
|
|
111
|
+
if (dir === root) {
|
|
112
|
+
return `${dir}${resolvedBase}`;
|
|
113
|
+
}
|
|
114
|
+
return `${dir}/${resolvedBase}`;
|
|
115
|
+
}
|
|
116
|
+
return `${root}${resolvedBase}`;
|
|
117
|
+
}
|
|
118
|
+
function relative(from, to) {
|
|
119
|
+
const fromNorm = normalize(from);
|
|
120
|
+
const toNorm = normalize(to);
|
|
121
|
+
if (fromNorm === toNorm) return "";
|
|
122
|
+
const fromParts = fromNorm === "/" ? [""] : fromNorm.split("/");
|
|
123
|
+
const toParts = toNorm === "/" ? [""] : toNorm.split("/");
|
|
124
|
+
const fromAbs = fromNorm.charCodeAt(0) === 47;
|
|
125
|
+
const toAbs = toNorm.charCodeAt(0) === 47;
|
|
126
|
+
const startIdx = fromAbs && toAbs ? 1 : 0;
|
|
127
|
+
let common = startIdx;
|
|
128
|
+
const minLen = Math.min(fromParts.length, toParts.length);
|
|
129
|
+
while (common < minLen && fromParts[common] === toParts[common]) {
|
|
130
|
+
common++;
|
|
131
|
+
}
|
|
132
|
+
const ups = fromParts.length - common;
|
|
133
|
+
const rest = toParts.slice(common);
|
|
134
|
+
const parts = [];
|
|
135
|
+
for (let i = 0; i < ups; i++) parts.push("..");
|
|
136
|
+
for (const r of rest) parts.push(r);
|
|
137
|
+
return parts.join("/") || ".";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
sep,
|
|
142
|
+
delimiter,
|
|
143
|
+
isAbsolute,
|
|
144
|
+
normalize,
|
|
145
|
+
join,
|
|
146
|
+
resolve,
|
|
147
|
+
dirname,
|
|
148
|
+
basename,
|
|
149
|
+
extname,
|
|
150
|
+
parse,
|
|
151
|
+
format,
|
|
152
|
+
relative
|
|
153
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
basename,
|
|
3
|
+
dirname,
|
|
4
|
+
extname,
|
|
5
|
+
join
|
|
6
|
+
} from "./chunk-DAO5RF73.js";
|
|
7
|
+
|
|
8
|
+
// src/config/find-up.ts
|
|
9
|
+
async function findUp(ctx, name, options) {
|
|
10
|
+
const names = Array.isArray(name) ? name : [name];
|
|
11
|
+
const from = options?.from ?? ctx.cwd;
|
|
12
|
+
const stopAt = options?.stopAt ?? "/";
|
|
13
|
+
let dir = from;
|
|
14
|
+
while (true) {
|
|
15
|
+
for (const n of names) {
|
|
16
|
+
const filepath = join(dir, n);
|
|
17
|
+
if (await ctx.fs.exists(filepath)) {
|
|
18
|
+
return filepath;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (dir === stopAt) break;
|
|
22
|
+
const parent = dirname(dir);
|
|
23
|
+
if (parent === dir) break;
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/config/jsonc.ts
|
|
30
|
+
function stripJsonComments(input) {
|
|
31
|
+
let result = "";
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < input.length) {
|
|
34
|
+
const ch = input[i];
|
|
35
|
+
if (ch === '"') {
|
|
36
|
+
let str = '"';
|
|
37
|
+
i++;
|
|
38
|
+
while (i < input.length) {
|
|
39
|
+
const c = input[i];
|
|
40
|
+
str += c;
|
|
41
|
+
if (c === "\\" && i + 1 < input.length) {
|
|
42
|
+
str += input[i + 1];
|
|
43
|
+
i += 2;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
i++;
|
|
47
|
+
if (c === '"') break;
|
|
48
|
+
}
|
|
49
|
+
result += str;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (ch === "/" && input[i + 1] === "/") {
|
|
53
|
+
i += 2;
|
|
54
|
+
while (i < input.length && input[i] !== "\n") i++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === "/" && input[i + 1] === "*") {
|
|
58
|
+
i += 2;
|
|
59
|
+
while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
|
|
60
|
+
i += 2;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
result += ch;
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function parseJsonc(input) {
|
|
69
|
+
const withoutComments = stripJsonComments(input);
|
|
70
|
+
const withoutTrailingCommas = withoutComments.replace(/,\s*([}\]])/g, "$1");
|
|
71
|
+
return JSON.parse(withoutTrailingCommas);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/config/explorer.ts
|
|
75
|
+
function isPlainObject(value) {
|
|
76
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
77
|
+
}
|
|
78
|
+
function deepMerge(base, override) {
|
|
79
|
+
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
80
|
+
return override;
|
|
81
|
+
}
|
|
82
|
+
const result = { ...base };
|
|
83
|
+
for (const key of Object.keys(override)) {
|
|
84
|
+
result[key] = key in result ? deepMerge(result[key], override[key]) : override[key];
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
var defaultLoader = (content) => parseJsonc(content);
|
|
89
|
+
function defaultSearchPlaces(name) {
|
|
90
|
+
return [
|
|
91
|
+
"package.json",
|
|
92
|
+
`.${name}rc`,
|
|
93
|
+
`.${name}rc.json`,
|
|
94
|
+
`${name}.config.json`
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
var builtinLoaders = {
|
|
98
|
+
".json": defaultLoader,
|
|
99
|
+
"noExt": defaultLoader
|
|
100
|
+
};
|
|
101
|
+
function resolveLoader(filepath, customLoaders) {
|
|
102
|
+
const ext = extname(filepath);
|
|
103
|
+
const key = ext || "noExt";
|
|
104
|
+
if (customLoaders?.[key]) return customLoaders[key];
|
|
105
|
+
if (builtinLoaders[key]) return builtinLoaders[key];
|
|
106
|
+
return defaultLoader;
|
|
107
|
+
}
|
|
108
|
+
function checkEmpty(value) {
|
|
109
|
+
if (value == null) return true;
|
|
110
|
+
if (typeof value === "object" && Object.keys(value).length === 0) return true;
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
async function loadFileInternal(fs, filepath, customLoaders, packageJsonProp) {
|
|
114
|
+
const content = await fs.readFile(filepath);
|
|
115
|
+
const loader = resolveLoader(filepath, customLoaders);
|
|
116
|
+
let config = loader(content, filepath);
|
|
117
|
+
if (basename(filepath) === "package.json" && packageJsonProp !== false) {
|
|
118
|
+
if (config != null && typeof config === "object" && packageJsonProp in config) {
|
|
119
|
+
config = config[packageJsonProp];
|
|
120
|
+
} else {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
config,
|
|
126
|
+
filepath,
|
|
127
|
+
isEmpty: checkEmpty(config)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async function collectAll(ctx, options) {
|
|
131
|
+
const {
|
|
132
|
+
name,
|
|
133
|
+
from = ctx.cwd,
|
|
134
|
+
searchPlaces = defaultSearchPlaces(name),
|
|
135
|
+
loaders: customLoaders,
|
|
136
|
+
packageJsonProp = name,
|
|
137
|
+
stopAt = "/",
|
|
138
|
+
stopWhen
|
|
139
|
+
} = options;
|
|
140
|
+
const results = [];
|
|
141
|
+
let dir = from;
|
|
142
|
+
while (true) {
|
|
143
|
+
for (const place of searchPlaces) {
|
|
144
|
+
const filepath = join(dir, place);
|
|
145
|
+
if (!await ctx.fs.exists(filepath)) continue;
|
|
146
|
+
try {
|
|
147
|
+
const result = await loadFileInternal(ctx.fs, filepath, customLoaders, packageJsonProp);
|
|
148
|
+
if (result !== null) {
|
|
149
|
+
results.push(result);
|
|
150
|
+
if (stopWhen?.(result.config)) return results;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (dir === stopAt) break;
|
|
158
|
+
const parent = dirname(dir);
|
|
159
|
+
if (parent === dir) break;
|
|
160
|
+
dir = parent;
|
|
161
|
+
}
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
function mergeResults(results) {
|
|
165
|
+
if (results.length === 0) return null;
|
|
166
|
+
if (results.length === 1) return results[0];
|
|
167
|
+
const merged = results.reduceRight(
|
|
168
|
+
(acc, r) => deepMerge(acc, r.config),
|
|
169
|
+
{}
|
|
170
|
+
);
|
|
171
|
+
return {
|
|
172
|
+
config: merged,
|
|
173
|
+
filepath: results[0].filepath,
|
|
174
|
+
isEmpty: checkEmpty(merged)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function searchConfig(ctx, options) {
|
|
178
|
+
if (options.merge) {
|
|
179
|
+
const results = await collectAll(ctx, options);
|
|
180
|
+
return mergeResults(results);
|
|
181
|
+
}
|
|
182
|
+
const {
|
|
183
|
+
name,
|
|
184
|
+
from = ctx.cwd,
|
|
185
|
+
searchPlaces = defaultSearchPlaces(name),
|
|
186
|
+
loaders: customLoaders,
|
|
187
|
+
packageJsonProp = name,
|
|
188
|
+
stopAt = "/"
|
|
189
|
+
} = options;
|
|
190
|
+
let dir = from;
|
|
191
|
+
while (true) {
|
|
192
|
+
for (const place of searchPlaces) {
|
|
193
|
+
const filepath = join(dir, place);
|
|
194
|
+
if (!await ctx.fs.exists(filepath)) continue;
|
|
195
|
+
try {
|
|
196
|
+
const result = await loadFileInternal(ctx.fs, filepath, customLoaders, packageJsonProp);
|
|
197
|
+
if (result !== null) return result;
|
|
198
|
+
} catch {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (dir === stopAt) break;
|
|
203
|
+
const parent = dirname(dir);
|
|
204
|
+
if (parent === dir) break;
|
|
205
|
+
dir = parent;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
async function loadConfig(ctx, filepath, options) {
|
|
210
|
+
const {
|
|
211
|
+
loaders: customLoaders,
|
|
212
|
+
packageJsonProp = false
|
|
213
|
+
} = options ?? {};
|
|
214
|
+
return loadFileInternal(ctx.fs, filepath, customLoaders, packageJsonProp);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export {
|
|
218
|
+
findUp,
|
|
219
|
+
searchConfig,
|
|
220
|
+
loadConfig
|
|
221
|
+
};
|