@amritk/mjst 0.12.2 → 0.12.3
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/dist/cli.js +280 -362
- package/dist/combine-files.js +24 -35
- package/dist/detect-helpers-mode.js +28 -41
- package/dist/emit-examples.js +18 -28
- package/dist/has-external-refs.js +16 -21
- package/dist/help-text.js +4 -7
- package/dist/lint/resolver.js +79 -112
- package/dist/lint/ruleset-loader.js +30 -28
- package/dist/lint/run.js +153 -194
- package/dist/load-config.js +44 -45
- package/dist/load-schema-module.js +28 -43
- package/dist/load-schema.js +31 -47
- package/dist/parse-cli-args.js +190 -229
- package/dist/read-version.js +8 -12
- package/dist/resolve-import-ext.js +4 -9
- package/package.json +11 -11
package/dist/combine-files.js
CHANGED
|
@@ -1,39 +1,28 @@
|
|
|
1
|
-
// Require whitespace after `import` so a property literally named `import` (e.g.
|
|
2
|
-
// `import?: string`) is not mistaken for an import statement.
|
|
3
1
|
const IMPORT_RE = /^import\s/;
|
|
4
2
|
const RELATIVE_IMPORT_RE = /\bfrom\s+['"]\.\.?\//;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (file.filename === 'index.ts')
|
|
21
|
-
continue;
|
|
22
|
-
const bodyLines = [];
|
|
23
|
-
for (const line of file.content.split('\n')) {
|
|
24
|
-
if (IMPORT_RE.test(line.trimStart())) {
|
|
25
|
-
// Relative imports point at sibling files that are now inlined — drop them.
|
|
26
|
-
if (RELATIVE_IMPORT_RE.test(line))
|
|
27
|
-
continue;
|
|
28
|
-
externalImports.add(line.trim());
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
bodyLines.push(line);
|
|
32
|
-
}
|
|
33
|
-
const body = bodyLines.join('\n').trim();
|
|
34
|
-
if (body)
|
|
35
|
-
bodies.push(body);
|
|
3
|
+
const combineGeneratedFiles = (files) => {
|
|
4
|
+
const externalImports = /* @__PURE__ */ new Set();
|
|
5
|
+
const bodies = [];
|
|
6
|
+
for (const file of files) {
|
|
7
|
+
if (file.filename === "index.ts")
|
|
8
|
+
continue;
|
|
9
|
+
const bodyLines = [];
|
|
10
|
+
for (const line of file.content.split("\n")) {
|
|
11
|
+
if (IMPORT_RE.test(line.trimStart())) {
|
|
12
|
+
if (RELATIVE_IMPORT_RE.test(line))
|
|
13
|
+
continue;
|
|
14
|
+
externalImports.add(line.trim());
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
bodyLines.push(line);
|
|
36
18
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
19
|
+
const body = bodyLines.join("\n").trim();
|
|
20
|
+
if (body)
|
|
21
|
+
bodies.push(body);
|
|
22
|
+
}
|
|
23
|
+
const importBlock = [...externalImports].sort().join("\n");
|
|
24
|
+
return [importBlock, bodies.join("\n\n")].filter(Boolean).join("\n\n") + "\n";
|
|
25
|
+
};
|
|
26
|
+
export {
|
|
27
|
+
combineGeneratedFiles
|
|
39
28
|
};
|
|
@@ -1,45 +1,32 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
2
|
-
import { dirname, join, parse as parsePath, resolve } from
|
|
3
|
-
/** Reads a package.json's declared dependency names, or `undefined` if unreadable. */
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, parse as parsePath, resolve } from "node:path";
|
|
4
3
|
const readDeclaredDeps = (packageJsonPath) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
4
|
+
try {
|
|
5
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
6
|
+
return /* @__PURE__ */ new Set([
|
|
7
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
8
|
+
...Object.keys(pkg.devDependencies ?? {}),
|
|
9
|
+
...Object.keys(pkg.peerDependencies ?? {}),
|
|
10
|
+
...Object.keys(pkg.optionalDependencies ?? {})
|
|
11
|
+
]);
|
|
12
|
+
} catch {
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
17
15
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* enough: under npm/bun's hoisted layouts `@amritk/helpers` can sit in
|
|
26
|
-
* `node_modules` purely as a transitive dependency of `@amritk/mjst`, which
|
|
27
|
-
* makes `import '@amritk/helpers/...'` work locally but break under
|
|
28
|
-
* pnpm/isolated installs where undeclared packages are not reachable. Requiring
|
|
29
|
-
* a declaration keeps auto-detected `'package'` output portable across install
|
|
30
|
-
* layouts; everything else falls back to the self-contained `'embedded'` mode.
|
|
31
|
-
*/
|
|
32
|
-
export const detectHelpersMode = (outputDir) => {
|
|
33
|
-
let dir = resolve(outputDir);
|
|
34
|
-
const { root } = parsePath(dir);
|
|
35
|
-
while (true) {
|
|
36
|
-
const declared = readDeclaredDeps(join(dir, 'package.json'));
|
|
37
|
-
if (declared) {
|
|
38
|
-
return declared.has('@amritk/helpers') ? 'package' : 'embedded';
|
|
39
|
-
}
|
|
40
|
-
if (dir === root)
|
|
41
|
-
break;
|
|
42
|
-
dir = dirname(dir);
|
|
16
|
+
const detectHelpersMode = (outputDir) => {
|
|
17
|
+
let dir = resolve(outputDir);
|
|
18
|
+
const { root } = parsePath(dir);
|
|
19
|
+
while (true) {
|
|
20
|
+
const declared = readDeclaredDeps(join(dir, "package.json"));
|
|
21
|
+
if (declared) {
|
|
22
|
+
return declared.has("@amritk/helpers") ? "package" : "embedded";
|
|
43
23
|
}
|
|
44
|
-
|
|
24
|
+
if (dir === root)
|
|
25
|
+
break;
|
|
26
|
+
dir = dirname(dir);
|
|
27
|
+
}
|
|
28
|
+
return "embedded";
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
detectHelpersMode
|
|
45
32
|
};
|
package/dist/emit-examples.js
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
|
-
import { mkdir, writeFile } from
|
|
2
|
-
import { dirname, join, relative } from
|
|
3
|
-
import { buildExampleSchema } from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
const files = await buildExampleSchema(schema, rootTypeName, typeSuffix);
|
|
20
|
-
const exampleDir = join(outputDir, 'examples', subDir);
|
|
21
|
-
const written = [];
|
|
22
|
-
for (const file of files) {
|
|
23
|
-
const filePath = join(exampleDir, file.filename);
|
|
24
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
25
|
-
await writeFile(filePath, bannerPrefix + file.content, 'utf-8');
|
|
26
|
-
written.push(relative(outputDir, filePath));
|
|
27
|
-
}
|
|
28
|
-
return written;
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { buildExampleSchema } from "@amritk/generate-examples";
|
|
4
|
+
const emitExamples = async (options) => {
|
|
5
|
+
const { schema, rootTypeName, outputDir, subDir = "", typeSuffix, bannerPrefix = "" } = options;
|
|
6
|
+
const files = await buildExampleSchema(schema, rootTypeName, typeSuffix);
|
|
7
|
+
const exampleDir = join(outputDir, "examples", subDir);
|
|
8
|
+
const written = [];
|
|
9
|
+
for (const file of files) {
|
|
10
|
+
const filePath = join(exampleDir, file.filename);
|
|
11
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
12
|
+
await writeFile(filePath, bannerPrefix + file.content, "utf-8");
|
|
13
|
+
written.push(relative(outputDir, filePath));
|
|
14
|
+
}
|
|
15
|
+
return written;
|
|
16
|
+
};
|
|
17
|
+
export {
|
|
18
|
+
emitExamples
|
|
29
19
|
};
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const value = obj[keyword];
|
|
18
|
-
if (typeof value === 'string' && !value.startsWith('#'))
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
return Object.values(obj).some(hasExternalRefs);
|
|
1
|
+
const REF_KEYWORDS = ["$ref", "$dynamicRef", "$recursiveRef"];
|
|
2
|
+
const hasExternalRefs = (data) => {
|
|
3
|
+
if (data === null || typeof data !== "object")
|
|
4
|
+
return false;
|
|
5
|
+
if (Array.isArray(data))
|
|
6
|
+
return data.some(hasExternalRefs);
|
|
7
|
+
const obj = data;
|
|
8
|
+
for (const keyword of REF_KEYWORDS) {
|
|
9
|
+
const value = obj[keyword];
|
|
10
|
+
if (typeof value === "string" && !value.startsWith("#"))
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return Object.values(obj).some(hasExternalRefs);
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
hasExternalRefs
|
|
22
17
|
};
|
package/dist/help-text.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Usage text printed for `--help`/`-h` and when the CLI is invoked with no
|
|
3
|
-
* arguments. Kept as a hand-written constant because the config schema that
|
|
4
|
-
* documents these flags (config.schema.json) is not shipped in the published
|
|
5
|
-
* package; the help-text test cross-checks that every flag is listed.
|
|
6
|
-
*/
|
|
7
|
-
export const HELP_TEXT = `mjst — generate TypeScript parsers and type definitions from JSON Schemas
|
|
1
|
+
const HELP_TEXT = `mjst \u2014 generate TypeScript parsers and type definitions from JSON Schemas
|
|
8
2
|
|
|
9
3
|
Usage:
|
|
10
4
|
mjst --schema <path> --out-dir <dir> [options]
|
|
@@ -49,3 +43,6 @@ Misc:
|
|
|
49
43
|
|
|
50
44
|
Docs: https://github.com/amritk/mjst/tree/main/packages/cli#readme
|
|
51
45
|
`;
|
|
46
|
+
export {
|
|
47
|
+
HELP_TEXT
|
|
48
|
+
};
|
package/dist/lint/resolver.js
CHANGED
|
@@ -1,126 +1,93 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
2
|
-
import { resolve as resolvePath } from
|
|
3
|
-
import { createDocument, DiagnosticSeverity, resolveSourceOriginFromMap
|
|
4
|
-
import { resolveRefs, resolveRefsFromFile } from
|
|
5
|
-
import { parse as parseYaml } from
|
|
6
|
-
import { hasExternalRefs } from
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve as resolvePath } from "node:path";
|
|
3
|
+
import { createDocument, DiagnosticSeverity, resolveSourceOriginFromMap } from "@amritk/lint";
|
|
4
|
+
import { resolveRefs, resolveRefsFromFile } from "@amritk/resolve-refs";
|
|
5
|
+
import { parse as parseYaml } from "@amritk/yaml";
|
|
6
|
+
import { hasExternalRefs } from "../has-external-refs.js";
|
|
7
7
|
const isRemote = (location) => /^https?:\/\//i.test(location);
|
|
8
|
-
/**
|
|
9
|
-
* Parses a referenced document by extension: YAML for `.yaml`/`.yml`, JSON for
|
|
10
|
-
* `.json`, and for anything else (extensionless, remote) tries JSON first, then
|
|
11
|
-
* YAML. YAML is a JSON superset, so this accepts every document the linter does.
|
|
12
|
-
*/
|
|
13
8
|
const parseDoc = (content, location) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
9
|
+
if (/\.ya?ml$/i.test(location))
|
|
10
|
+
return parseYaml(content);
|
|
11
|
+
if (/\.json$/i.test(location))
|
|
12
|
+
return JSON.parse(content);
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
} catch {
|
|
16
|
+
return parseYaml(content);
|
|
17
|
+
}
|
|
24
18
|
};
|
|
25
|
-
/** Whether the file at `absolute` reads back byte-for-byte as `input` (the document we linted). */
|
|
26
19
|
const readsBackAs = (absolute, input) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
20
|
+
try {
|
|
21
|
+
return readFileSync(absolute, "utf8") === input;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
33
25
|
};
|
|
34
|
-
/**
|
|
35
|
-
* Builds the {@link ISourceSet} the runner uses to map a finding on a resolved
|
|
36
|
-
* (dereferenced) node back to the file it came from. Origins from the resolver
|
|
37
|
-
* attribute each inlined node to its source document + path; `get` lazily parses
|
|
38
|
-
* each source file (with a position map) so findings on cross-file nodes report
|
|
39
|
-
* that file's own `line:column`. The root document is already parsed by the
|
|
40
|
-
* linter, so it is reused directly. Remote documents are not re-fetched here, so
|
|
41
|
-
* a finding inlined from a URL falls back to the root document's position.
|
|
42
|
-
*/
|
|
43
26
|
const buildSourceSet = (rootDocument, resolved, origins, rootLocation) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
};
|
|
27
|
+
const documents = /* @__PURE__ */ new Map([[rootLocation, rootDocument]]);
|
|
28
|
+
const originMap = origins ?? /* @__PURE__ */ new Map();
|
|
29
|
+
const load = (location) => {
|
|
30
|
+
if (documents.has(location))
|
|
31
|
+
return documents.get(location);
|
|
32
|
+
let document;
|
|
33
|
+
if (location !== "" && !isRemote(location)) {
|
|
34
|
+
try {
|
|
35
|
+
document = createDocument(readFileSync(location, "utf8"), { source: location });
|
|
36
|
+
} catch {
|
|
37
|
+
document = void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
documents.set(location, document);
|
|
41
|
+
return document;
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
get: load,
|
|
45
|
+
origin: (path) => resolveSourceOriginFromMap(resolved, originMap, rootLocation, path)
|
|
46
|
+
};
|
|
65
47
|
};
|
|
66
|
-
/** A zero-width range at the document start, for resolve errors with no recoverable position. */
|
|
67
48
|
const DOCUMENT_START = { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
|
|
68
|
-
/**
|
|
69
|
-
* Maps resolver failures ({@link ResolveError}) into lint findings so an
|
|
70
|
-
* unresolvable `$ref` — a typo'd pointer, a missing file, a refused/failed
|
|
71
|
-
* remote fetch — surfaces as a diagnostic instead of being silently dropped. An
|
|
72
|
-
* error carrying a JSON path is anchored to that node's position in the root
|
|
73
|
-
* document (best effort, `closest`); otherwise the finding is document-level.
|
|
74
|
-
*/
|
|
75
49
|
const toFindings = (errors, root) => errors.map((error) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
50
|
+
const location = error.path.length > 0 ? root.getLocationForJsonPath(error.path, true) : void 0;
|
|
51
|
+
const finding = {
|
|
52
|
+
code: "unresolved-ref",
|
|
53
|
+
message: error.message,
|
|
54
|
+
path: error.path,
|
|
55
|
+
severity: DiagnosticSeverity.Error,
|
|
56
|
+
range: location?.range ?? DOCUMENT_START
|
|
57
|
+
};
|
|
58
|
+
if (root.source !== void 0)
|
|
59
|
+
finding.source = root.source;
|
|
60
|
+
return finding;
|
|
87
61
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...(options.allowPrivateHosts ? { allowPrivateHosts: options.allowPrivateHosts } : {}),
|
|
103
|
-
parse: parseDoc,
|
|
104
|
-
trackOrigins: true,
|
|
105
|
-
};
|
|
106
|
-
return async (document, { input }) => {
|
|
107
|
-
const { source } = document;
|
|
108
|
-
if (source && hasExternalRefs(document.data)) {
|
|
109
|
-
const absolute = resolvePath(source);
|
|
110
|
-
if (readsBackAs(absolute, input)) {
|
|
111
|
-
const { resolved, origins, errors } = await resolveRefsFromFile(absolute, fromFileOptions);
|
|
112
|
-
return {
|
|
113
|
-
resolved,
|
|
114
|
-
sources: buildSourceSet(document, resolved, origins, absolute),
|
|
115
|
-
...(errors.length > 0 ? { diagnostics: toFindings(errors, document) } : {}),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const { resolved, origins, errors } = resolveRefs(document.data, { trackOrigins: true });
|
|
62
|
+
const createLintResolver = (options = {}) => {
|
|
63
|
+
const fromFileOptions = {
|
|
64
|
+
remote: options.remote ?? false,
|
|
65
|
+
...options.allowedHosts ? { allowedHosts: options.allowedHosts } : {},
|
|
66
|
+
...options.allowPrivateHosts ? { allowPrivateHosts: options.allowPrivateHosts } : {},
|
|
67
|
+
parse: parseDoc,
|
|
68
|
+
trackOrigins: true
|
|
69
|
+
};
|
|
70
|
+
return async (document, { input }) => {
|
|
71
|
+
const { source } = document;
|
|
72
|
+
if (source && hasExternalRefs(document.data)) {
|
|
73
|
+
const absolute = resolvePath(source);
|
|
74
|
+
if (readsBackAs(absolute, input)) {
|
|
75
|
+
const { resolved: resolved2, origins: origins2, errors: errors2 } = await resolveRefsFromFile(absolute, fromFileOptions);
|
|
120
76
|
return {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
77
|
+
resolved: resolved2,
|
|
78
|
+
sources: buildSourceSet(document, resolved2, origins2, absolute),
|
|
79
|
+
...errors2.length > 0 ? { diagnostics: toFindings(errors2, document) } : {}
|
|
124
80
|
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const { resolved, origins, errors } = resolveRefs(document.data, { trackOrigins: true });
|
|
84
|
+
return {
|
|
85
|
+
resolved,
|
|
86
|
+
sources: buildSourceSet(document, resolved, origins, ""),
|
|
87
|
+
...errors.length > 0 ? { diagnostics: toFindings(errors, document) } : {}
|
|
125
88
|
};
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
export {
|
|
92
|
+
createLintResolver
|
|
126
93
|
};
|
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
import { existsSync } from
|
|
2
|
-
import { readFile } from
|
|
3
|
-
import { dirname, isAbsolute, join, resolve } from
|
|
4
|
-
import { pathToFileURL } from
|
|
5
|
-
import { parseWithPointers } from
|
|
6
|
-
const RULESET_FILENAMES = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return parseWithPointers(content).data;
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { parseWithPointers } from "@amritk/lint";
|
|
6
|
+
const RULESET_FILENAMES = [".lint.yaml", ".lint.yml", ".lint.json", ".lint.js", ".lint.mjs"];
|
|
7
|
+
const loadRuleset = async (path) => {
|
|
8
|
+
const absolute = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
9
|
+
if (/\.(c|m)?js$/.test(absolute)) {
|
|
10
|
+
const module = await import(pathToFileURL(absolute).href);
|
|
11
|
+
return module.default ?? module;
|
|
12
|
+
}
|
|
13
|
+
const content = await readFile(absolute, "utf8");
|
|
14
|
+
return parseWithPointers(content).data;
|
|
16
15
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return candidate;
|
|
25
|
-
}
|
|
26
|
-
const parent = dirname(dir);
|
|
27
|
-
if (parent === dir)
|
|
28
|
-
return undefined;
|
|
29
|
-
dir = parent;
|
|
16
|
+
const discoverRuleset = (startDir) => {
|
|
17
|
+
let dir = resolve(startDir);
|
|
18
|
+
while (true) {
|
|
19
|
+
for (const name of RULESET_FILENAMES) {
|
|
20
|
+
const candidate = join(dir, name);
|
|
21
|
+
if (existsSync(candidate))
|
|
22
|
+
return candidate;
|
|
30
23
|
}
|
|
24
|
+
const parent = dirname(dir);
|
|
25
|
+
if (parent === dir)
|
|
26
|
+
return void 0;
|
|
27
|
+
dir = parent;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
discoverRuleset,
|
|
32
|
+
loadRuleset
|
|
31
33
|
};
|