typescript-bedrock 0.1.3 → 0.1.5
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/files/base/.markdownlint.jsonc +4 -0
- package/markdownlint.jsonc +4 -0
- package/oxlintrc.json +8 -0
- package/package.json +1 -1
- package/src/adopt/adopt.ts +4 -58
- package/src/adopt/copies.ts +92 -0
- package/src/adopt/ignore-lists.ts +41 -0
- package/src/adopt/tests.ts +10 -2
- package/src/files.ts +15 -3
- package/src/guard.ts +24 -14
- package/src/ignores.ts +55 -0
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
"default": true,
|
|
3
3
|
// Lines wrap where the writer breaks them; prose is not reflowed.
|
|
4
4
|
"line-length": false,
|
|
5
|
+
// A changelog repeats Added, Changed, and Fixed under each version.
|
|
6
|
+
"no-duplicate-heading": {
|
|
7
|
+
"siblings_only": true,
|
|
8
|
+
},
|
|
5
9
|
"table-column-style": {
|
|
6
10
|
"style": "compact",
|
|
7
11
|
},
|
package/markdownlint.jsonc
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
"default": true,
|
|
3
3
|
// Lines wrap where the writer breaks them; prose is not reflowed.
|
|
4
4
|
"line-length": false,
|
|
5
|
+
// A changelog repeats Added, Changed, and Fixed under each version.
|
|
6
|
+
"no-duplicate-heading": {
|
|
7
|
+
"siblings_only": true,
|
|
8
|
+
},
|
|
5
9
|
"table-column-style": {
|
|
6
10
|
"style": "compact",
|
|
7
11
|
},
|
package/oxlintrc.json
CHANGED
|
@@ -345,6 +345,14 @@
|
|
|
345
345
|
"import/no-default-export": "off"
|
|
346
346
|
}
|
|
347
347
|
},
|
|
348
|
+
{
|
|
349
|
+
"files": ["**/hooks/**"],
|
|
350
|
+
"rules": {
|
|
351
|
+
"eslint/func-style": "off",
|
|
352
|
+
"promise/prefer-await-to-then": "off",
|
|
353
|
+
"promise/no-callback-in-promise": "off"
|
|
354
|
+
}
|
|
355
|
+
},
|
|
348
356
|
{
|
|
349
357
|
"files": ["**/*.d.{ts,mts,cts}"],
|
|
350
358
|
"rules": {
|
package/package.json
CHANGED
package/src/adopt/adopt.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { realpath } from "node:fs/promises";
|
|
2
2
|
import { basename, join } from "node:path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { asRecord,
|
|
4
|
+
import { copies, existing, planBunfig } from "./copies.ts";
|
|
5
|
+
import { asRecord, type Json, readJsonc, toJson } from "./json.ts";
|
|
6
6
|
import { migrateManifest } from "./manifest.ts";
|
|
7
7
|
import { sharedSteps } from "./shared.ts";
|
|
8
8
|
import { pending, type Step } from "./steps.ts";
|
|
@@ -100,7 +100,8 @@ export const BASE_FILES = [
|
|
|
100
100
|
".markdownlint.jsonc",
|
|
101
101
|
"justfile",
|
|
102
102
|
];
|
|
103
|
-
/** Thin files that point at typescript-bedrock. A changed copy is backed up, then replaced
|
|
103
|
+
/** Thin files that point at typescript-bedrock. A changed copy is backed up, then replaced,
|
|
104
|
+
* except the entries a repo adds to the ignore list of `.oxlintrc.json` or `.markdownlint-cli2.jsonc`, which stay. */
|
|
104
105
|
const TOOL_FILES = [
|
|
105
106
|
".bun-version",
|
|
106
107
|
".github/workflows/typescript.yml",
|
|
@@ -114,46 +115,6 @@ const STARTER_FILES = [".vscode/extensions.json", ".vscode/settings.json"];
|
|
|
114
115
|
const OLD_CI = ".github/workflows/ci.yml";
|
|
115
116
|
const OLD_CI_COMMAND = /\b(?:npm|npx|pnpm|yarn)\b|bun run verify/u;
|
|
116
117
|
|
|
117
|
-
const existing = async (
|
|
118
|
-
root: string,
|
|
119
|
-
paths: readonly string[],
|
|
120
|
-
): Promise<string[]> => {
|
|
121
|
-
const found = await Promise.all(
|
|
122
|
-
paths.map((path) => exists(join(root, path))),
|
|
123
|
-
);
|
|
124
|
-
return paths.filter((_, index) => found[index] === true);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/** npm packs drop `bunfig.toml`, and linters read nested configs, so these keep a suffix. */
|
|
128
|
-
const STORED_WITH_SUFFIX = new Set([
|
|
129
|
-
".markdownlint-cli2.jsonc",
|
|
130
|
-
".oxlintrc.json",
|
|
131
|
-
"bunfig.toml",
|
|
132
|
-
]);
|
|
133
|
-
const storedPath = (path: string): string =>
|
|
134
|
-
STORED_WITH_SUFFIX.has(path) ? `${path}.template` : path;
|
|
135
|
-
|
|
136
|
-
const copyFile = async (dir: string, path: string): Promise<Step> => ({
|
|
137
|
-
kind: "write",
|
|
138
|
-
path,
|
|
139
|
-
content: await Bun.file(join(dir, storedPath(path))).text(),
|
|
140
|
-
executable: path.startsWith(".githooks/"),
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const copies = async (
|
|
144
|
-
dir: string,
|
|
145
|
-
target: string,
|
|
146
|
-
paths: readonly string[],
|
|
147
|
-
overwrite: boolean,
|
|
148
|
-
): Promise<Step[]> => {
|
|
149
|
-
const present = new Set(overwrite ? [] : await existing(target, paths));
|
|
150
|
-
return Promise.all(
|
|
151
|
-
paths
|
|
152
|
-
.filter((path) => !present.has(path))
|
|
153
|
-
.map((path) => copyFile(dir, path)),
|
|
154
|
-
);
|
|
155
|
-
};
|
|
156
|
-
|
|
157
118
|
const planManifest = async (
|
|
158
119
|
packageRoot: string,
|
|
159
120
|
target: string,
|
|
@@ -176,21 +137,6 @@ const planManifest = async (
|
|
|
176
137
|
);
|
|
177
138
|
};
|
|
178
139
|
|
|
179
|
-
const planBunfig = async (
|
|
180
|
-
filesRoot: string,
|
|
181
|
-
target: string,
|
|
182
|
-
): Promise<{ steps: Step[]; notes: string[] }> => {
|
|
183
|
-
const file = Bun.file(join(target, "bunfig.toml"));
|
|
184
|
-
const merged = mergeBunfig(
|
|
185
|
-
await Bun.file(join(filesRoot, storedPath("bunfig.toml"))).text(),
|
|
186
|
-
(await file.exists()) ? await file.text() : undefined,
|
|
187
|
-
);
|
|
188
|
-
return {
|
|
189
|
-
steps: [{ kind: "write", path: "bunfig.toml", content: merged.content }],
|
|
190
|
-
notes: merged.notes,
|
|
191
|
-
};
|
|
192
|
-
};
|
|
193
|
-
|
|
194
140
|
const planTsconfig = async (
|
|
195
141
|
packageRoot: string,
|
|
196
142
|
target: string,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { mergeBunfig } from "./bunfig.ts";
|
|
4
|
+
import { IGNORE_KEYS, mergeIgnoreList } from "./ignore-lists.ts";
|
|
5
|
+
import { exists, readJsonc } from "./json.ts";
|
|
6
|
+
import type { Step } from "./steps.ts";
|
|
7
|
+
|
|
8
|
+
/** The paths under `root` that exist. */
|
|
9
|
+
export const existing = async (
|
|
10
|
+
root: string,
|
|
11
|
+
paths: readonly string[],
|
|
12
|
+
): Promise<string[]> => {
|
|
13
|
+
const found = await Promise.all(
|
|
14
|
+
paths.map((path) => exists(join(root, path))),
|
|
15
|
+
);
|
|
16
|
+
return paths.filter((_, index) => found[index] === true);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** npm packs drop `bunfig.toml`, and linters read nested configs, so these keep a suffix. */
|
|
20
|
+
const STORED_WITH_SUFFIX = new Set([
|
|
21
|
+
".markdownlint-cli2.jsonc",
|
|
22
|
+
".oxlintrc.json",
|
|
23
|
+
"bunfig.toml",
|
|
24
|
+
]);
|
|
25
|
+
export const storedPath = (path: string): string =>
|
|
26
|
+
STORED_WITH_SUFFIX.has(path) ? `${path}.template` : path;
|
|
27
|
+
|
|
28
|
+
/** The template's text, plus the ignore entries the repo's copy adds to it. */
|
|
29
|
+
const contentOf = async (
|
|
30
|
+
dir: string,
|
|
31
|
+
target: string,
|
|
32
|
+
path: string,
|
|
33
|
+
): Promise<string> => {
|
|
34
|
+
const template = await Bun.file(join(dir, storedPath(path))).text();
|
|
35
|
+
const key = IGNORE_KEYS[path];
|
|
36
|
+
return key === undefined
|
|
37
|
+
? template
|
|
38
|
+
: mergeIgnoreList(template, await readJsonc(join(target, path)), key);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const copyFile = async (
|
|
42
|
+
dir: string,
|
|
43
|
+
target: string,
|
|
44
|
+
path: string,
|
|
45
|
+
): Promise<Step[]> => {
|
|
46
|
+
const content = await contentOf(dir, target, path);
|
|
47
|
+
// A repo's own layout of its ignore list stays when the data matches.
|
|
48
|
+
if (
|
|
49
|
+
path in IGNORE_KEYS &&
|
|
50
|
+
Bun.deepEquals(
|
|
51
|
+
Bun.JSONC.parse(content),
|
|
52
|
+
await readJsonc(join(target, path)),
|
|
53
|
+
)
|
|
54
|
+
) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
return [
|
|
58
|
+
{ kind: "write", path, content, executable: path.startsWith(".githooks/") },
|
|
59
|
+
];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Writes each of `paths` from `dir`; with `overwrite` off, only the ones `target` lacks. */
|
|
63
|
+
export const copies = async (
|
|
64
|
+
dir: string,
|
|
65
|
+
target: string,
|
|
66
|
+
paths: readonly string[],
|
|
67
|
+
overwrite: boolean,
|
|
68
|
+
): Promise<Step[]> => {
|
|
69
|
+
const present = new Set(overwrite ? [] : await existing(target, paths));
|
|
70
|
+
const steps = await Promise.all(
|
|
71
|
+
paths
|
|
72
|
+
.filter((path) => !present.has(path))
|
|
73
|
+
.map((path) => copyFile(dir, target, path)),
|
|
74
|
+
);
|
|
75
|
+
return steps.flat();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** The template's `bunfig.toml` plus the settings the repo's copy adds. */
|
|
79
|
+
export const planBunfig = async (
|
|
80
|
+
filesRoot: string,
|
|
81
|
+
target: string,
|
|
82
|
+
): Promise<{ steps: Step[]; notes: string[] }> => {
|
|
83
|
+
const file = Bun.file(join(target, "bunfig.toml"));
|
|
84
|
+
const merged = mergeBunfig(
|
|
85
|
+
await Bun.file(join(filesRoot, storedPath("bunfig.toml"))).text(),
|
|
86
|
+
(await file.exists()) ? await file.text() : undefined,
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
steps: [{ kind: "write", path: "bunfig.toml", content: merged.content }],
|
|
90
|
+
notes: merged.notes,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { asRecord, asStrings } from "./json.ts";
|
|
2
|
+
|
|
3
|
+
/** The ignore list each tool config keeps, which a repo may extend. */
|
|
4
|
+
export const IGNORE_KEYS: Readonly<Record<string, string>> = {
|
|
5
|
+
".markdownlint-cli2.jsonc": "ignores",
|
|
6
|
+
".oxlintrc.json": "ignorePatterns",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const arrayOf = (key: string): RegExp =>
|
|
10
|
+
new RegExp(`("${key}"\\s*:\\s*)\\[([^\\]]*)\\]`, "u");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Writes the template's config with the entries `current` lists under `key`
|
|
14
|
+
* and the template lacks added after the template's own, in the template's
|
|
15
|
+
* layout and with its comments.
|
|
16
|
+
*/
|
|
17
|
+
export const mergeIgnoreList = (
|
|
18
|
+
template: string,
|
|
19
|
+
current: unknown,
|
|
20
|
+
key: string,
|
|
21
|
+
): string => {
|
|
22
|
+
const found = arrayOf(key).exec(template);
|
|
23
|
+
if (found === null) {
|
|
24
|
+
return template;
|
|
25
|
+
}
|
|
26
|
+
const have = new Set(asStrings(Bun.JSONC.parse(`[${found[2] ?? ""}]`)));
|
|
27
|
+
const extra = [
|
|
28
|
+
...new Set(asStrings(asRecord(current)[key]).filter((e) => !have.has(e))),
|
|
29
|
+
];
|
|
30
|
+
if (extra.length === 0) {
|
|
31
|
+
return template;
|
|
32
|
+
}
|
|
33
|
+
const items = [...have, ...extra].map((entry) => JSON.stringify(entry));
|
|
34
|
+
const body = (found[2] ?? "").includes("\n")
|
|
35
|
+
? `\n${items.map((item) => `\t\t${item}`).join(",\n")}\n\t`
|
|
36
|
+
: items.join(", ");
|
|
37
|
+
return template.replace(
|
|
38
|
+
arrayOf(key),
|
|
39
|
+
(_, head: string) => `${head}[${body}]`,
|
|
40
|
+
);
|
|
41
|
+
};
|
package/src/adopt/tests.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
|
|
3
3
|
import { listFiles } from "../files.ts";
|
|
4
|
+
import { readBunfigTestIgnores } from "../ignores.ts";
|
|
4
5
|
import type { Step } from "./steps.ts";
|
|
5
6
|
|
|
6
7
|
const TEST_FILES = new Bun.Glob(
|
|
@@ -40,9 +41,16 @@ export const migrateTestFile = (text: string): string => {
|
|
|
40
41
|
: text;
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Rewrites each test file that is not on bun:test yet, except those in node_modules,
|
|
46
|
+
* those git ignores, and those bunfig.toml keeps `bun test` from running.
|
|
47
|
+
*/
|
|
44
48
|
export const testSteps = async (target: string): Promise<Step[]> => {
|
|
45
|
-
const paths = await listFiles(
|
|
49
|
+
const paths = await listFiles(
|
|
50
|
+
target,
|
|
51
|
+
TEST_FILES,
|
|
52
|
+
await readBunfigTestIgnores(target),
|
|
53
|
+
);
|
|
46
54
|
const texts = await Promise.all(
|
|
47
55
|
paths.map((path) => Bun.file(join(target, path)).text()),
|
|
48
56
|
);
|
package/src/files.ts
CHANGED
|
@@ -2,8 +2,17 @@ import { join } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import { $, type Glob } from "bun";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { isIgnored } from "./ignores.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The files under `cwd` that match `glob`, without the ones git ignores, the
|
|
9
|
+
* ones `ignore` matches, and node_modules.
|
|
10
|
+
*/
|
|
11
|
+
export const listFiles = async (
|
|
12
|
+
cwd: string,
|
|
13
|
+
glob: Glob,
|
|
14
|
+
ignore: readonly string[] = [],
|
|
15
|
+
): Promise<string[]> => {
|
|
7
16
|
const git = await $`git ls-files --cached --others --exclude-standard`
|
|
8
17
|
.cwd(cwd)
|
|
9
18
|
.nothrow()
|
|
@@ -13,7 +22,10 @@ export const listFiles = async (cwd: string, glob: Glob): Promise<string[]> => {
|
|
|
13
22
|
? git.stdout.toString().split("\n")
|
|
14
23
|
: await Array.fromAsync(glob.scan({ cwd }))
|
|
15
24
|
).filter(
|
|
16
|
-
(path) =>
|
|
25
|
+
(path) =>
|
|
26
|
+
glob.match(path) &&
|
|
27
|
+
!path.split("/").includes("node_modules") &&
|
|
28
|
+
!isIgnored(path, ignore),
|
|
17
29
|
);
|
|
18
30
|
// The index still lists a file deleted since the last commit.
|
|
19
31
|
const present = await Promise.all(
|
package/src/guard.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { $, Glob } from "bun";
|
|
|
5
5
|
|
|
6
6
|
import { planAdoption, PROJECT_FILES } from "./adopt/adopt.ts";
|
|
7
7
|
import { listFiles } from "./files.ts";
|
|
8
|
+
import { readOxlintIgnores } from "./ignores.ts";
|
|
8
9
|
|
|
9
10
|
// Written as alternations so this file does not match itself.
|
|
10
11
|
const SUPPRESSION =
|
|
@@ -181,9 +182,12 @@ const reExported = (
|
|
|
181
182
|
* The tracked source files each package exposes: its entry points and, through
|
|
182
183
|
* relative re-exports, the files they re-export from.
|
|
183
184
|
*/
|
|
184
|
-
const listApiFiles = async (
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
const listApiFiles = async (
|
|
186
|
+
cwd: string,
|
|
187
|
+
ignore: readonly string[],
|
|
188
|
+
): Promise<string[]> => {
|
|
189
|
+
const sources = new Set(await listFiles(cwd, SOURCE, ignore));
|
|
190
|
+
const manifests = await listFiles(cwd, MANIFEST, ignore);
|
|
187
191
|
const entries = await Promise.all(
|
|
188
192
|
manifests.map(async (path) => {
|
|
189
193
|
const manifest: unknown = await Bun.file(join(cwd, path)).json();
|
|
@@ -215,8 +219,11 @@ const listApiFiles = async (cwd: string): Promise<string[]> => {
|
|
|
215
219
|
};
|
|
216
220
|
|
|
217
221
|
/** Exported declarations in the package API that lack a JSDoc comment. */
|
|
218
|
-
const findUndocumentedApi = async (
|
|
219
|
-
|
|
222
|
+
const findUndocumentedApi = async (
|
|
223
|
+
cwd: string,
|
|
224
|
+
ignore: readonly string[],
|
|
225
|
+
): Promise<string[]> => {
|
|
226
|
+
const files = await listApiFiles(cwd, ignore);
|
|
220
227
|
if (files.length === 0) {
|
|
221
228
|
return [];
|
|
222
229
|
}
|
|
@@ -255,15 +262,18 @@ const findDrift = async (cwd: string): Promise<string[]> => {
|
|
|
255
262
|
);
|
|
256
263
|
};
|
|
257
264
|
|
|
258
|
-
export const findProblems = async (cwd: string): Promise<string[]> =>
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
export const findProblems = async (cwd: string): Promise<string[]> => {
|
|
266
|
+
const ignore = await readOxlintIgnores(cwd);
|
|
267
|
+
return [
|
|
268
|
+
...(await findSuppressions(cwd, await listFiles(cwd, SOURCE, ignore))),
|
|
269
|
+
...(await findUndocumentedApi(cwd, ignore)),
|
|
270
|
+
...(await findForeignFiles(cwd)),
|
|
271
|
+
...(await findForeignScripts(cwd)),
|
|
272
|
+
...(await findTypescriptDrift(cwd)),
|
|
273
|
+
...findUnsortedCatalogs(await Bun.file(join(cwd, "package.json")).json()),
|
|
274
|
+
...(await findDrift(cwd)),
|
|
275
|
+
];
|
|
276
|
+
};
|
|
267
277
|
|
|
268
278
|
export const guard = async (cwd: string): Promise<number> => {
|
|
269
279
|
const problems = await findProblems(cwd);
|
package/src/ignores.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { Glob } from "bun";
|
|
4
|
+
|
|
5
|
+
import { asRecord, asStrings, readJsonc } from "./adopt/json.ts";
|
|
6
|
+
|
|
7
|
+
/** The globs a gitignore-style `pattern` stands for: the path itself and what is under it. */
|
|
8
|
+
const globsOf = (pattern: string): string[] => {
|
|
9
|
+
const dir = pattern.replace(/\/+$/u, "");
|
|
10
|
+
const anchored = dir.startsWith("/") || dir.includes("/", 1);
|
|
11
|
+
const base = dir.replace(/^\//u, "");
|
|
12
|
+
const glob = anchored ? base : `**/${base}`;
|
|
13
|
+
return [glob, `${glob}/**`];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Whether `path`, relative to the repo root, matches `patterns`: gitignore-style
|
|
18
|
+
* entries (a bare name matches at any depth, `dir/` matches a directory, a
|
|
19
|
+
* later `!entry` re-includes) that Bun's glob syntax can read.
|
|
20
|
+
*/
|
|
21
|
+
export const isIgnored = (
|
|
22
|
+
path: string,
|
|
23
|
+
patterns: readonly string[],
|
|
24
|
+
): boolean => {
|
|
25
|
+
let ignored = false;
|
|
26
|
+
for (const entry of patterns) {
|
|
27
|
+
const negated = entry.startsWith("!");
|
|
28
|
+
const pattern = negated ? entry.slice(1) : entry;
|
|
29
|
+
if (
|
|
30
|
+
pattern !== "" &&
|
|
31
|
+
globsOf(pattern).some((glob) => new Glob(glob).match(path))
|
|
32
|
+
) {
|
|
33
|
+
ignored = !negated;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return ignored;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** The `ignorePatterns` of the repo's `.oxlintrc.json`, which the repo may extend. */
|
|
40
|
+
export const readOxlintIgnores = async (cwd: string): Promise<string[]> =>
|
|
41
|
+
asStrings(
|
|
42
|
+
asRecord(await readJsonc(join(cwd, ".oxlintrc.json")))["ignorePatterns"],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
/** The `[test] pathIgnorePatterns` of the repo's `bunfig.toml`: the paths `bun test` skips. */
|
|
46
|
+
export const readBunfigTestIgnores = async (cwd: string): Promise<string[]> => {
|
|
47
|
+
const file = Bun.file(join(cwd, "bunfig.toml"));
|
|
48
|
+
if (!(await file.exists())) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const patterns = asRecord(
|
|
52
|
+
asRecord(Bun.TOML.parse(await file.text()))["test"],
|
|
53
|
+
)["pathIgnorePatterns"];
|
|
54
|
+
return typeof patterns === "string" ? [patterns] : asStrings(patterns);
|
|
55
|
+
};
|