@ontrails/warden 1.0.0-beta.2 → 1.0.0-beta.22
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 +508 -6
- package/README.md +77 -26
- package/bin/warden.ts +50 -0
- package/package.json +27 -5
- package/src/adapter-check.ts +136 -0
- package/src/ast.ts +28 -0
- package/src/cli.ts +1374 -103
- package/src/command.ts +953 -0
- package/src/config.ts +184 -0
- package/src/draft.ts +22 -0
- package/src/drift.ts +106 -22
- package/src/fix.ts +120 -0
- package/src/formatters.ts +79 -9
- package/src/guide.ts +245 -0
- package/src/index.ts +206 -14
- package/src/project-context.ts +163 -0
- package/src/resolve.ts +530 -0
- package/src/rules/activation-orphan.ts +97 -0
- package/src/rules/ast.ts +3176 -85
- package/src/rules/circular-refs.ts +154 -0
- package/src/rules/composes-declarations.ts +704 -0
- package/src/rules/context-no-surface-types.ts +68 -8
- package/src/rules/contour-exists.ts +251 -0
- package/src/rules/contour-ids.ts +15 -0
- package/src/rules/dead-internal-trail.ts +154 -0
- package/src/rules/draft-file-marking.ts +160 -0
- package/src/rules/draft-visible-debt.ts +87 -0
- package/src/rules/error-mapping-completeness.ts +288 -0
- package/src/rules/example-valid.ts +401 -0
- package/src/rules/fires-declarations.ts +758 -0
- package/src/rules/implementation-returns-result.ts +1265 -95
- package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
- package/src/rules/incomplete-crud.ts +580 -0
- package/src/rules/index.ts +219 -18
- package/src/rules/intent-propagation.ts +127 -0
- package/src/rules/layer-field-name-drift.ts +96 -0
- package/src/rules/metadata.ts +654 -0
- package/src/rules/missing-reconcile.ts +98 -0
- package/src/rules/missing-visibility.ts +110 -0
- package/src/rules/no-destructured-compose.ts +192 -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 +111 -0
- package/src/rules/no-redundant-result-error-wrap.ts +331 -0
- package/src/rules/no-retired-cross-vocabulary.ts +194 -0
- package/src/rules/no-sync-result-assumption.ts +1134 -99
- 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 +389 -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 +146 -0
- package/src/rules/permit-governance.ts +25 -0
- package/src/rules/public-export-example-coverage.ts +553 -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 +145 -0
- package/src/rules/resolved-import-boundary.ts +146 -0
- package/src/rules/resource-declarations.ts +704 -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 +657 -0
- package/src/rules/surface-facet-coherence.ts +370 -0
- package/src/rules/trail-versioning-source.ts +1094 -0
- package/src/rules/trail-versioning-topo.ts +172 -0
- package/src/rules/types.ts +270 -6
- package/src/rules/unmaterialized-activation-source.ts +84 -0
- package/src/rules/unreachable-detour-shadowing.ts +344 -0
- package/src/rules/valid-describe-refs.ts +160 -32
- package/src/rules/valid-detour-contract.ts +78 -0
- package/src/rules/warden-export-symmetry.ts +533 -0
- package/src/rules/warden-rules-use-ast.ts +996 -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/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/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/error-mapping-completeness.trail.ts +29 -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 +78 -0
- package/src/trails/intent-propagation.trail.ts +30 -0
- package/src/trails/layer-field-name-drift.trail.ts +39 -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 +194 -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/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 +45 -0
- package/src/trails/webhook-route-collision.trail.ts +50 -0
- package/src/trails/wrap-rule.ts +213 -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 -231
- 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 -75
- 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 -188
- 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
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
|
|
6
|
-
import { trail, topo, Result } from '@ontrails/core';
|
|
7
|
-
import { hashSurfaceMap, generateSurfaceMap } from '@ontrails/schema';
|
|
8
|
-
import { z } from 'zod';
|
|
9
|
-
|
|
10
|
-
import { checkDrift } from '../drift.js';
|
|
11
|
-
|
|
12
|
-
const makeTopo = () => {
|
|
13
|
-
const t = trail('test.hello', {
|
|
14
|
-
implementation: () => Result.ok({ greeting: 'hi' }),
|
|
15
|
-
input: z.object({ name: z.string() }),
|
|
16
|
-
output: z.object({ greeting: z.string() }),
|
|
17
|
-
});
|
|
18
|
-
return topo('test-app', { t });
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const createTempDir = (): string => {
|
|
22
|
-
const dir = join(tmpdir(), `drift-test-${Date.now()}`);
|
|
23
|
-
mkdirSync(dir, { recursive: true });
|
|
24
|
-
return dir;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
describe('checkDrift', () => {
|
|
28
|
-
test('returns stale: false when no topo is provided', async () => {
|
|
29
|
-
const result = await checkDrift('/tmp');
|
|
30
|
-
expect(result.stale).toBe(false);
|
|
31
|
-
expect(result.currentHash).toBe('unknown');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('returns stale: false when no lock file exists', async () => {
|
|
35
|
-
const dir = createTempDir();
|
|
36
|
-
try {
|
|
37
|
-
const result = await checkDrift(dir, makeTopo());
|
|
38
|
-
expect(result.stale).toBe(false);
|
|
39
|
-
expect(result.committedHash).toBeNull();
|
|
40
|
-
expect(result.currentHash.length).toBeGreaterThan(0);
|
|
41
|
-
} finally {
|
|
42
|
-
rmSync(dir, { force: true, recursive: true });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('returns stale: false when lock matches current hash', async () => {
|
|
47
|
-
const dir = createTempDir();
|
|
48
|
-
try {
|
|
49
|
-
const tp = makeTopo();
|
|
50
|
-
const hash = hashSurfaceMap(generateSurfaceMap(tp));
|
|
51
|
-
writeFileSync(join(dir, 'surface.lock'), `${hash}\n`);
|
|
52
|
-
|
|
53
|
-
const result = await checkDrift(dir, tp);
|
|
54
|
-
expect(result.stale).toBe(false);
|
|
55
|
-
expect(result.committedHash).toBe(hash);
|
|
56
|
-
expect(result.currentHash).toBe(hash);
|
|
57
|
-
} finally {
|
|
58
|
-
rmSync(dir, { force: true, recursive: true });
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('returns stale: true when lock does not match', async () => {
|
|
63
|
-
const dir = createTempDir();
|
|
64
|
-
try {
|
|
65
|
-
writeFileSync(join(dir, 'surface.lock'), 'outdated-hash\n');
|
|
66
|
-
|
|
67
|
-
const result = await checkDrift(dir, makeTopo());
|
|
68
|
-
expect(result.stale).toBe(true);
|
|
69
|
-
expect(result.committedHash).toBe('outdated-hash');
|
|
70
|
-
} finally {
|
|
71
|
-
rmSync(dir, { force: true, recursive: true });
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
});
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import type { WardenReport } from '../cli.js';
|
|
4
|
-
import {
|
|
5
|
-
formatGitHubAnnotations,
|
|
6
|
-
formatJson,
|
|
7
|
-
formatSummary,
|
|
8
|
-
} from '../formatters.js';
|
|
9
|
-
|
|
10
|
-
const cleanReport: WardenReport = {
|
|
11
|
-
diagnostics: [],
|
|
12
|
-
drift: { committedHash: 'abc', currentHash: 'abc', stale: false },
|
|
13
|
-
errorCount: 0,
|
|
14
|
-
passed: true,
|
|
15
|
-
warnCount: 0,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const reportWithDiagnostics: WardenReport = {
|
|
19
|
-
diagnostics: [
|
|
20
|
-
{
|
|
21
|
-
filePath: 'packages/core/src/result.ts',
|
|
22
|
-
line: 42,
|
|
23
|
-
message: 'Throw statement found in trail implementation',
|
|
24
|
-
rule: 'no-throw-in-implementation',
|
|
25
|
-
severity: 'error',
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
filePath: 'packages/core/src/trails.ts',
|
|
29
|
-
line: 15,
|
|
30
|
-
message: 'Trail "entity.show" has no output schema',
|
|
31
|
-
rule: 'require-output-schema',
|
|
32
|
-
severity: 'warn',
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
drift: null,
|
|
36
|
-
errorCount: 1,
|
|
37
|
-
passed: false,
|
|
38
|
-
warnCount: 1,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const reportWithDrift: WardenReport = {
|
|
42
|
-
diagnostics: [],
|
|
43
|
-
drift: { committedHash: 'abc', currentHash: 'def', stale: true },
|
|
44
|
-
errorCount: 0,
|
|
45
|
-
passed: false,
|
|
46
|
-
warnCount: 0,
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
describe('formatGitHubAnnotations', () => {
|
|
50
|
-
test('produces empty string for clean report', () => {
|
|
51
|
-
expect(formatGitHubAnnotations(cleanReport)).toBe('');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('maps error severity to ::error', () => {
|
|
55
|
-
const output = formatGitHubAnnotations(reportWithDiagnostics);
|
|
56
|
-
expect(output).toContain(
|
|
57
|
-
'::error file=packages/core/src/result.ts,line=42::no-throw-in-implementation:'
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('maps warn severity to ::warning', () => {
|
|
62
|
-
const output = formatGitHubAnnotations(reportWithDiagnostics);
|
|
63
|
-
expect(output).toContain(
|
|
64
|
-
'::warning file=packages/core/src/trails.ts,line=15::require-output-schema:'
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('emits drift as a single ::error annotation', () => {
|
|
69
|
-
const output = formatGitHubAnnotations(reportWithDrift);
|
|
70
|
-
expect(output).toContain('::error::drift: surface.lock is stale');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('produces one line per diagnostic', () => {
|
|
74
|
-
const lines = formatGitHubAnnotations(reportWithDiagnostics)
|
|
75
|
-
.split('\n')
|
|
76
|
-
.filter(Boolean);
|
|
77
|
-
expect(lines).toHaveLength(2);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('formatJson', () => {
|
|
82
|
-
test('produces valid JSON', () => {
|
|
83
|
-
const parsed = JSON.parse(formatJson(cleanReport));
|
|
84
|
-
expect(parsed).toBeDefined();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('includes passed status', () => {
|
|
88
|
-
const parsed = JSON.parse(formatJson(cleanReport));
|
|
89
|
-
expect(parsed.passed).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('includes summary counts', () => {
|
|
93
|
-
const parsed = JSON.parse(formatJson(reportWithDiagnostics));
|
|
94
|
-
expect(parsed.summary).toEqual({
|
|
95
|
-
errors: 1,
|
|
96
|
-
suggestions: 0,
|
|
97
|
-
warnings: 1,
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('includes diagnostics array', () => {
|
|
102
|
-
const parsed = JSON.parse(formatJson(reportWithDiagnostics));
|
|
103
|
-
expect(parsed.diagnostics).toHaveLength(2);
|
|
104
|
-
expect(parsed.diagnostics[0].rule).toBe('no-throw-in-implementation');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('includes null drift when absent', () => {
|
|
108
|
-
const parsed = JSON.parse(formatJson(reportWithDiagnostics));
|
|
109
|
-
expect(parsed.drift).toBeNull();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('includes drift result when present', () => {
|
|
113
|
-
const parsed = JSON.parse(formatJson(reportWithDrift));
|
|
114
|
-
expect(parsed.drift.stale).toBe(true);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('formatSummary', () => {
|
|
119
|
-
test('includes markdown heading', () => {
|
|
120
|
-
expect(formatSummary(cleanReport)).toContain('## Warden Report');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('shows PASS for clean report', () => {
|
|
124
|
-
expect(formatSummary(cleanReport)).toContain('**Result: PASS**');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('shows FAIL for failing report', () => {
|
|
128
|
-
expect(formatSummary(reportWithDiagnostics)).toContain('**Result: FAIL**');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('groups errors under ### Errors heading', () => {
|
|
132
|
-
const output = formatSummary(reportWithDiagnostics);
|
|
133
|
-
expect(output).toContain('### Errors');
|
|
134
|
-
expect(output).toContain('no-throw-in-implementation');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('groups warnings under ### Warnings heading', () => {
|
|
138
|
-
const output = formatSummary(reportWithDiagnostics);
|
|
139
|
-
expect(output).toContain('### Warnings');
|
|
140
|
-
expect(output).toContain('require-output-schema');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('includes file:line in backticks', () => {
|
|
144
|
-
const output = formatSummary(reportWithDiagnostics);
|
|
145
|
-
expect(output).toContain('`packages/core/src/result.ts:42`');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('includes drift section when stale', () => {
|
|
149
|
-
const output = formatSummary(reportWithDrift);
|
|
150
|
-
expect(output).toContain('### Drift');
|
|
151
|
-
expect(output).toContain('surface.lock is stale');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('omits drift section when clean', () => {
|
|
155
|
-
expect(formatSummary(cleanReport)).not.toContain('### Drift');
|
|
156
|
-
});
|
|
157
|
-
});
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { implementationReturnsResult } from '../rules/implementation-returns-result.js';
|
|
4
|
-
|
|
5
|
-
const TEST_FILE = 'test.ts';
|
|
6
|
-
|
|
7
|
-
describe('implementation-returns-result', () => {
|
|
8
|
-
test('flags raw object return in trail implementation', () => {
|
|
9
|
-
const code = `
|
|
10
|
-
trail("entity.show", {
|
|
11
|
-
implementation: async (input, ctx) => {
|
|
12
|
-
return { name: "foo" };
|
|
13
|
-
}
|
|
14
|
-
})`;
|
|
15
|
-
|
|
16
|
-
const diagnostics = implementationReturnsResult.check(code, TEST_FILE);
|
|
17
|
-
|
|
18
|
-
expect(diagnostics.length).toBe(1);
|
|
19
|
-
expect(diagnostics[0]?.rule).toBe('implementation-returns-result');
|
|
20
|
-
expect(diagnostics[0]?.severity).toBe('error');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('allows Result.ok() and returning ctx.follow() results', () => {
|
|
24
|
-
const code = `
|
|
25
|
-
hike("entity.onboard", {
|
|
26
|
-
implementation: async (input, ctx) => {
|
|
27
|
-
const result = await ctx.follow("entity.create", input);
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
trail("entity.create", {
|
|
33
|
-
implementation: async (input, ctx) => Result.ok({ id: "123" })
|
|
34
|
-
})`;
|
|
35
|
-
|
|
36
|
-
const diagnostics = implementationReturnsResult.check(code, TEST_FILE);
|
|
37
|
-
|
|
38
|
-
expect(diagnostics.length).toBe(0);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('flags concise raw implementation bodies', () => {
|
|
42
|
-
const code = `
|
|
43
|
-
trail("entity.create", {
|
|
44
|
-
implementation: async (input, ctx) => ({ id: "123" })
|
|
45
|
-
})`;
|
|
46
|
-
|
|
47
|
-
const diagnostics = implementationReturnsResult.check(code, TEST_FILE);
|
|
48
|
-
|
|
49
|
-
expect(diagnostics.length).toBe(1);
|
|
50
|
-
expect(diagnostics[0]?.message).toContain('entity.create');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('allows returning explicitly Result-typed local helpers', () => {
|
|
54
|
-
const code = `
|
|
55
|
-
const buildDetail = (trailId: string): Result<object, Error> =>
|
|
56
|
-
Result.ok({ trailId });
|
|
57
|
-
|
|
58
|
-
const buildDiff = async (): Promise<Result<object, Error>> =>
|
|
59
|
-
Result.ok({ breaking: [] });
|
|
60
|
-
|
|
61
|
-
trail("survey", {
|
|
62
|
-
implementation: async (input, ctx) => {
|
|
63
|
-
if (input.diff) {
|
|
64
|
-
return await buildDiff();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return buildDetail(input.trailId);
|
|
68
|
-
}
|
|
69
|
-
})`;
|
|
70
|
-
|
|
71
|
-
const diagnostics = implementationReturnsResult.check(code, TEST_FILE);
|
|
72
|
-
|
|
73
|
-
expect(diagnostics.length).toBe(0);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { noDirectImplementationCall } from '../rules/no-direct-implementation-call.js';
|
|
4
|
-
|
|
5
|
-
describe('no-direct-implementation-call', () => {
|
|
6
|
-
test('flags direct implementation access in application code', () => {
|
|
7
|
-
const code = `
|
|
8
|
-
import { trail, Result } from "@ontrails/core";
|
|
9
|
-
|
|
10
|
-
const entityShow = trail("entity.show", {
|
|
11
|
-
implementation: async (input, ctx) => Result.ok({ id: input.id }),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
async function run() {
|
|
15
|
-
const result = await entityShow.implementation({ id: "1" }, ctx);
|
|
16
|
-
return result;
|
|
17
|
-
}`;
|
|
18
|
-
|
|
19
|
-
const diagnostics = noDirectImplementationCall.check(code, 'src/app.ts');
|
|
20
|
-
|
|
21
|
-
expect(diagnostics).toHaveLength(1);
|
|
22
|
-
expect(diagnostics[0]?.rule).toBe('no-direct-implementation-call');
|
|
23
|
-
expect(diagnostics[0]?.severity).toBe('warn');
|
|
24
|
-
expect(diagnostics[0]?.message).toContain('ctx.follow');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('allows ctx.follow() calls', () => {
|
|
28
|
-
const code = `
|
|
29
|
-
hike("entity.onboard", {
|
|
30
|
-
follows: ["entity.create"],
|
|
31
|
-
implementation: async (input, ctx) => {
|
|
32
|
-
const result = await ctx.follow("entity.create", input);
|
|
33
|
-
return Result.ok(result);
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
`;
|
|
37
|
-
|
|
38
|
-
const diagnostics = noDirectImplementationCall.check(code, 'src/app.ts');
|
|
39
|
-
|
|
40
|
-
expect(diagnostics).toHaveLength(0);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('ignores test files', () => {
|
|
44
|
-
const code = `
|
|
45
|
-
async function run() {
|
|
46
|
-
return await entityShow.implementation({ id: "1" }, ctx);
|
|
47
|
-
}`;
|
|
48
|
-
|
|
49
|
-
const diagnostics = noDirectImplementationCall.check(
|
|
50
|
-
code,
|
|
51
|
-
'src/__tests__/app.test.ts'
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
expect(diagnostics).toHaveLength(0);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('ignores framework internals that intentionally call implementations', () => {
|
|
58
|
-
const code = `
|
|
59
|
-
export async function run() {
|
|
60
|
-
return await entityShow.implementation({ id: "1" }, ctx);
|
|
61
|
-
}`;
|
|
62
|
-
|
|
63
|
-
const diagnostics = noDirectImplementationCall.check(
|
|
64
|
-
code,
|
|
65
|
-
'/repo/packages/testing/src/trail.ts'
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
expect(diagnostics).toHaveLength(0);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('ignores implementation references inside template strings', () => {
|
|
72
|
-
const code = `
|
|
73
|
-
const generated = \`const result = await entityShow.implementation({ id: "1" }, ctx);\`;
|
|
74
|
-
`;
|
|
75
|
-
|
|
76
|
-
const diagnostics = noDirectImplementationCall.check(
|
|
77
|
-
code,
|
|
78
|
-
'src/new-trail.ts'
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
expect(diagnostics).toHaveLength(0);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { noSyncResultAssumption } from '../rules/no-sync-result-assumption.js';
|
|
4
|
-
|
|
5
|
-
describe('no-sync-result-assumption', () => {
|
|
6
|
-
test('flags direct result access on implementation calls', () => {
|
|
7
|
-
const code = `
|
|
8
|
-
async function run() {
|
|
9
|
-
const isOk = entityShow.implementation({ id: "1" }, ctx).isOk();
|
|
10
|
-
return isOk;
|
|
11
|
-
}`;
|
|
12
|
-
|
|
13
|
-
const diagnostics = noSyncResultAssumption.check(code, 'src/app.ts');
|
|
14
|
-
|
|
15
|
-
expect(diagnostics).toHaveLength(1);
|
|
16
|
-
expect(diagnostics[0]?.rule).toBe('no-sync-result-assumption');
|
|
17
|
-
expect(diagnostics[0]?.severity).toBe('error');
|
|
18
|
-
expect(diagnostics[0]?.message).toContain('Missing await');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('flags a stored implementation result that is used synchronously', () => {
|
|
22
|
-
const code = `
|
|
23
|
-
const result = entityShow.implementation({ id: "1" }, ctx);
|
|
24
|
-
|
|
25
|
-
if (result.isOk()) {
|
|
26
|
-
console.log("ok");
|
|
27
|
-
}`;
|
|
28
|
-
|
|
29
|
-
const diagnostics = noSyncResultAssumption.check(code, 'src/app.ts');
|
|
30
|
-
|
|
31
|
-
expect(diagnostics).toHaveLength(1);
|
|
32
|
-
expect(diagnostics[0]?.line).toBe(4);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('allows awaited implementation calls before result access', () => {
|
|
36
|
-
const code = `
|
|
37
|
-
async function run() {
|
|
38
|
-
const result = await entityShow.implementation({ id: "1" }, ctx);
|
|
39
|
-
return result.isOk();
|
|
40
|
-
}`;
|
|
41
|
-
|
|
42
|
-
const diagnostics = noSyncResultAssumption.check(code, 'src/app.ts');
|
|
43
|
-
|
|
44
|
-
expect(diagnostics).toHaveLength(0);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('allows awaited implementation calls when the property access is chained', () => {
|
|
48
|
-
const code = `
|
|
49
|
-
async function run() {
|
|
50
|
-
return (await entityShow.implementation({ id: "1" }, ctx)).isOk();
|
|
51
|
-
}`;
|
|
52
|
-
|
|
53
|
-
const diagnostics = noSyncResultAssumption.check(code, 'src/app.ts');
|
|
54
|
-
|
|
55
|
-
expect(diagnostics).toHaveLength(0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('ignores test files', () => {
|
|
59
|
-
const code = `
|
|
60
|
-
const result = entityShow.implementation({ id: "1" }, ctx);
|
|
61
|
-
result.isOk();
|
|
62
|
-
`;
|
|
63
|
-
|
|
64
|
-
const diagnostics = noSyncResultAssumption.check(
|
|
65
|
-
code,
|
|
66
|
-
'src/__tests__/app.test.ts'
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
expect(diagnostics).toHaveLength(0);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('ignores framework internals that intentionally call implementations', () => {
|
|
73
|
-
const code = `
|
|
74
|
-
const result = entityShow.implementation({ id: "1" }, ctx);
|
|
75
|
-
result.isOk();
|
|
76
|
-
`;
|
|
77
|
-
|
|
78
|
-
const diagnostics = noSyncResultAssumption.check(
|
|
79
|
-
code,
|
|
80
|
-
'/repo/packages/testing/src/trail.ts'
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
expect(diagnostics).toHaveLength(0);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { noThrowInDetourTarget } from '../rules/no-throw-in-detour-target.js';
|
|
4
|
-
|
|
5
|
-
const TEST_FILE = 'test.ts';
|
|
6
|
-
|
|
7
|
-
describe('no-throw-in-detour-target', () => {
|
|
8
|
-
test('flags throw inside a detour target implementation', () => {
|
|
9
|
-
const code = `
|
|
10
|
-
trail("entity.show", {
|
|
11
|
-
detours: { NotFoundError: ["entity.fallback"] },
|
|
12
|
-
implementation: async (input, ctx) => Result.ok({ id: "123" })
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
trail("entity.fallback", {
|
|
16
|
-
implementation: async (input, ctx) => {
|
|
17
|
-
throw new Error("boom");
|
|
18
|
-
}
|
|
19
|
-
})`;
|
|
20
|
-
|
|
21
|
-
const diagnostics = noThrowInDetourTarget.check(code, TEST_FILE);
|
|
22
|
-
|
|
23
|
-
expect(diagnostics.length).toBe(1);
|
|
24
|
-
expect(diagnostics[0]?.rule).toBe('no-throw-in-detour-target');
|
|
25
|
-
expect(diagnostics[0]?.severity).toBe('error');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('allows throw in implementations that are not detour targets', () => {
|
|
29
|
-
const code = `
|
|
30
|
-
trail("entity.show", {
|
|
31
|
-
implementation: async (input, ctx) => {
|
|
32
|
-
throw new Error("boom");
|
|
33
|
-
}
|
|
34
|
-
})`;
|
|
35
|
-
|
|
36
|
-
const diagnostics = noThrowInDetourTarget.check(code, TEST_FILE);
|
|
37
|
-
|
|
38
|
-
expect(diagnostics.length).toBe(0);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('flags concise detour target implementations that throw inline', () => {
|
|
42
|
-
const code = `
|
|
43
|
-
trail("entity.show", {
|
|
44
|
-
detours: { NotFoundError: ["entity.fallback"] },
|
|
45
|
-
implementation: async (input, ctx) => Result.ok({ id: "123" })
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
trail("entity.fallback", {
|
|
49
|
-
implementation: async (input, ctx) => { throw new Error("boom"); }
|
|
50
|
-
})`;
|
|
51
|
-
|
|
52
|
-
const diagnostics = noThrowInDetourTarget.check(code, TEST_FILE);
|
|
53
|
-
|
|
54
|
-
expect(diagnostics.length).toBe(1);
|
|
55
|
-
expect(diagnostics[0]?.message).toContain('entity.fallback');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('uses project context when the detour target is defined in another file', () => {
|
|
59
|
-
const code = `
|
|
60
|
-
trail("entity.fallback", {
|
|
61
|
-
implementation: async (input, ctx) => {
|
|
62
|
-
throw new Error("boom");
|
|
63
|
-
}
|
|
64
|
-
})`;
|
|
65
|
-
|
|
66
|
-
const diagnostics = noThrowInDetourTarget.checkWithContext(
|
|
67
|
-
code,
|
|
68
|
-
TEST_FILE,
|
|
69
|
-
{
|
|
70
|
-
detourTargetTrailIds: new Set(['entity.fallback']),
|
|
71
|
-
knownTrailIds: new Set(['entity.show', 'entity.fallback']),
|
|
72
|
-
}
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
expect(diagnostics).toHaveLength(1);
|
|
76
|
-
expect(diagnostics[0]?.rule).toBe('no-throw-in-detour-target');
|
|
77
|
-
});
|
|
78
|
-
});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { preferSchemaInference } from '../rules/prefer-schema-inference.js';
|
|
4
|
-
|
|
5
|
-
describe('prefer-schema-inference', () => {
|
|
6
|
-
test('warns when a fields label only repeats the derived humanized label', () => {
|
|
7
|
-
const code = `
|
|
8
|
-
trail("entity.show", {
|
|
9
|
-
input: z.object({ firstName: z.string() }),
|
|
10
|
-
fields: {
|
|
11
|
-
firstName: { label: "First Name" },
|
|
12
|
-
},
|
|
13
|
-
implementation: (input) => Result.ok(input),
|
|
14
|
-
})`;
|
|
15
|
-
|
|
16
|
-
const diagnostics = preferSchemaInference.check(code, 'src/entity.ts');
|
|
17
|
-
|
|
18
|
-
expect(diagnostics).toHaveLength(1);
|
|
19
|
-
expect(diagnostics[0]?.rule).toBe('prefer-schema-inference');
|
|
20
|
-
expect(diagnostics[0]?.message).toContain('firstName');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('warns when enum options only repeat schema-derived values', () => {
|
|
24
|
-
const code = `
|
|
25
|
-
trail("entity.paint", {
|
|
26
|
-
input: z.object({
|
|
27
|
-
color: z.enum(["red", "green"]),
|
|
28
|
-
}),
|
|
29
|
-
fields: {
|
|
30
|
-
color: {
|
|
31
|
-
options: [{ value: "red" }, { value: "green" }],
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
implementation: (input) => Result.ok(input),
|
|
35
|
-
})`;
|
|
36
|
-
|
|
37
|
-
const diagnostics = preferSchemaInference.check(code, 'src/entity.ts');
|
|
38
|
-
|
|
39
|
-
expect(diagnostics).toHaveLength(1);
|
|
40
|
-
expect(diagnostics[0]?.message).toContain('schema-derived options');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('allows custom labels and enriched enum options', () => {
|
|
44
|
-
const code = `
|
|
45
|
-
trail("entity.paint", {
|
|
46
|
-
input: z.object({
|
|
47
|
-
color: z.enum(["red", "green"]),
|
|
48
|
-
displayName: z.string().describe("Display name"),
|
|
49
|
-
}),
|
|
50
|
-
fields: {
|
|
51
|
-
color: {
|
|
52
|
-
options: [
|
|
53
|
-
{ value: "red", label: "Red" },
|
|
54
|
-
{ value: "green", hint: "Safe default" },
|
|
55
|
-
],
|
|
56
|
-
},
|
|
57
|
-
displayName: { label: "Public name" },
|
|
58
|
-
},
|
|
59
|
-
implementation: (input) => Result.ok(input),
|
|
60
|
-
})`;
|
|
61
|
-
|
|
62
|
-
const diagnostics = preferSchemaInference.check(code, 'src/entity.ts');
|
|
63
|
-
|
|
64
|
-
expect(diagnostics).toHaveLength(0);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('does not warn when the override carries other field metadata', () => {
|
|
68
|
-
const code = `
|
|
69
|
-
trail("entity.show", {
|
|
70
|
-
input: z.object({ firstName: z.string() }),
|
|
71
|
-
fields: {
|
|
72
|
-
firstName: {
|
|
73
|
-
label: "First Name",
|
|
74
|
-
message: "Who should we greet?",
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
implementation: (input) => Result.ok(input),
|
|
78
|
-
})`;
|
|
79
|
-
|
|
80
|
-
const diagnostics = preferSchemaInference.check(code, 'src/entity.ts');
|
|
81
|
-
|
|
82
|
-
expect(diagnostics).toHaveLength(0);
|
|
83
|
-
});
|
|
84
|
-
});
|