@ontrails/warden 1.0.0-beta.3 → 1.0.0-beta.30
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/CHANGELOG.md +625 -9
- package/README.md +111 -26
- package/bin/warden.ts +50 -0
- package/package.json +28 -5
- package/src/adapter-check.ts +136 -0
- package/src/ast.ts +137 -0
- package/src/cli.ts +1415 -104
- package/src/command.ts +943 -0
- package/src/config.ts +184 -0
- package/src/draft.ts +22 -0
- package/src/drift.ts +122 -22
- package/src/fix.ts +120 -0
- package/src/formatters.ts +79 -9
- package/src/guide.ts +245 -0
- package/src/index.ts +215 -14
- package/src/project-context.ts +163 -0
- package/src/project-rules.ts +290 -0
- package/src/resolve.ts +531 -0
- package/src/rules/activation-orphan.ts +97 -0
- package/src/rules/ast.ts +4008 -86
- package/src/rules/circular-refs.ts +154 -0
- package/src/rules/cli-command-route-coherence.ts +177 -0
- package/src/rules/composes-declarations.ts +837 -0
- package/src/rules/context-no-surface-types.ts +78 -15
- package/src/rules/contour-exists.ts +251 -0
- package/src/rules/contour-ids.ts +15 -0
- package/src/rules/dead-internal-trail.ts +161 -0
- package/src/rules/dead-public-trail.ts +258 -0
- package/src/rules/draft-file-marking.ts +160 -0
- package/src/rules/draft-visible-debt.ts +87 -0
- package/src/rules/duplicate-public-contract.ts +91 -0
- package/src/rules/error-mapping-completeness.ts +290 -0
- package/src/rules/example-valid.ts +395 -0
- package/src/rules/fires-declarations.ts +735 -0
- package/src/rules/implementation-returns-result.ts +1411 -166
- package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
- package/src/rules/incomplete-crud.ts +581 -0
- package/src/rules/index.ts +234 -18
- package/src/rules/intent-propagation.ts +127 -0
- package/src/rules/layer-field-name-drift.ts +102 -0
- package/src/rules/library-projection-coherence.ts +100 -0
- package/src/rules/metadata.ts +755 -0
- package/src/rules/missing-reconcile.ts +98 -0
- package/src/rules/missing-visibility.ts +110 -0
- package/src/rules/no-destructured-compose.ts +194 -0
- package/src/rules/no-dev-permit-in-source.ts +99 -0
- package/src/rules/no-direct-implementation-call.ts +7 -7
- package/src/rules/no-legacy-layer-imports.ts +211 -0
- package/src/rules/no-native-error-result.ts +118 -0
- package/src/rules/no-redundant-result-error-wrap.ts +384 -0
- package/src/rules/no-retired-cross-vocabulary.ts +194 -0
- package/src/rules/no-sync-result-assumption.ts +1135 -98
- package/src/rules/no-throw-in-detour-recover.ts +225 -0
- package/src/rules/no-throw-in-implementation.ts +10 -9
- package/src/rules/no-top-level-surface.ts +371 -0
- package/src/rules/on-references-exist.ts +194 -0
- package/src/rules/orphaned-signal.ts +150 -0
- package/src/rules/owner-projection-parity.ts +143 -0
- package/src/rules/permit-governance.ts +25 -0
- package/src/rules/public-export-example-coverage.ts +562 -0
- package/src/rules/public-internal-deep-imports.ts +517 -0
- package/src/rules/public-output-schema.ts +29 -0
- package/src/rules/public-union-output-discriminants.ts +150 -0
- package/src/rules/read-intent-fires.ts +187 -0
- package/src/rules/reference-exists.ts +98 -0
- package/src/rules/registry-names.ts +155 -0
- package/src/rules/resolved-import-boundary.ts +146 -0
- package/src/rules/resource-declarations.ts +693 -0
- package/src/rules/resource-exists.ts +179 -0
- package/src/rules/resource-id-grammar.ts +65 -0
- package/src/rules/resource-mock-coverage.ts +115 -0
- package/src/rules/scan.ts +38 -25
- package/src/rules/scheduled-destroy-intent.ts +44 -0
- package/src/rules/signal-graph-coaching.ts +191 -0
- package/src/rules/specs.ts +9 -5
- package/src/rules/static-resource-accessor-preference.ts +649 -0
- package/src/rules/surface-facet-coherence.ts +362 -0
- package/src/rules/trail-fork-coaching.ts +616 -0
- package/src/rules/trail-versioning-source.ts +1072 -0
- package/src/rules/trail-versioning-topo.ts +172 -0
- package/src/rules/types.ts +272 -6
- package/src/rules/unmaterialized-activation-source.ts +84 -0
- package/src/rules/unreachable-detour-shadowing.ts +339 -0
- package/src/rules/valid-describe-refs.ts +162 -32
- package/src/rules/valid-detour-contract.ts +78 -0
- package/src/rules/warden-export-symmetry.ts +540 -0
- package/src/rules/warden-rules-use-ast.ts +1114 -0
- package/src/rules/webhook-route-collision.ts +243 -0
- package/src/trails/activation-orphan.trail.ts +84 -0
- package/src/trails/circular-refs.trail.ts +29 -0
- package/src/trails/cli-command-route-coherence.trail.ts +47 -0
- package/src/trails/composes-declarations.trail.ts +22 -0
- package/src/trails/context-no-surface-types.trail.ts +21 -0
- package/src/trails/contour-exists.trail.ts +21 -0
- package/src/trails/dead-internal-trail.trail.ts +26 -0
- package/src/trails/dead-public-trail.trail.ts +31 -0
- package/src/trails/deprecation-without-guidance.trail.ts +21 -0
- package/src/trails/draft-file-marking.trail.ts +16 -0
- package/src/trails/draft-visible-debt.trail.ts +16 -0
- package/src/trails/duplicate-public-contract.trail.ts +47 -0
- package/src/trails/error-mapping-completeness.trail.ts +30 -0
- package/src/trails/example-valid.trail.ts +25 -0
- package/src/trails/fires-declarations.trail.ts +23 -0
- package/src/trails/fork-without-preserved-blaze.trail.ts +31 -0
- package/src/trails/implementation-returns-result.trail.ts +20 -0
- package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
- package/src/trails/incomplete-crud.trail.ts +39 -0
- package/src/trails/index.ts +83 -0
- package/src/trails/intent-propagation.trail.ts +30 -0
- package/src/trails/layer-field-name-drift.trail.ts +39 -0
- package/src/trails/library-projection-coherence.trail.ts +43 -0
- package/src/trails/marker-schema-unsupported.trail.ts +23 -0
- package/src/trails/missing-reconcile.trail.ts +33 -0
- package/src/trails/missing-visibility.trail.ts +22 -0
- package/src/trails/no-destructured-compose.trail.ts +44 -0
- package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
- package/src/trails/no-direct-implementation-call.trail.ts +16 -0
- package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
- package/src/trails/no-native-error-result.trail.ts +18 -0
- package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
- package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
- package/src/trails/no-sync-result-assumption.trail.ts +19 -0
- package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
- package/src/trails/no-throw-in-implementation.trail.ts +20 -0
- package/src/trails/no-top-level-surface.trail.ts +43 -0
- package/src/trails/on-references-exist.trail.ts +21 -0
- package/src/trails/orphaned-signal.trail.ts +36 -0
- package/src/trails/owner-projection-parity.trail.ts +26 -0
- package/src/trails/pending-force.trail.ts +21 -0
- package/src/trails/permit-governance.trail.ts +51 -0
- package/src/trails/prefer-schema-inference.trail.ts +21 -0
- package/src/trails/public-export-example-coverage.trail.ts +16 -0
- package/src/trails/public-internal-deep-imports.trail.ts +94 -0
- package/src/trails/public-output-schema.trail.ts +55 -0
- package/src/trails/public-union-output-discriminants.trail.ts +33 -0
- package/src/trails/read-intent-fires.trail.ts +20 -0
- package/src/trails/reference-exists.trail.ts +25 -0
- package/src/trails/resolved-import-boundary.trail.ts +109 -0
- package/src/trails/resource-declarations.trail.ts +25 -0
- package/src/trails/resource-exists.trail.ts +27 -0
- package/src/trails/resource-id-grammar.trail.ts +39 -0
- package/src/trails/resource-mock-coverage.trail.ts +40 -0
- package/src/trails/run.ts +162 -0
- package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
- package/src/trails/schema.ts +198 -0
- package/src/trails/signal-graph-coaching.trail.ts +77 -0
- package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
- package/src/trails/surface-facet-coherence.trail.ts +25 -0
- package/src/trails/topo.ts +6 -0
- package/src/trails/trail-fork-coaching.trail.ts +42 -0
- package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
- package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
- package/src/trails/valid-describe-refs.trail.ts +18 -0
- package/src/trails/valid-detour-contract.trail.ts +71 -0
- package/src/trails/version-gap.trail.ts +35 -0
- package/src/trails/version-pinned-compose.trail.ts +23 -0
- package/src/trails/version-without-examples.trail.ts +38 -0
- package/src/trails/warden-export-symmetry.trail.ts +16 -0
- package/src/trails/warden-rules-use-ast.trail.ts +64 -0
- package/src/trails/webhook-route-collision.trail.ts +50 -0
- package/src/trails/wrap-rule.ts +214 -0
- package/src/workspaces.ts +238 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/cli.d.ts +0 -46
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -221
- package/dist/cli.js.map +0 -1
- package/dist/drift.d.ts +0 -26
- package/dist/drift.d.ts.map +0 -1
- package/dist/drift.js +0 -27
- package/dist/drift.js.map +0 -1
- package/dist/formatters.d.ts +0 -29
- package/dist/formatters.d.ts.map +0 -1
- package/dist/formatters.js +0 -87
- package/dist/formatters.js.map +0 -1
- package/dist/index.d.ts +0 -26
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
- package/dist/rules/ast.d.ts +0 -41
- package/dist/rules/ast.d.ts.map +0 -1
- package/dist/rules/ast.js +0 -163
- package/dist/rules/ast.js.map +0 -1
- package/dist/rules/context-no-surface-types.d.ts +0 -12
- package/dist/rules/context-no-surface-types.d.ts.map +0 -1
- package/dist/rules/context-no-surface-types.js +0 -96
- package/dist/rules/context-no-surface-types.js.map +0 -1
- package/dist/rules/implementation-returns-result.d.ts +0 -13
- package/dist/rules/implementation-returns-result.d.ts.map +0 -1
- package/dist/rules/implementation-returns-result.js +0 -277
- package/dist/rules/implementation-returns-result.js.map +0 -1
- package/dist/rules/index.d.ts +0 -22
- package/dist/rules/index.d.ts.map +0 -1
- package/dist/rules/index.js +0 -41
- package/dist/rules/index.js.map +0 -1
- package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
- package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
- package/dist/rules/no-direct-impl-in-route.js +0 -46
- package/dist/rules/no-direct-impl-in-route.js.map +0 -1
- package/dist/rules/no-direct-implementation-call.d.ts +0 -12
- package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
- package/dist/rules/no-direct-implementation-call.js +0 -39
- package/dist/rules/no-direct-implementation-call.js.map +0 -1
- package/dist/rules/no-sync-result-assumption.d.ts +0 -6
- package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
- package/dist/rules/no-sync-result-assumption.js +0 -98
- package/dist/rules/no-sync-result-assumption.js.map +0 -1
- package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
- package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
- package/dist/rules/no-throw-in-detour-target.js +0 -87
- package/dist/rules/no-throw-in-detour-target.js.map +0 -1
- package/dist/rules/no-throw-in-implementation.d.ts +0 -9
- package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
- package/dist/rules/no-throw-in-implementation.js +0 -34
- package/dist/rules/no-throw-in-implementation.js.map +0 -1
- package/dist/rules/prefer-schema-inference.d.ts +0 -7
- package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
- package/dist/rules/prefer-schema-inference.js +0 -86
- package/dist/rules/prefer-schema-inference.js.map +0 -1
- package/dist/rules/scan.d.ts +0 -8
- package/dist/rules/scan.d.ts.map +0 -1
- package/dist/rules/scan.js +0 -32
- package/dist/rules/scan.js.map +0 -1
- package/dist/rules/specs.d.ts +0 -29
- package/dist/rules/specs.d.ts.map +0 -1
- package/dist/rules/specs.js +0 -192
- package/dist/rules/specs.js.map +0 -1
- package/dist/rules/structure.d.ts +0 -13
- package/dist/rules/structure.d.ts.map +0 -1
- package/dist/rules/structure.js +0 -142
- package/dist/rules/structure.js.map +0 -1
- package/dist/rules/types.d.ts +0 -52
- package/dist/rules/types.d.ts.map +0 -1
- package/dist/rules/types.js +0 -2
- package/dist/rules/types.js.map +0 -1
- package/dist/rules/valid-describe-refs.d.ts +0 -7
- package/dist/rules/valid-describe-refs.d.ts.map +0 -1
- package/dist/rules/valid-describe-refs.js +0 -51
- package/dist/rules/valid-describe-refs.js.map +0 -1
- package/dist/rules/valid-detour-refs.d.ts +0 -6
- package/dist/rules/valid-detour-refs.d.ts.map +0 -1
- package/dist/rules/valid-detour-refs.js +0 -116
- package/dist/rules/valid-detour-refs.js.map +0 -1
- package/src/__tests__/cli.test.ts +0 -198
- package/src/__tests__/drift.test.ts +0 -74
- package/src/__tests__/formatters.test.ts +0 -157
- package/src/__tests__/implementation-returns-result.test.ts +0 -128
- package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
- package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
- package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
- package/src/__tests__/prefer-schema-inference.test.ts +0 -84
- package/src/__tests__/rules.test.ts +0 -215
- package/src/__tests__/valid-describe-refs.test.ts +0 -60
- package/src/rules/no-direct-impl-in-route.ts +0 -77
- package/src/rules/no-throw-in-detour-target.ts +0 -150
- package/src/rules/valid-detour-refs.ts +0 -187
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory that wraps a WardenRule as a trail.
|
|
3
|
+
*
|
|
4
|
+
* Keeps each rule trail file minimal — just the import + examples.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { InternalError, trail, Result } from '@ontrails/core';
|
|
8
|
+
import type { Trail } from '@ontrails/core';
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
ProjectAwareWardenRule,
|
|
12
|
+
ProjectContext,
|
|
13
|
+
TopoAwareWardenRule,
|
|
14
|
+
WardenRule,
|
|
15
|
+
} from '../rules/types.js';
|
|
16
|
+
import { getWardenRuleMetadata } from '../rules/metadata.js';
|
|
17
|
+
import {
|
|
18
|
+
projectAwareRuleInput,
|
|
19
|
+
ruleInput,
|
|
20
|
+
ruleOutput,
|
|
21
|
+
topoAwareRuleInput,
|
|
22
|
+
} from './schema.js';
|
|
23
|
+
import type {
|
|
24
|
+
ProjectAwareRuleInput,
|
|
25
|
+
RuleInput,
|
|
26
|
+
RuleOutput,
|
|
27
|
+
TopoAwareRuleInput,
|
|
28
|
+
} from './schema.js';
|
|
29
|
+
|
|
30
|
+
interface WrapRuleOptions {
|
|
31
|
+
/** The existing warden rule to wrap. */
|
|
32
|
+
readonly rule: WardenRule;
|
|
33
|
+
/** Trail examples for testing and documentation. */
|
|
34
|
+
readonly examples: Trail<RuleInput, RuleOutput>['examples'];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface WrapProjectAwareRuleOptions {
|
|
38
|
+
/** The existing project-aware warden rule to wrap. */
|
|
39
|
+
readonly rule: ProjectAwareWardenRule;
|
|
40
|
+
/** Trail examples for testing and documentation. */
|
|
41
|
+
readonly examples: Trail<ProjectAwareRuleInput, RuleOutput>['examples'];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const buildRuleMeta = (rule: WardenRule | TopoAwareWardenRule) => {
|
|
45
|
+
const metadata = getWardenRuleMetadata(rule);
|
|
46
|
+
return {
|
|
47
|
+
category: 'governance',
|
|
48
|
+
...(metadata ? { warden: metadata } : {}),
|
|
49
|
+
severity: rule.severity,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const buildProjectContext = (input: ProjectAwareRuleInput): ProjectContext => ({
|
|
54
|
+
...(input.contourReferencesByName
|
|
55
|
+
? {
|
|
56
|
+
contourReferencesByName: new Map(
|
|
57
|
+
Object.entries(input.contourReferencesByName)
|
|
58
|
+
),
|
|
59
|
+
}
|
|
60
|
+
: {}),
|
|
61
|
+
...(input.crudTableIds ? { crudTableIds: new Set(input.crudTableIds) } : {}),
|
|
62
|
+
...(input.crudCoverageByEntity
|
|
63
|
+
? {
|
|
64
|
+
crudCoverageByEntity: new Map(
|
|
65
|
+
Object.entries(input.crudCoverageByEntity).map(
|
|
66
|
+
([entityId, operations]) => [
|
|
67
|
+
entityId,
|
|
68
|
+
new Set(operations) as ReadonlySet<string>,
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
),
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
...(input.knownContourIds
|
|
75
|
+
? { knownContourIds: new Set(input.knownContourIds) }
|
|
76
|
+
: {}),
|
|
77
|
+
...(input.importResolutionsByFile
|
|
78
|
+
? {
|
|
79
|
+
importResolutionsByFile: new Map(
|
|
80
|
+
Object.entries(input.importResolutionsByFile)
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
: {}),
|
|
84
|
+
...(input.documentedImportResolutionsByFile
|
|
85
|
+
? {
|
|
86
|
+
documentedImportResolutionsByFile: new Map(
|
|
87
|
+
Object.entries(input.documentedImportResolutionsByFile)
|
|
88
|
+
),
|
|
89
|
+
}
|
|
90
|
+
: {}),
|
|
91
|
+
...(input.publicWorkspaces
|
|
92
|
+
? { publicWorkspaces: new Map(Object.entries(input.publicWorkspaces)) }
|
|
93
|
+
: {}),
|
|
94
|
+
knownTrailIds: input.knownTrailIds
|
|
95
|
+
? new Set(input.knownTrailIds)
|
|
96
|
+
: new Set<string>(),
|
|
97
|
+
...(input.composeTargetTrailIds
|
|
98
|
+
? { composeTargetTrailIds: new Set(input.composeTargetTrailIds) }
|
|
99
|
+
: {}),
|
|
100
|
+
...(input.knownResourceIds
|
|
101
|
+
? { knownResourceIds: new Set(input.knownResourceIds) }
|
|
102
|
+
: {}),
|
|
103
|
+
...(input.knownSignalIds
|
|
104
|
+
? { knownSignalIds: new Set(input.knownSignalIds) }
|
|
105
|
+
: {}),
|
|
106
|
+
...(input.onTargetSignalIds
|
|
107
|
+
? { onTargetSignalIds: new Set(input.onTargetSignalIds) }
|
|
108
|
+
: {}),
|
|
109
|
+
...(input.reconcileTableIds
|
|
110
|
+
? { reconcileTableIds: new Set(input.reconcileTableIds) }
|
|
111
|
+
: {}),
|
|
112
|
+
...(input.trailIntentsById
|
|
113
|
+
? { trailIntentsById: new Map(Object.entries(input.trailIntentsById)) }
|
|
114
|
+
: {}),
|
|
115
|
+
...(input.topoTrailIds ? { topoTrailIds: new Set(input.topoTrailIds) } : {}),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Wrap an existing `WardenRule` as a trail with typed input/output.
|
|
120
|
+
*
|
|
121
|
+
* The trail ID follows the pattern `warden.rule.<rule-name>`.
|
|
122
|
+
*/
|
|
123
|
+
export function wrapRule(
|
|
124
|
+
options: WrapProjectAwareRuleOptions
|
|
125
|
+
): Trail<ProjectAwareRuleInput, RuleOutput>;
|
|
126
|
+
export function wrapRule(
|
|
127
|
+
options: WrapRuleOptions
|
|
128
|
+
): Trail<RuleInput, RuleOutput>;
|
|
129
|
+
export function wrapRule(
|
|
130
|
+
options: WrapRuleOptions | WrapProjectAwareRuleOptions
|
|
131
|
+
): Trail<RuleInput, RuleOutput> | Trail<ProjectAwareRuleInput, RuleOutput> {
|
|
132
|
+
const { rule, examples } = options;
|
|
133
|
+
const isProjectAware = 'checkWithContext' in rule;
|
|
134
|
+
|
|
135
|
+
if (isProjectAware) {
|
|
136
|
+
const projectAwareRule = rule as ProjectAwareWardenRule;
|
|
137
|
+
return trail(`warden.rule.${rule.name}`, {
|
|
138
|
+
blaze: (input: ProjectAwareRuleInput) => {
|
|
139
|
+
const diagnostics = projectAwareRule.checkWithContext(
|
|
140
|
+
input.sourceCode,
|
|
141
|
+
input.filePath,
|
|
142
|
+
buildProjectContext(input)
|
|
143
|
+
);
|
|
144
|
+
return Result.ok({ diagnostics: [...diagnostics] });
|
|
145
|
+
},
|
|
146
|
+
description: rule.description,
|
|
147
|
+
examples: examples as Trail<
|
|
148
|
+
ProjectAwareRuleInput,
|
|
149
|
+
RuleOutput
|
|
150
|
+
>['examples'],
|
|
151
|
+
input: projectAwareRuleInput,
|
|
152
|
+
intent: 'read',
|
|
153
|
+
meta: buildRuleMeta(rule),
|
|
154
|
+
output: ruleOutput,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return trail(`warden.rule.${rule.name}`, {
|
|
159
|
+
blaze: (input: RuleInput) => {
|
|
160
|
+
const diagnostics = rule.check(input.sourceCode, input.filePath);
|
|
161
|
+
return Result.ok({ diagnostics: [...diagnostics] });
|
|
162
|
+
},
|
|
163
|
+
description: rule.description,
|
|
164
|
+
examples: examples as Trail<RuleInput, RuleOutput>['examples'],
|
|
165
|
+
input: ruleInput,
|
|
166
|
+
intent: 'read',
|
|
167
|
+
meta: buildRuleMeta(rule),
|
|
168
|
+
output: ruleOutput,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface WrapTopoRuleOptions {
|
|
173
|
+
/** The existing topo-aware warden rule to wrap. */
|
|
174
|
+
readonly rule: TopoAwareWardenRule;
|
|
175
|
+
/** Trail examples for testing and documentation. */
|
|
176
|
+
readonly examples: Trail<TopoAwareRuleInput, RuleOutput>['examples'];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Wrap an existing `TopoAwareWardenRule` as a trail.
|
|
181
|
+
*
|
|
182
|
+
* Mirrors `wrapRule` for the per-topo dispatch path. Topo-aware rules run
|
|
183
|
+
* once per topo against the compiled runtime graph rather than per file,
|
|
184
|
+
* so the trail accepts the live `Topo` as input.
|
|
185
|
+
*/
|
|
186
|
+
export const wrapTopoRule = (
|
|
187
|
+
options: WrapTopoRuleOptions
|
|
188
|
+
): Trail<TopoAwareRuleInput, RuleOutput> => {
|
|
189
|
+
const { rule, examples } = options;
|
|
190
|
+
return trail(`warden.rule.${rule.name}`, {
|
|
191
|
+
blaze: async (input: TopoAwareRuleInput) => {
|
|
192
|
+
try {
|
|
193
|
+
const diagnostics = await rule.checkTopo(input.topo, {
|
|
194
|
+
graph: input.graph,
|
|
195
|
+
});
|
|
196
|
+
return Result.ok({ diagnostics: [...diagnostics] });
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
199
|
+
return Result.err(
|
|
200
|
+
new InternalError(
|
|
201
|
+
`Topo-aware rule "${rule.name}" threw while inspecting topo: ${cause.message}`,
|
|
202
|
+
{ cause }
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
description: rule.description,
|
|
208
|
+
examples,
|
|
209
|
+
input: topoAwareRuleInput,
|
|
210
|
+
intent: 'read',
|
|
211
|
+
meta: buildRuleMeta(rule),
|
|
212
|
+
output: ruleOutput,
|
|
213
|
+
});
|
|
214
|
+
};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-manifest discovery for Warden project-aware rules.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs';
|
|
6
|
+
import { dirname, join, resolve } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export interface WardenPublicWorkspace {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly rootDir: string;
|
|
11
|
+
readonly packageJsonPath: string;
|
|
12
|
+
readonly hasExports: boolean;
|
|
13
|
+
readonly bin?: Readonly<Record<string, string>> | undefined;
|
|
14
|
+
readonly exportTargets?: Readonly<Record<string, string>> | undefined;
|
|
15
|
+
readonly files?: readonly string[] | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface RootManifest {
|
|
19
|
+
readonly workspaces?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface PackageManifest {
|
|
23
|
+
readonly bin?: unknown;
|
|
24
|
+
readonly exports?: unknown;
|
|
25
|
+
readonly files?: unknown;
|
|
26
|
+
readonly name?: unknown;
|
|
27
|
+
readonly private?: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const normalizePath = (path: string): string => path.replaceAll('\\', '/');
|
|
31
|
+
|
|
32
|
+
const normalizeRealPath = (path: string): string => {
|
|
33
|
+
try {
|
|
34
|
+
return normalizePath(realpathSync(path));
|
|
35
|
+
} catch {
|
|
36
|
+
return normalizePath(resolve(path));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
41
|
+
Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
42
|
+
|
|
43
|
+
const readJson = <T>(path: string): T | undefined => {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(readFileSync(path, 'utf8')) as T;
|
|
46
|
+
} catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const workspacePatternsFromManifest = (
|
|
52
|
+
manifest: RootManifest | undefined
|
|
53
|
+
): readonly string[] => {
|
|
54
|
+
const { workspaces } = manifest ?? {};
|
|
55
|
+
if (Array.isArray(workspaces)) {
|
|
56
|
+
return workspaces.filter(
|
|
57
|
+
(pattern): pattern is string => typeof pattern === 'string'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const packages = isRecord(workspaces) ? workspaces['packages'] : undefined;
|
|
62
|
+
return Array.isArray(packages)
|
|
63
|
+
? packages.filter(
|
|
64
|
+
(pattern): pattern is string => typeof pattern === 'string'
|
|
65
|
+
)
|
|
66
|
+
: [];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const workspaceDirsForPattern = (
|
|
70
|
+
rootDir: string,
|
|
71
|
+
pattern: string
|
|
72
|
+
): readonly string[] => {
|
|
73
|
+
if (!pattern.endsWith('/*')) {
|
|
74
|
+
const workspaceDir = join(rootDir, pattern);
|
|
75
|
+
return existsSync(workspaceDir) ? [workspaceDir] : [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const groupDir = join(rootDir, pattern.slice(0, -2));
|
|
79
|
+
if (!existsSync(groupDir)) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return readdirSync(groupDir, { withFileTypes: true })
|
|
84
|
+
.filter((entry) => entry.isDirectory())
|
|
85
|
+
.map((entry) => join(groupDir, entry.name))
|
|
86
|
+
.toSorted();
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const packageLocalName = (name: string): string =>
|
|
90
|
+
name.split('/').at(-1) ?? name;
|
|
91
|
+
|
|
92
|
+
const normalizeBin = (
|
|
93
|
+
name: string,
|
|
94
|
+
value: unknown
|
|
95
|
+
): Readonly<Record<string, string>> | undefined => {
|
|
96
|
+
if (typeof value === 'string') {
|
|
97
|
+
return { [packageLocalName(name)]: value };
|
|
98
|
+
}
|
|
99
|
+
if (!isRecord(value)) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const entries = Object.entries(value).filter(
|
|
104
|
+
(entry): entry is [string, string] => typeof entry[1] === 'string'
|
|
105
|
+
);
|
|
106
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const normalizeFiles = (value: unknown): readonly string[] | undefined => {
|
|
110
|
+
if (!Array.isArray(value)) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const files = value.filter(
|
|
115
|
+
(entry): entry is string => typeof entry === 'string'
|
|
116
|
+
);
|
|
117
|
+
return files.length > 0 ? files : undefined;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const resolveExportTarget = (
|
|
121
|
+
target: unknown,
|
|
122
|
+
depth = 0
|
|
123
|
+
): string | undefined => {
|
|
124
|
+
if (typeof target === 'string') {
|
|
125
|
+
return target;
|
|
126
|
+
}
|
|
127
|
+
if (!isRecord(target) || depth > 8) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const condition of ['bun', 'import', 'default', 'require'] as const) {
|
|
132
|
+
const conditionalTarget = target[condition];
|
|
133
|
+
const resolvedTarget = resolveExportTarget(conditionalTarget, depth + 1);
|
|
134
|
+
if (resolvedTarget) {
|
|
135
|
+
return resolvedTarget;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const exportSpecifierFromKey = (
|
|
142
|
+
packageName: string,
|
|
143
|
+
key: string
|
|
144
|
+
): string | undefined => {
|
|
145
|
+
if (key === '.') {
|
|
146
|
+
return packageName;
|
|
147
|
+
}
|
|
148
|
+
if (!key.startsWith('./') || key.includes('*')) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
return `${packageName}/${key.slice(2)}`;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const normalizeExportTargets = (
|
|
155
|
+
packageDir: string,
|
|
156
|
+
packageName: string,
|
|
157
|
+
exportsValue: unknown
|
|
158
|
+
): Readonly<Record<string, string>> | undefined => {
|
|
159
|
+
if (!isRecord(exportsValue)) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const targets: Record<string, string> = {};
|
|
164
|
+
for (const [key, value] of Object.entries(exportsValue)) {
|
|
165
|
+
const specifier = exportSpecifierFromKey(packageName, key);
|
|
166
|
+
const target = resolveExportTarget(value);
|
|
167
|
+
if (!specifier || !target) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
targets[specifier] = normalizeRealPath(join(packageDir, target));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return Object.keys(targets).length > 0 ? targets : undefined;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const publicWorkspaceFromManifest = (
|
|
177
|
+
packageJsonPath: string,
|
|
178
|
+
manifest: PackageManifest
|
|
179
|
+
): WardenPublicWorkspace | undefined => {
|
|
180
|
+
if (typeof manifest.name !== 'string') {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
if (!manifest.name.startsWith('@ontrails/')) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
if (manifest.private === true) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const packageDir = dirname(packageJsonPath);
|
|
191
|
+
const bin = normalizeBin(manifest.name, manifest.bin);
|
|
192
|
+
const exportTargets = normalizeExportTargets(
|
|
193
|
+
packageDir,
|
|
194
|
+
manifest.name,
|
|
195
|
+
manifest.exports
|
|
196
|
+
);
|
|
197
|
+
const files = normalizeFiles(manifest.files);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
...(bin ? { bin } : {}),
|
|
201
|
+
...(exportTargets ? { exportTargets } : {}),
|
|
202
|
+
...(files ? { files } : {}),
|
|
203
|
+
hasExports: manifest.exports !== undefined,
|
|
204
|
+
name: manifest.name,
|
|
205
|
+
packageJsonPath: normalizeRealPath(packageJsonPath),
|
|
206
|
+
rootDir: normalizeRealPath(dirname(packageJsonPath)),
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const collectPublicWorkspaces = (
|
|
211
|
+
rootDir: string
|
|
212
|
+
): ReadonlyMap<string, WardenPublicWorkspace> => {
|
|
213
|
+
const normalizedRoot = normalizeRealPath(rootDir);
|
|
214
|
+
const rootManifest = readJson<RootManifest>(
|
|
215
|
+
join(normalizedRoot, 'package.json')
|
|
216
|
+
);
|
|
217
|
+
const workspaces = new Map<string, WardenPublicWorkspace>();
|
|
218
|
+
|
|
219
|
+
for (const pattern of workspacePatternsFromManifest(rootManifest)) {
|
|
220
|
+
for (const workspaceDir of workspaceDirsForPattern(
|
|
221
|
+
normalizedRoot,
|
|
222
|
+
pattern
|
|
223
|
+
)) {
|
|
224
|
+
const packageJsonPath = join(workspaceDir, 'package.json');
|
|
225
|
+
const manifest = readJson<PackageManifest>(packageJsonPath);
|
|
226
|
+
if (!manifest) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const workspace = publicWorkspaceFromManifest(packageJsonPath, manifest);
|
|
231
|
+
if (workspace) {
|
|
232
|
+
workspaces.set(workspace.name, workspace);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return workspaces;
|
|
238
|
+
};
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc -b
|
package/.turbo/turbo-lint.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc --noEmit
|
package/dist/cli.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Warden CLI command runner.
|
|
3
|
-
*
|
|
4
|
-
* Scans TypeScript files, runs all warden rules, optionally checks drift,
|
|
5
|
-
* and returns a structured report.
|
|
6
|
-
*/
|
|
7
|
-
import type { Topo } from '@ontrails/core';
|
|
8
|
-
import type { DriftResult } from './drift.js';
|
|
9
|
-
import type { WardenDiagnostic } from './rules/types.js';
|
|
10
|
-
/**
|
|
11
|
-
* Options for the warden CLI runner.
|
|
12
|
-
*/
|
|
13
|
-
export interface WardenOptions {
|
|
14
|
-
/** Root directory to scan for TypeScript files. Defaults to cwd. */
|
|
15
|
-
readonly rootDir?: string | undefined;
|
|
16
|
-
/** Only run lint rules, skip drift detection */
|
|
17
|
-
readonly lintOnly?: boolean | undefined;
|
|
18
|
-
/** Only run drift detection, skip lint rules */
|
|
19
|
-
readonly driftOnly?: boolean | undefined;
|
|
20
|
-
/** App topology for drift detection. When provided, enables real surface lock comparison. */
|
|
21
|
-
readonly topo?: Topo | undefined;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Result of a warden run.
|
|
25
|
-
*/
|
|
26
|
-
export interface WardenReport {
|
|
27
|
-
/** All diagnostics from lint rules */
|
|
28
|
-
readonly diagnostics: readonly WardenDiagnostic[];
|
|
29
|
-
/** Count of error-severity diagnostics */
|
|
30
|
-
readonly errorCount: number;
|
|
31
|
-
/** Count of warn-severity diagnostics */
|
|
32
|
-
readonly warnCount: number;
|
|
33
|
-
/** Drift detection result, or null if skipped */
|
|
34
|
-
readonly drift: DriftResult | null;
|
|
35
|
-
/** Whether the warden run passed (no errors, no drift) */
|
|
36
|
-
readonly passed: boolean;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Run all warden checks and return a structured report.
|
|
40
|
-
*/
|
|
41
|
-
export declare const runWarden: (options?: WardenOptions) => Promise<WardenReport>;
|
|
42
|
-
/**
|
|
43
|
-
* Format a warden report as a human-readable string.
|
|
44
|
-
*/
|
|
45
|
-
export declare const formatWardenReport: (report: WardenReport) => string;
|
|
46
|
-
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C,OAAO,KAAK,EAGV,gBAAgB,EAEjB,MAAM,kBAAkB,CAAC;AAE1B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,gDAAgD;IAChD,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD,0CAA0C;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAyLD;;GAEG;AACH,eAAO,MAAM,SAAS,GACpB,UAAS,aAAkB,KAC1B,OAAO,CAAC,YAAY,CAqBtB,CAAC;AAsDF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,YAAY,KAAG,MAmBzD,CAAC"}
|