@gobing-ai/ts-rule-engine 0.2.6 → 0.2.8
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 +563 -1
- package/dist/config/extensions.d.ts +48 -0
- package/dist/config/extensions.d.ts.map +1 -0
- package/dist/config/extensions.js +67 -0
- package/dist/config/loader.d.ts +20 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +52 -26
- package/dist/engine.d.ts +26 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +79 -0
- package/dist/evaluators/exit-code-evaluator.d.ts +1 -1
- package/dist/evaluators/exit-code-evaluator.d.ts.map +1 -1
- package/dist/evaluators/exit-code-evaluator.js +22 -9
- package/dist/evaluators/forbidden-import-evaluator.d.ts +16 -3
- package/dist/evaluators/forbidden-import-evaluator.d.ts.map +1 -1
- package/dist/evaluators/forbidden-import-evaluator.js +71 -6
- package/dist/evaluators/import-boundary-evaluator.d.ts +19 -0
- package/dist/evaluators/import-boundary-evaluator.d.ts.map +1 -0
- package/dist/evaluators/import-boundary-evaluator.js +85 -0
- package/dist/evaluators/path-evaluator.d.ts +15 -2
- package/dist/evaluators/path-evaluator.d.ts.map +1 -1
- package/dist/evaluators/path-evaluator.js +49 -3
- package/dist/evaluators/regex-evaluator.d.ts.map +1 -1
- package/dist/evaluators/regex-evaluator.js +43 -8
- package/dist/evaluators/schema-artifact-evaluator.d.ts +21 -0
- package/dist/evaluators/schema-artifact-evaluator.d.ts.map +1 -0
- package/dist/evaluators/schema-artifact-evaluator.js +102 -0
- package/dist/evaluators/secrets-scanner-evaluator.d.ts +13 -2
- package/dist/evaluators/secrets-scanner-evaluator.d.ts.map +1 -1
- package/dist/evaluators/secrets-scanner-evaluator.js +72 -9
- package/dist/evaluators/sg-evaluator.d.ts +19 -0
- package/dist/evaluators/sg-evaluator.d.ts.map +1 -0
- package/dist/evaluators/sg-evaluator.js +112 -0
- package/dist/evaluators/test-location-evaluator.d.ts +14 -1
- package/dist/evaluators/test-location-evaluator.d.ts.map +1 -1
- package/dist/evaluators/test-location-evaluator.js +42 -22
- package/dist/evaluators/tsdoc-export-evaluator.js +19 -5
- package/dist/fixers/fixers.d.ts +86 -0
- package/dist/fixers/fixers.d.ts.map +1 -0
- package/dist/fixers/fixers.js +230 -0
- package/dist/fixers/test-stub-fixer.d.ts +49 -0
- package/dist/fixers/test-stub-fixer.d.ts.map +1 -0
- package/dist/fixers/test-stub-fixer.js +91 -0
- package/dist/host/builtins.d.ts.map +1 -1
- package/dist/host/builtins.js +12 -1
- package/dist/host/rule-engine-host.d.ts +3 -0
- package/dist/host/rule-engine-host.d.ts.map +1 -1
- package/dist/host/rule-engine-host.js +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/resolvers/test-path-resolver.d.ts +72 -0
- package/dist/resolvers/test-path-resolver.d.ts.map +1 -0
- package/dist/resolvers/test-path-resolver.js +112 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/package.json +4 -3
- package/schemas/preset.schema.json +40 -0
- package/schemas/rule-file.schema.json +49 -0
- package/src/config/extensions.ts +115 -0
- package/src/config/loader.ts +81 -25
- package/src/engine.ts +99 -2
- package/src/evaluators/exit-code-evaluator.ts +27 -9
- package/src/evaluators/forbidden-import-evaluator.ts +101 -7
- package/src/evaluators/import-boundary-evaluator.ts +135 -0
- package/src/evaluators/path-evaluator.ts +66 -3
- package/src/evaluators/regex-evaluator.ts +53 -12
- package/src/evaluators/schema-artifact-evaluator.ts +134 -0
- package/src/evaluators/secrets-scanner-evaluator.ts +89 -11
- package/src/evaluators/sg-evaluator.ts +133 -0
- package/src/evaluators/test-location-evaluator.ts +47 -35
- package/src/evaluators/tsdoc-export-evaluator.ts +19 -5
- package/src/fixers/fixers.ts +294 -0
- package/src/fixers/test-stub-fixer.ts +118 -0
- package/src/host/builtins.ts +17 -1
- package/src/host/rule-engine-host.ts +4 -0
- package/src/index.ts +4 -0
- package/src/resolvers/test-path-resolver.ts +133 -0
- package/src/types.ts +40 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TestPathResolver } from '../resolvers/test-path-resolver';
|
|
1
2
|
import type { ResultFormatter, RuleEvaluator } from '../types';
|
|
2
3
|
import { CapabilityRegistry } from './capability-registry';
|
|
3
4
|
/** Host container for rule-engine capabilities. */
|
|
@@ -6,6 +7,8 @@ export declare class RuleEngineHost {
|
|
|
6
7
|
readonly evaluators: CapabilityRegistry<RuleEvaluator>;
|
|
7
8
|
/** Formatter registry keyed by formatter name. */
|
|
8
9
|
readonly formatters: CapabilityRegistry<ResultFormatter>;
|
|
10
|
+
/** Test-path resolver registry keyed by language name. */
|
|
11
|
+
readonly resolvers: CapabilityRegistry<TestPathResolver>;
|
|
9
12
|
constructor();
|
|
10
13
|
}
|
|
11
14
|
//# sourceMappingURL=rule-engine-host.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-engine-host.d.ts","sourceRoot":"","sources":["../../src/host/rule-engine-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,mDAAmD;AACnD,qBAAa,cAAc;IACvB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACvD,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;;
|
|
1
|
+
{"version":3,"file":"rule-engine-host.d.ts","sourceRoot":"","sources":["../../src/host/rule-engine-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,mDAAmD;AACnD,qBAAa,cAAc;IACvB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACvD,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACzD,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;;CAO5D"}
|
|
@@ -5,8 +5,11 @@ export class RuleEngineHost {
|
|
|
5
5
|
evaluators;
|
|
6
6
|
/** Formatter registry keyed by formatter name. */
|
|
7
7
|
formatters;
|
|
8
|
+
/** Test-path resolver registry keyed by language name. */
|
|
9
|
+
resolvers;
|
|
8
10
|
constructor() {
|
|
9
11
|
this.evaluators = new CapabilityRegistry('evaluator');
|
|
10
12
|
this.formatters = new CapabilityRegistry('formatter');
|
|
13
|
+
this.resolvers = new CapabilityRegistry('resolver');
|
|
11
14
|
}
|
|
12
15
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
export * from './config/extensions';
|
|
1
2
|
export * from './config/loader';
|
|
2
3
|
export * from './engine';
|
|
4
|
+
export * from './fixers/fixers';
|
|
5
|
+
export * from './fixers/test-stub-fixer';
|
|
3
6
|
export * from './formatters/json';
|
|
4
7
|
export * from './formatters/text';
|
|
5
8
|
export * from './host/capability-registry';
|
|
6
9
|
export * from './host/rule-engine-host';
|
|
10
|
+
export * from './resolvers/test-path-resolver';
|
|
7
11
|
export * from './types';
|
|
8
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
export * from './config/extensions.js';
|
|
1
2
|
export * from './config/loader.js';
|
|
2
3
|
export * from './engine.js';
|
|
4
|
+
export * from './fixers/fixers.js';
|
|
5
|
+
export * from './fixers/test-stub-fixer.js';
|
|
3
6
|
export * from './formatters/json.js';
|
|
4
7
|
export * from './formatters/text.js';
|
|
5
8
|
export * from './host/capability-registry.js';
|
|
6
9
|
export * from './host/rule-engine-host.js';
|
|
10
|
+
export * from './resolvers/test-path-resolver.js';
|
|
7
11
|
export * from './types.js';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable test-path resolution for the test-location evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Each implementation maps a source file path to its conventional test file path
|
|
5
|
+
* for one language, so the same evaluator works across TypeScript, Python, Go, and
|
|
6
|
+
* Rust projects. Resolvers are registered on the host and selected per rule via
|
|
7
|
+
* `evaluator.config.resolver`.
|
|
8
|
+
*/
|
|
9
|
+
/** Metadata about an exported symbol, used when generating a test skeleton. */
|
|
10
|
+
export interface ExportInfo {
|
|
11
|
+
/** Symbol name. */
|
|
12
|
+
readonly name: string;
|
|
13
|
+
/** Declaration kind. */
|
|
14
|
+
readonly kind: 'function' | 'class' | 'const' | 'type' | 'interface' | 'unknown';
|
|
15
|
+
/** One-based source line, when known. */
|
|
16
|
+
readonly line?: number;
|
|
17
|
+
}
|
|
18
|
+
/** Maps source files to expected test files for a project type. */
|
|
19
|
+
export interface TestPathResolver {
|
|
20
|
+
/** Language name this resolver handles (registry key). */
|
|
21
|
+
readonly name: string;
|
|
22
|
+
/** Compute the expected test file path for a source file. */
|
|
23
|
+
resolveTestPath(srcRelPath: string): string;
|
|
24
|
+
/** Optionally generate a test skeleton for a source file. */
|
|
25
|
+
generateSkeleton?(srcRelPath: string, exports: ExportInfo[]): string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* TypeScript conventions:
|
|
29
|
+
* src/foo/bar.ts → tests/foo/bar.test.ts
|
|
30
|
+
* packages/core/src/foo/bar.ts → packages/core/tests/foo/bar.test.ts
|
|
31
|
+
*/
|
|
32
|
+
export declare class TypeScriptTestPathResolver implements TestPathResolver {
|
|
33
|
+
/** Registry key. */
|
|
34
|
+
readonly name = "typescript";
|
|
35
|
+
constructor();
|
|
36
|
+
/** Map a TS/JS source path to its `tests/…test.ts` counterpart. */
|
|
37
|
+
resolveTestPath(srcRelPath: string): string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Python conventions (pytest):
|
|
41
|
+
* src/foo/bar.py → tests/foo/test_bar.py
|
|
42
|
+
*/
|
|
43
|
+
export declare class PythonTestPathResolver implements TestPathResolver {
|
|
44
|
+
/** Registry key. */
|
|
45
|
+
readonly name = "python";
|
|
46
|
+
constructor();
|
|
47
|
+
/** Map a Python source path to its `tests/…/test_*.py` counterpart. */
|
|
48
|
+
resolveTestPath(srcRelPath: string): string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Go conventions:
|
|
52
|
+
* foo/bar.go → foo/bar_test.go (sibling)
|
|
53
|
+
*/
|
|
54
|
+
export declare class GoTestPathResolver implements TestPathResolver {
|
|
55
|
+
/** Registry key. */
|
|
56
|
+
readonly name = "go";
|
|
57
|
+
constructor();
|
|
58
|
+
/** Map a Go source path to its sibling `_test.go` file. */
|
|
59
|
+
resolveTestPath(srcRelPath: string): string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Rust conventions (Cargo integration tests):
|
|
63
|
+
* crate/src/foo.rs → crate/tests/foo.rs
|
|
64
|
+
*/
|
|
65
|
+
export declare class RustTestPathResolver implements TestPathResolver {
|
|
66
|
+
/** Registry key. */
|
|
67
|
+
readonly name = "rust";
|
|
68
|
+
constructor();
|
|
69
|
+
/** Map a Rust source path to its `tests/` integration-test counterpart. */
|
|
70
|
+
resolveTestPath(srcRelPath: string): string;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=test-path-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-path-resolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/test-path-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACvB,mBAAmB;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IACjF,yCAAyC;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5C,6DAA6D;IAC7D,gBAAgB,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;CACxE;AAED;;;;GAIG;AACH,qBAAa,0BAA2B,YAAW,gBAAgB;IAC/D,oBAAoB;IACpB,QAAQ,CAAC,IAAI,gBAAgB;;IAI7B,mEAAmE;IACnE,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAW9C;AAED;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,gBAAgB;IAC3D,oBAAoB;IACpB,QAAQ,CAAC,IAAI,YAAY;;IAIzB,uEAAuE;IACvE,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAe9C;AAED;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,gBAAgB;IACvD,oBAAoB;IACpB,QAAQ,CAAC,IAAI,QAAQ;;IAIrB,2DAA2D;IAC3D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAM9C;AAED;;;GAGG;AACH,qBAAa,oBAAqB,YAAW,gBAAgB;IACzD,oBAAoB;IACpB,QAAQ,CAAC,IAAI,UAAU;;IAIvB,2EAA2E;IAC3E,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAY9C"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable test-path resolution for the test-location evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Each implementation maps a source file path to its conventional test file path
|
|
5
|
+
* for one language, so the same evaluator works across TypeScript, Python, Go, and
|
|
6
|
+
* Rust projects. Resolvers are registered on the host and selected per rule via
|
|
7
|
+
* `evaluator.config.resolver`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* TypeScript conventions:
|
|
11
|
+
* src/foo/bar.ts → tests/foo/bar.test.ts
|
|
12
|
+
* packages/core/src/foo/bar.ts → packages/core/tests/foo/bar.test.ts
|
|
13
|
+
*/
|
|
14
|
+
export class TypeScriptTestPathResolver {
|
|
15
|
+
/** Registry key. */
|
|
16
|
+
name = 'typescript';
|
|
17
|
+
constructor() { }
|
|
18
|
+
/** Map a TS/JS source path to its `tests/…test.ts` counterpart. */
|
|
19
|
+
resolveTestPath(srcRelPath) {
|
|
20
|
+
if (srcRelPath.includes('.test.') || srcRelPath.includes('.spec.'))
|
|
21
|
+
return srcRelPath;
|
|
22
|
+
const srcIdx = srcRelPath.indexOf('/src/');
|
|
23
|
+
if (srcIdx !== -1) {
|
|
24
|
+
const pkg = srcRelPath.slice(0, srcIdx);
|
|
25
|
+
const rel = srcRelPath.slice(srcIdx + '/src/'.length).replace(/\.(ts|tsx|js|jsx)$/, '.test.ts');
|
|
26
|
+
return `${pkg}/tests/${rel}`;
|
|
27
|
+
}
|
|
28
|
+
const withoutExt = srcRelPath.replace(/\.(ts|tsx|js|jsx)$/, '');
|
|
29
|
+
return `tests/${withoutExt.replace(/^src\//, '')}.test.ts`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Python conventions (pytest):
|
|
34
|
+
* src/foo/bar.py → tests/foo/test_bar.py
|
|
35
|
+
*/
|
|
36
|
+
export class PythonTestPathResolver {
|
|
37
|
+
/** Registry key. */
|
|
38
|
+
name = 'python';
|
|
39
|
+
constructor() { }
|
|
40
|
+
/** Map a Python source path to its `tests/…/test_*.py` counterpart. */
|
|
41
|
+
resolveTestPath(srcRelPath) {
|
|
42
|
+
if (!srcRelPath)
|
|
43
|
+
throw new Error('empty source path');
|
|
44
|
+
if (srcRelPath.endsWith('_test.py') || srcRelPath.includes('/test_') || srcRelPath.startsWith('test_')) {
|
|
45
|
+
return srcRelPath;
|
|
46
|
+
}
|
|
47
|
+
if (srcRelPath.startsWith('tests/'))
|
|
48
|
+
return srcRelPath;
|
|
49
|
+
if (!srcRelPath.endsWith('.py'))
|
|
50
|
+
throw new Error(`unsupported extension for python resolver: ${srcRelPath}`);
|
|
51
|
+
const srcIdx = srcRelPath.indexOf('/src/');
|
|
52
|
+
if (srcIdx !== -1) {
|
|
53
|
+
const pkg = srcRelPath.slice(0, srcIdx);
|
|
54
|
+
return `${pkg}/tests/${testify(srcRelPath.slice(srcIdx + '/src/'.length))}`;
|
|
55
|
+
}
|
|
56
|
+
if (srcRelPath.startsWith('src/'))
|
|
57
|
+
return `tests/${testify(srcRelPath.slice(4))}`;
|
|
58
|
+
return `tests/${testify(srcRelPath)}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Go conventions:
|
|
63
|
+
* foo/bar.go → foo/bar_test.go (sibling)
|
|
64
|
+
*/
|
|
65
|
+
export class GoTestPathResolver {
|
|
66
|
+
/** Registry key. */
|
|
67
|
+
name = 'go';
|
|
68
|
+
constructor() { }
|
|
69
|
+
/** Map a Go source path to its sibling `_test.go` file. */
|
|
70
|
+
resolveTestPath(srcRelPath) {
|
|
71
|
+
if (!srcRelPath)
|
|
72
|
+
throw new Error('empty source path');
|
|
73
|
+
if (srcRelPath.endsWith('_test.go'))
|
|
74
|
+
return srcRelPath;
|
|
75
|
+
if (!srcRelPath.endsWith('.go'))
|
|
76
|
+
throw new Error(`unsupported extension for go resolver: ${srcRelPath}`);
|
|
77
|
+
return srcRelPath.replace(/\.go$/, '_test.go');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Rust conventions (Cargo integration tests):
|
|
82
|
+
* crate/src/foo.rs → crate/tests/foo.rs
|
|
83
|
+
*/
|
|
84
|
+
export class RustTestPathResolver {
|
|
85
|
+
/** Registry key. */
|
|
86
|
+
name = 'rust';
|
|
87
|
+
constructor() { }
|
|
88
|
+
/** Map a Rust source path to its `tests/` integration-test counterpart. */
|
|
89
|
+
resolveTestPath(srcRelPath) {
|
|
90
|
+
if (!srcRelPath)
|
|
91
|
+
throw new Error('empty source path');
|
|
92
|
+
if (srcRelPath.startsWith('tests/') || srcRelPath.includes('/tests/'))
|
|
93
|
+
return srcRelPath;
|
|
94
|
+
if (!srcRelPath.endsWith('.rs'))
|
|
95
|
+
throw new Error(`unsupported extension for rust resolver: ${srcRelPath}`);
|
|
96
|
+
const srcIdx = srcRelPath.indexOf('/src/');
|
|
97
|
+
if (srcIdx !== -1) {
|
|
98
|
+
const crate = srcRelPath.slice(0, srcIdx);
|
|
99
|
+
return `${crate}/tests/${srcRelPath.slice(srcIdx + '/src/'.length)}`;
|
|
100
|
+
}
|
|
101
|
+
if (srcRelPath.startsWith('src/'))
|
|
102
|
+
return `tests/${srcRelPath.slice(4)}`;
|
|
103
|
+
return `tests/${srcRelPath}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Prefix the basename of a Python source path with `test_`. */
|
|
107
|
+
function testify(rel) {
|
|
108
|
+
const lastSlash = rel.lastIndexOf('/');
|
|
109
|
+
const dir = lastSlash >= 0 ? rel.slice(0, lastSlash + 1) : '';
|
|
110
|
+
const base = lastSlash >= 0 ? rel.slice(lastSlash + 1) : rel;
|
|
111
|
+
return `${dir}test_${base}`;
|
|
112
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export interface RuleFixConfig {
|
|
|
40
40
|
}
|
|
41
41
|
/** Rule file shape before normalization. */
|
|
42
42
|
export interface ConstraintRuleFile {
|
|
43
|
+
/** Optional JSON Schema ref used by editors and loader validation. */
|
|
44
|
+
$schema?: string;
|
|
43
45
|
/** File-level default include patterns. */
|
|
44
46
|
include?: string[];
|
|
45
47
|
/** File-level default exclude patterns. */
|
|
@@ -49,8 +51,21 @@ export interface ConstraintRuleFile {
|
|
|
49
51
|
/** Rule definitions. */
|
|
50
52
|
rules: ConstraintRule[];
|
|
51
53
|
}
|
|
54
|
+
/** Relative module paths a preset contributes per capability kind. */
|
|
55
|
+
export interface PresetExtensions {
|
|
56
|
+
/** Test-path resolver module paths. */
|
|
57
|
+
resolvers?: string[];
|
|
58
|
+
/** Evaluator module paths. */
|
|
59
|
+
evaluators?: string[];
|
|
60
|
+
/** Fixer module paths. */
|
|
61
|
+
fixers?: string[];
|
|
62
|
+
/** Formatter module paths. */
|
|
63
|
+
formatters?: string[];
|
|
64
|
+
}
|
|
52
65
|
/** Preset definition that composes category folders or other presets. */
|
|
53
66
|
export interface PresetDefinition {
|
|
67
|
+
/** Optional JSON Schema ref used by editors and loader validation. */
|
|
68
|
+
$schema?: string;
|
|
54
69
|
/** Preset name. */
|
|
55
70
|
name: string;
|
|
56
71
|
/** Category folders or preset names to compose. */
|
|
@@ -63,6 +78,8 @@ export interface PresetDefinition {
|
|
|
63
78
|
mode: FixMode;
|
|
64
79
|
};
|
|
65
80
|
}>;
|
|
81
|
+
/** Custom capability modules contributed by this preset (opt-in to load). */
|
|
82
|
+
extensions?: PresetExtensions;
|
|
66
83
|
}
|
|
67
84
|
/** Candidate fix emitted by an evaluator or fixer. */
|
|
68
85
|
export interface Fix {
|
|
@@ -79,6 +96,15 @@ export interface Fix {
|
|
|
79
96
|
/** Whether this fix may be applied automatically. */
|
|
80
97
|
mode: Exclude<FixMode, 'none'>;
|
|
81
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* What a finding represents.
|
|
101
|
+
*
|
|
102
|
+
* - `violation`: the rule ran and the project breached its policy (the default).
|
|
103
|
+
* - `error`: the rule could not run — a misconfiguration or runtime fault in the
|
|
104
|
+
* evaluator itself. These are not policy breaches and callers may surface them
|
|
105
|
+
* separately (e.g. "rule misconfigured") rather than as project violations.
|
|
106
|
+
*/
|
|
107
|
+
export type FindingKind = 'violation' | 'error';
|
|
82
108
|
/** Finding emitted by a constraint rule. */
|
|
83
109
|
export interface ConstraintFinding {
|
|
84
110
|
/** Rule identifier. */
|
|
@@ -95,6 +121,8 @@ export interface ConstraintFinding {
|
|
|
95
121
|
column?: number;
|
|
96
122
|
/** Machine-readable evaluator/source code. */
|
|
97
123
|
code?: string;
|
|
124
|
+
/** Whether this is a policy violation or an evaluator error. Absent means `violation`. */
|
|
125
|
+
kind?: FindingKind;
|
|
98
126
|
}
|
|
99
127
|
/** Aggregate result returned by a rule evaluator. */
|
|
100
128
|
export interface RuleEvaluationResult {
|
|
@@ -167,6 +195,7 @@ export declare const ConstraintRuleSchema: z.ZodObject<{
|
|
|
167
195
|
}, z.core.$strip>;
|
|
168
196
|
/** Zod schema for a constraint rule file. */
|
|
169
197
|
export declare const ConstraintRuleFileSchema: z.ZodObject<{
|
|
198
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
170
199
|
include: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
171
200
|
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
172
201
|
severity: z.ZodOptional<z.ZodEnum<{
|
|
@@ -202,6 +231,7 @@ export declare const ConstraintRuleFileSchema: z.ZodObject<{
|
|
|
202
231
|
}, z.core.$strip>;
|
|
203
232
|
/** Zod schema for a preset definition. */
|
|
204
233
|
export declare const PresetDefinitionSchema: z.ZodObject<{
|
|
234
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
205
235
|
name: z.ZodString;
|
|
206
236
|
extends: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
207
237
|
disable: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -214,5 +244,11 @@ export declare const PresetDefinitionSchema: z.ZodObject<{
|
|
|
214
244
|
}>;
|
|
215
245
|
}, z.core.$strip>>;
|
|
216
246
|
}, z.core.$strip>>>;
|
|
247
|
+
extensions: z.ZodOptional<z.ZodObject<{
|
|
248
|
+
resolvers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
249
|
+
evaluators: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
250
|
+
fixers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
251
|
+
formatters: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
252
|
+
}, z.core.$strip>>;
|
|
217
253
|
}, z.core.$strip>;
|
|
218
254
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,mDAAmD;AACnD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAExD,+CAA+C;AAC/C,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAElD,qDAAqD;AACrD,MAAM,WAAW,mBAAmB;IAChC,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC3B,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,QAAQ,EAAE,YAAY,CAAC;IACvB,+BAA+B;IAC/B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6BAA6B;IAC7B,GAAG,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC1B,4CAA4C;IAC5C,IAAI,EAAE,OAAO,CAAC;IACd,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IAC/B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,wBAAwB;IACxB,KAAK,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC7B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -33,6 +33,7 @@ export const ConstraintRuleSchema = z.object({
|
|
|
33
33
|
});
|
|
34
34
|
/** Zod schema for a constraint rule file. */
|
|
35
35
|
export const ConstraintRuleFileSchema = z.object({
|
|
36
|
+
$schema: z.string().optional(),
|
|
36
37
|
include: z.array(z.string()).optional(),
|
|
37
38
|
exclude: z.array(z.string()).optional(),
|
|
38
39
|
severity: z.enum(['error', 'warning', 'info']).optional(),
|
|
@@ -40,10 +41,19 @@ export const ConstraintRuleFileSchema = z.object({
|
|
|
40
41
|
});
|
|
41
42
|
/** Zod schema for a preset definition. */
|
|
42
43
|
export const PresetDefinitionSchema = z.object({
|
|
44
|
+
$schema: z.string().optional(),
|
|
43
45
|
name: z.string().min(1),
|
|
44
46
|
extends: z.array(z.string()).default([]),
|
|
45
47
|
disable: z.array(z.string()).optional(),
|
|
46
48
|
overrides: z
|
|
47
49
|
.record(z.string(), z.object({ fix: z.object({ mode: z.enum(['none', 'suggest', 'auto']) }).optional() }))
|
|
48
50
|
.optional(),
|
|
51
|
+
extensions: z
|
|
52
|
+
.object({
|
|
53
|
+
resolvers: z.array(z.string()).optional(),
|
|
54
|
+
evaluators: z.array(z.string()).optional(),
|
|
55
|
+
fixers: z.array(z.string()).optional(),
|
|
56
|
+
formatters: z.array(z.string()).optional(),
|
|
57
|
+
})
|
|
58
|
+
.optional(),
|
|
49
59
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-rule-engine",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "@gobing-ai/ts-rule-engine — Constraint rule schemas, loading, evaluation, and result formatting.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
|
+
"schemas",
|
|
35
36
|
"src",
|
|
36
37
|
"README.md"
|
|
37
38
|
],
|
|
@@ -47,8 +48,8 @@
|
|
|
47
48
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-rule-engine-v<version> && git push --tags' && exit 1"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"@gobing-ai/ts-ai-runner": "^0.2.
|
|
51
|
-
"@gobing-ai/ts-runtime": "^0.2.
|
|
51
|
+
"@gobing-ai/ts-ai-runner": "^0.2.8",
|
|
52
|
+
"@gobing-ai/ts-runtime": "^0.2.8",
|
|
52
53
|
"yaml": "^2.7.0",
|
|
53
54
|
"zod": "^4.1.0"
|
|
54
55
|
},
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/gobing-ai/ts-libs/main/packages/rule-engine/schemas/preset.schema.json",
|
|
4
|
+
"title": "@gobing-ai/ts-rule-engine Preset",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["name"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": { "type": "string" },
|
|
10
|
+
"name": { "type": "string" },
|
|
11
|
+
"extends": { "type": "array", "items": { "type": "string" } },
|
|
12
|
+
"disable": { "type": "array", "items": { "type": "string" } },
|
|
13
|
+
"overrides": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"properties": {
|
|
19
|
+
"fix": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"mode": { "enum": ["none", "suggest", "auto"] }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"extensions": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"properties": {
|
|
33
|
+
"resolvers": { "type": "array", "items": { "type": "string" } },
|
|
34
|
+
"evaluators": { "type": "array", "items": { "type": "string" } },
|
|
35
|
+
"fixers": { "type": "array", "items": { "type": "string" } },
|
|
36
|
+
"formatters": { "type": "array", "items": { "type": "string" } }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/gobing-ai/ts-libs/main/packages/rule-engine/schemas/rule-file.schema.json",
|
|
4
|
+
"title": "@gobing-ai/ts-rule-engine Rule File",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["rules"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": { "type": "string" },
|
|
10
|
+
"include": { "type": "array", "items": { "type": "string" } },
|
|
11
|
+
"exclude": { "type": "array", "items": { "type": "string" } },
|
|
12
|
+
"severity": { "enum": ["error", "warning", "info"] },
|
|
13
|
+
"rules": { "type": "array", "items": { "$ref": "#/$defs/rule" } }
|
|
14
|
+
},
|
|
15
|
+
"$defs": {
|
|
16
|
+
"rule": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"required": ["id", "evaluator"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"id": { "type": "string" },
|
|
22
|
+
"description": { "type": "string" },
|
|
23
|
+
"enabled": { "type": "boolean" },
|
|
24
|
+
"severity": { "enum": ["error", "warning", "info"] },
|
|
25
|
+
"include": { "type": "array", "items": { "type": "string" } },
|
|
26
|
+
"exclude": { "type": "array", "items": { "type": "string" } },
|
|
27
|
+
"evaluator": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"required": ["type"],
|
|
31
|
+
"properties": {
|
|
32
|
+
"type": { "type": "string" },
|
|
33
|
+
"config": { "type": "object", "additionalProperties": true }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"fix": { "$ref": "#/$defs/fix" }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"fix": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"properties": {
|
|
43
|
+
"mode": { "enum": ["none", "suggest", "auto"] },
|
|
44
|
+
"replacement": { "type": "string" },
|
|
45
|
+
"params": { "type": "object", "additionalProperties": true }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import type { RuleEngineHost } from '../host/rule-engine-host';
|
|
3
|
+
|
|
4
|
+
/** A capability kind a preset extension can contribute. */
|
|
5
|
+
export type ExtensionKind = 'resolvers' | 'evaluators' | 'fixers' | 'formatters';
|
|
6
|
+
|
|
7
|
+
/** A single extension module reference, resolved to an absolute path. */
|
|
8
|
+
export interface ExtensionRef {
|
|
9
|
+
/** Capability registry the module registers into. */
|
|
10
|
+
readonly kind: ExtensionKind;
|
|
11
|
+
/** Absolute path to the module to import. */
|
|
12
|
+
readonly absPath: string;
|
|
13
|
+
/** Name of the preset that declared this extension (for diagnostics). */
|
|
14
|
+
readonly presetName: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Options controlling preset-extension loading. */
|
|
18
|
+
export interface LoadExtensionsOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Whether to actually import extension modules. Defaults to `false`: loading
|
|
21
|
+
* arbitrary code referenced by a preset is a trust decision the caller must
|
|
22
|
+
* make explicitly. When refs exist and this is false, loading throws.
|
|
23
|
+
*/
|
|
24
|
+
allowExtensions?: boolean;
|
|
25
|
+
/** Optional sink for non-fatal warnings (e.g. built-in overrides). */
|
|
26
|
+
logger?: { warn: (message: string) => void };
|
|
27
|
+
/** Optional module loader seam for tests or embedders with custom import policy. */
|
|
28
|
+
moduleLoader?: (absPath: string) => Promise<Record<string, unknown>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Host registries that can receive extension capabilities (fixers live on the engine, not the host). */
|
|
32
|
+
const HOST_REGISTRY_BY_KIND: Partial<Record<ExtensionKind, 'resolvers' | 'evaluators' | 'formatters'>> = {
|
|
33
|
+
resolvers: 'resolvers',
|
|
34
|
+
evaluators: 'evaluators',
|
|
35
|
+
formatters: 'formatters',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Collect extension refs declared by a preset's `extensions` block.
|
|
40
|
+
*
|
|
41
|
+
* Paths are resolved relative to the preset file's directory. Use the returned
|
|
42
|
+
* refs with {@link loadExtensionsIntoHost}.
|
|
43
|
+
*/
|
|
44
|
+
export function collectPresetExtensions(
|
|
45
|
+
presetName: string,
|
|
46
|
+
presetDir: string,
|
|
47
|
+
extensions: Partial<Record<ExtensionKind, string[] | undefined>> | undefined,
|
|
48
|
+
): ExtensionRef[] {
|
|
49
|
+
if (extensions === undefined) return [];
|
|
50
|
+
const refs: ExtensionRef[] = [];
|
|
51
|
+
for (const kind of ['resolvers', 'evaluators', 'fixers', 'formatters'] as ExtensionKind[]) {
|
|
52
|
+
for (const path of extensions[kind] ?? []) {
|
|
53
|
+
refs.push({ kind, presetName, absPath: resolve(presetDir, path) });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return refs;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Import each extension module and register its export on the matching host
|
|
61
|
+
* registry.
|
|
62
|
+
*
|
|
63
|
+
* A module must default-export (or named-export `extension`) an object with a
|
|
64
|
+
* `name: string` and the capability implementation. Loading is gated by
|
|
65
|
+
* {@link LoadExtensionsOptions.allowExtensions}; when refs are present but
|
|
66
|
+
* loading is not allowed, this throws so the requirement is never silently dropped.
|
|
67
|
+
*
|
|
68
|
+
* @throws When extensions are present but `allowExtensions` is not true, or when
|
|
69
|
+
* a module cannot be imported or lacks a valid `name`.
|
|
70
|
+
*/
|
|
71
|
+
export async function loadExtensionsIntoHost(
|
|
72
|
+
host: RuleEngineHost,
|
|
73
|
+
refs: readonly ExtensionRef[],
|
|
74
|
+
options: LoadExtensionsOptions = {},
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
if (refs.length === 0) return;
|
|
77
|
+
if (options.allowExtensions !== true) {
|
|
78
|
+
const first = refs[0] as ExtensionRef;
|
|
79
|
+
throw new Error(
|
|
80
|
+
`preset "${first.presetName}" declares ${first.kind} extension "${first.absPath}", but extensions are disabled — pass allowExtensions: true to load preset extension modules`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const loadModule = options.moduleLoader ?? defaultModuleLoader;
|
|
85
|
+
for (const ref of refs) {
|
|
86
|
+
const moduleExports = await loadModule(ref.absPath);
|
|
87
|
+
const candidate = moduleExports.default ?? moduleExports.extension;
|
|
88
|
+
if (
|
|
89
|
+
candidate === null ||
|
|
90
|
+
typeof candidate !== 'object' ||
|
|
91
|
+
typeof (candidate as { name?: unknown }).name !== 'string'
|
|
92
|
+
) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`preset "${ref.presetName}" extension "${ref.absPath}" must export an object with a string "name"`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const name = (candidate as { name: string }).name;
|
|
98
|
+
const registryKey = HOST_REGISTRY_BY_KIND[ref.kind];
|
|
99
|
+
if (registryKey === undefined) {
|
|
100
|
+
throw new Error(`preset "${ref.presetName}" ${ref.kind} extensions are not supported`);
|
|
101
|
+
}
|
|
102
|
+
const registry = host[registryKey] as unknown as {
|
|
103
|
+
register: (name: string, impl: unknown, origin: 'builtin' | 'extension') => void;
|
|
104
|
+
has?: (name: string) => boolean;
|
|
105
|
+
};
|
|
106
|
+
if (options.logger && registry.has?.(name)) {
|
|
107
|
+
options.logger.warn(`preset "${ref.presetName}" ${ref.kind} extension overrides existing "${name}"`);
|
|
108
|
+
}
|
|
109
|
+
registry.register(name, candidate, 'extension');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function defaultModuleLoader(absPath: string): Promise<Record<string, unknown>> {
|
|
114
|
+
return (await import(absPath)) as Record<string, unknown>;
|
|
115
|
+
}
|