@ontrails/warden 1.0.0-beta.13 → 1.0.0-beta.14
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/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +12 -10
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -3
- package/dist/cli.js.map +1 -1
- package/dist/draft.d.ts +5 -0
- package/dist/draft.d.ts.map +1 -0
- package/dist/draft.js +16 -0
- package/dist/draft.js.map +1 -0
- package/dist/drift.d.ts +9 -6
- package/dist/drift.d.ts.map +1 -1
- package/dist/drift.js +49 -15
- package/dist/drift.js.map +1 -1
- package/dist/formatters.d.ts +2 -1
- package/dist/formatters.d.ts.map +1 -1
- package/dist/formatters.js +15 -4
- package/dist/formatters.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/rules/ast.d.ts +7 -0
- package/dist/rules/ast.d.ts.map +1 -1
- package/dist/rules/ast.js +22 -0
- package/dist/rules/ast.js.map +1 -1
- package/dist/rules/draft-file-marking.d.ts +6 -0
- package/dist/rules/draft-file-marking.d.ts.map +1 -0
- package/dist/rules/draft-file-marking.js +65 -0
- package/dist/rules/draft-file-marking.js.map +1 -0
- package/dist/rules/draft-visible-debt.d.ts +12 -0
- package/dist/rules/draft-visible-debt.d.ts.map +1 -0
- package/dist/rules/draft-visible-debt.js +45 -0
- package/dist/rules/draft-visible-debt.js.map +1 -0
- package/dist/rules/index.d.ts +2 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +6 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/provision-exists.d.ts.map +1 -1
- package/dist/rules/provision-exists.js +2 -1
- package/dist/rules/provision-exists.js.map +1 -1
- package/dist/rules/valid-detour-refs.d.ts.map +1 -1
- package/dist/rules/valid-detour-refs.js +3 -2
- package/dist/rules/valid-detour-refs.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/cli.test.ts +114 -1
- package/src/__tests__/drift.test.ts +74 -4
- package/src/__tests__/formatters.test.ts +2 -2
- package/src/cli.ts +11 -3
- package/src/draft.ts +22 -0
- package/src/drift.ts +60 -18
- package/src/formatters.ts +15 -4
- package/src/index.ts +21 -0
- package/src/rules/ast.ts +38 -0
- package/src/rules/draft-file-marking.ts +112 -0
- package/src/rules/draft-visible-debt.ts +62 -0
- package/src/rules/index.ts +6 -0
- package/src/rules/provision-exists.ts +3 -1
- package/src/rules/valid-detour-refs.ts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
package/src/drift.ts
CHANGED
|
@@ -1,32 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Topo lock drift detection.
|
|
3
3
|
*
|
|
4
|
-
* Compares the committed `
|
|
4
|
+
* Compares the committed `trails.lock` hash against a freshly generated
|
|
5
5
|
* trailhead map hash to detect when the trail topology has changed without
|
|
6
|
-
* updating the lock file.
|
|
6
|
+
* updating the lock file. The committed lock may be structured JSON or the
|
|
7
|
+
* legacy single-line hash format.
|
|
7
8
|
*/
|
|
8
9
|
|
|
10
|
+
import { existsSync, statSync } from 'node:fs';
|
|
11
|
+
|
|
9
12
|
import type { Topo } from '@ontrails/core';
|
|
13
|
+
import {
|
|
14
|
+
createTopoStore,
|
|
15
|
+
NotFoundError,
|
|
16
|
+
ValidationError,
|
|
17
|
+
} from '@ontrails/core';
|
|
18
|
+
import { resolveTrailsDir } from '@ontrails/core/internal/trails-db';
|
|
10
19
|
import {
|
|
11
20
|
generateTrailheadMap,
|
|
12
21
|
hashTrailheadMap,
|
|
13
|
-
|
|
22
|
+
readTrailheadLockData,
|
|
14
23
|
} from '@ontrails/schema';
|
|
15
24
|
|
|
16
25
|
/**
|
|
17
|
-
* Result of a drift check comparing committed
|
|
26
|
+
* Result of a drift check comparing committed trails.lock against the current state.
|
|
18
27
|
*/
|
|
19
28
|
export interface DriftResult {
|
|
29
|
+
/** Why drift could not be computed for the established graph, when blocked. */
|
|
30
|
+
readonly blockedReason?: string | undefined;
|
|
20
31
|
/** Whether the committed lock is out of date */
|
|
21
32
|
readonly stale: boolean;
|
|
22
|
-
/** Hash from the committed
|
|
33
|
+
/** Hash from the committed trails.lock file, or null if not found */
|
|
23
34
|
readonly committedHash: string | null;
|
|
24
35
|
/** Hash computed from the current trail topology */
|
|
25
36
|
readonly currentHash: string;
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
/**
|
|
29
|
-
* Check whether the committed
|
|
40
|
+
* Check whether the committed trails.lock is stale compared to the current topology.
|
|
30
41
|
*
|
|
31
42
|
* When no topo is provided, returns a clean result (no drift detectable without runtime info).
|
|
32
43
|
*/
|
|
@@ -34,17 +45,48 @@ export const checkDrift = async (
|
|
|
34
45
|
rootDir: string,
|
|
35
46
|
topo?: Topo | undefined
|
|
36
47
|
): Promise<DriftResult> => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
try {
|
|
49
|
+
const trailsDir = resolveTrailsDir({ rootDir });
|
|
50
|
+
const committedLock =
|
|
51
|
+
existsSync(rootDir) && statSync(rootDir).isDirectory()
|
|
52
|
+
? await readTrailheadLockData({ dir: trailsDir })
|
|
53
|
+
: null;
|
|
54
|
+
// Prefer the stored hash (computed by the export pipeline) to avoid
|
|
55
|
+
// divergence between the schema and store hash pipelines.
|
|
56
|
+
const storedHash = (() => {
|
|
57
|
+
try {
|
|
58
|
+
return createTopoStore({ rootDir }).exports.get()?.trailheadHash;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error instanceof NotFoundError) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
const currentHash =
|
|
67
|
+
storedHash ??
|
|
68
|
+
(topo === undefined
|
|
69
|
+
? 'unknown'
|
|
70
|
+
: hashTrailheadMap(generateTrailheadMap(topo)));
|
|
40
71
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
72
|
+
return {
|
|
73
|
+
committedHash: committedLock?.hash ?? null,
|
|
74
|
+
currentHash,
|
|
75
|
+
stale:
|
|
76
|
+
committedLock !== null &&
|
|
77
|
+
currentHash !== 'unknown' &&
|
|
78
|
+
committedLock.hash !== currentHash,
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (!(error instanceof ValidationError)) {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
44
84
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
85
|
+
return {
|
|
86
|
+
blockedReason: error.message,
|
|
87
|
+
committedHash: null,
|
|
88
|
+
currentHash: 'blocked',
|
|
89
|
+
stale: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
50
92
|
};
|
package/src/formatters.ts
CHANGED
|
@@ -19,7 +19,8 @@ const ghLevel: Record<WardenSeverity, string> = {
|
|
|
19
19
|
* Produce GitHub Actions workflow command annotations, one per diagnostic.
|
|
20
20
|
*
|
|
21
21
|
* Severity mapping: `error` to `::error`, `warn` to `::warning`.
|
|
22
|
-
* Drift staleness is emitted as a single
|
|
22
|
+
* Drift staleness or established-export blocking is emitted as a single
|
|
23
|
+
* `::error` annotation when detected.
|
|
23
24
|
*/
|
|
24
25
|
export const formatGitHubAnnotations = (report: WardenReport): string => {
|
|
25
26
|
const lines: string[] = [];
|
|
@@ -31,9 +32,11 @@ export const formatGitHubAnnotations = (report: WardenReport): string => {
|
|
|
31
32
|
);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
if (report.drift?.
|
|
35
|
+
if (report.drift?.blockedReason !== undefined) {
|
|
36
|
+
lines.push(`::error::drift: ${report.drift.blockedReason}`);
|
|
37
|
+
} else if (report.drift?.stale) {
|
|
35
38
|
lines.push(
|
|
36
|
-
'::error::drift:
|
|
39
|
+
'::error::drift: trails.lock is stale (regenerate with `trails topo export`)'
|
|
37
40
|
);
|
|
38
41
|
}
|
|
39
42
|
|
|
@@ -82,13 +85,21 @@ const severitySection = (
|
|
|
82
85
|
|
|
83
86
|
/** Render a drift section if stale, otherwise empty array. */
|
|
84
87
|
const driftSection = (drift: WardenReport['drift']): readonly string[] => {
|
|
88
|
+
if (drift?.blockedReason !== undefined) {
|
|
89
|
+
return [
|
|
90
|
+
'',
|
|
91
|
+
'### Drift',
|
|
92
|
+
`- established exports are blocked: ${drift.blockedReason}`,
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
if (!drift?.stale) {
|
|
86
97
|
return [];
|
|
87
98
|
}
|
|
88
99
|
return [
|
|
89
100
|
'',
|
|
90
101
|
'### Drift',
|
|
91
|
-
'-
|
|
102
|
+
'- trails.lock is stale (regenerate with `trails topo export`)',
|
|
92
103
|
];
|
|
93
104
|
};
|
|
94
105
|
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type {
|
|
|
19
19
|
// Individual rules
|
|
20
20
|
export { noThrowInImplementation } from './rules/no-throw-in-implementation.js';
|
|
21
21
|
export { contextNoTrailheadTypes } from './rules/context-no-trailhead-types.js';
|
|
22
|
+
export { draftFileMarking } from './rules/draft-file-marking.js';
|
|
23
|
+
export { draftVisibleDebt } from './rules/draft-visible-debt.js';
|
|
22
24
|
export { validDetourRefs } from './rules/valid-detour-refs.js';
|
|
23
25
|
export { noDirectImplInRoute } from './rules/no-direct-impl-in-route.js';
|
|
24
26
|
export { noDirectImplementationCall } from './rules/no-direct-implementation-call.js';
|
|
@@ -48,6 +50,25 @@ export {
|
|
|
48
50
|
export type { DriftResult } from './drift.js';
|
|
49
51
|
export { checkDrift } from './drift.js';
|
|
50
52
|
|
|
53
|
+
// Draft helpers
|
|
54
|
+
export {
|
|
55
|
+
DRAFT_FILE_PREFIX,
|
|
56
|
+
DRAFT_FILE_SEGMENT,
|
|
57
|
+
isDraftMarkedFile,
|
|
58
|
+
stripDraftFileMarkers,
|
|
59
|
+
} from './draft.js';
|
|
60
|
+
|
|
61
|
+
// AST helpers for repo-local tooling
|
|
62
|
+
export {
|
|
63
|
+
findStringLiterals,
|
|
64
|
+
getStringValue,
|
|
65
|
+
isStringLiteral,
|
|
66
|
+
offsetToLine,
|
|
67
|
+
parse,
|
|
68
|
+
walk,
|
|
69
|
+
} from './rules/ast.js';
|
|
70
|
+
export type { AstNode, StringLiteralMatch } from './rules/ast.js';
|
|
71
|
+
|
|
51
72
|
// Trail gate
|
|
52
73
|
export { wardenTopo } from './trails/topo.js';
|
|
53
74
|
export { runWardenTrails } from './trails/run.js';
|
package/src/rules/ast.ts
CHANGED
|
@@ -155,6 +155,44 @@ export const extractStringLiteral = (
|
|
|
155
155
|
): string | null =>
|
|
156
156
|
node && isStringLiteral(node) ? getStringValue(node) : null;
|
|
157
157
|
|
|
158
|
+
export interface StringLiteralMatch {
|
|
159
|
+
readonly end: number;
|
|
160
|
+
readonly node: AstNode;
|
|
161
|
+
readonly start: number;
|
|
162
|
+
readonly value: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export const findStringLiterals = (
|
|
166
|
+
ast: AstNode,
|
|
167
|
+
predicate?: (value: string, node: AstNode) => boolean
|
|
168
|
+
): StringLiteralMatch[] => {
|
|
169
|
+
const matches: StringLiteralMatch[] = [];
|
|
170
|
+
|
|
171
|
+
walk(ast, (node) => {
|
|
172
|
+
if (!isStringLiteral(node)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const value = getStringValue(node);
|
|
177
|
+
if (value === null) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (predicate && !predicate(value, node)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
matches.push({
|
|
186
|
+
end: node.end,
|
|
187
|
+
node,
|
|
188
|
+
start: node.start,
|
|
189
|
+
value,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return matches;
|
|
194
|
+
};
|
|
195
|
+
|
|
158
196
|
/** Extract the first string argument from a CallExpression. */
|
|
159
197
|
export const extractFirstStringArg = (node: AstNode): string | null => {
|
|
160
198
|
if (node.type !== 'CallExpression') {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { isDraftId } from '@ontrails/core';
|
|
2
|
+
|
|
3
|
+
import { isDraftMarkedFile } from '../draft.js';
|
|
4
|
+
import { findStringLiterals, offsetToLine, parse } from './ast.js';
|
|
5
|
+
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
6
|
+
|
|
7
|
+
const messageForMissingMarker = (draftId: string): string =>
|
|
8
|
+
`Draft id "${draftId}" appears in source, but the file is not draft-marked. ` +
|
|
9
|
+
'Rename it with an _draft. prefix or a .draft. trailing segment.';
|
|
10
|
+
|
|
11
|
+
const makeDiagnostic = (
|
|
12
|
+
sourceCode: string,
|
|
13
|
+
filePath: string,
|
|
14
|
+
start: number,
|
|
15
|
+
message: string,
|
|
16
|
+
severity: WardenDiagnostic['severity']
|
|
17
|
+
): WardenDiagnostic => ({
|
|
18
|
+
filePath,
|
|
19
|
+
line: offsetToLine(sourceCode, start),
|
|
20
|
+
message,
|
|
21
|
+
rule: 'draft-file-marking',
|
|
22
|
+
severity,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const draftMissingMarkerDiagnostic = (
|
|
26
|
+
sourceCode: string,
|
|
27
|
+
filePath: string,
|
|
28
|
+
ast: NonNullable<ReturnType<typeof parse>>
|
|
29
|
+
): WardenDiagnostic | null => {
|
|
30
|
+
const draftMatches = findStringLiterals(ast, (value) => isDraftId(value));
|
|
31
|
+
if (!draftMatches.length || isDraftMarkedFile(filePath)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const [first] = draftMatches;
|
|
36
|
+
if (!first) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return makeDiagnostic(
|
|
41
|
+
sourceCode,
|
|
42
|
+
filePath,
|
|
43
|
+
first.start,
|
|
44
|
+
messageForMissingMarker(first.value),
|
|
45
|
+
'error'
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const draftMarkedWithoutIdsDiagnostic = (
|
|
50
|
+
filePath: string,
|
|
51
|
+
ast: NonNullable<ReturnType<typeof parse>>
|
|
52
|
+
): WardenDiagnostic | null => {
|
|
53
|
+
if (findStringLiterals(ast, (value) => isDraftId(value)).length > 0) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!isDraftMarkedFile(filePath)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
filePath,
|
|
63
|
+
line: 1,
|
|
64
|
+
message:
|
|
65
|
+
'File is draft-marked but no longer contains draft ids. Remove the draft filename marker or finish the promotion cleanup.',
|
|
66
|
+
rule: 'draft-file-marking',
|
|
67
|
+
severity: 'warn',
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const collectDraftFileMarkingDiagnostics = (
|
|
72
|
+
sourceCode: string,
|
|
73
|
+
filePath: string,
|
|
74
|
+
ast: NonNullable<ReturnType<typeof parse>>
|
|
75
|
+
): WardenDiagnostic[] => {
|
|
76
|
+
const missingMarkerDiagnostic = draftMissingMarkerDiagnostic(
|
|
77
|
+
sourceCode,
|
|
78
|
+
filePath,
|
|
79
|
+
ast
|
|
80
|
+
);
|
|
81
|
+
if (missingMarkerDiagnostic) {
|
|
82
|
+
return [missingMarkerDiagnostic];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const markedWithoutIdsDiagnostic = draftMarkedWithoutIdsDiagnostic(
|
|
86
|
+
filePath,
|
|
87
|
+
ast
|
|
88
|
+
);
|
|
89
|
+
if (markedWithoutIdsDiagnostic) {
|
|
90
|
+
return [markedWithoutIdsDiagnostic];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return [];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Ensures files containing draft ids are visibly marked as draft-bearing files.
|
|
98
|
+
*/
|
|
99
|
+
export const draftFileMarking: WardenRule = {
|
|
100
|
+
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
101
|
+
const ast = parse(filePath, sourceCode);
|
|
102
|
+
if (!ast) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return collectDraftFileMarkingDiagnostics(sourceCode, filePath, ast);
|
|
107
|
+
},
|
|
108
|
+
description:
|
|
109
|
+
'Require draft-bearing files to use _draft.* or *.draft.* filename markers.',
|
|
110
|
+
name: 'draft-file-marking',
|
|
111
|
+
severity: 'error',
|
|
112
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isDraftId } from '@ontrails/core';
|
|
2
|
+
|
|
3
|
+
import { findStringLiterals, offsetToLine, parse } from './ast.js';
|
|
4
|
+
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
5
|
+
|
|
6
|
+
const createDiagnostic = (
|
|
7
|
+
sourceCode: string,
|
|
8
|
+
filePath: string,
|
|
9
|
+
match: { start: number; value: string }
|
|
10
|
+
): WardenDiagnostic => ({
|
|
11
|
+
filePath,
|
|
12
|
+
line: offsetToLine(sourceCode, match.start),
|
|
13
|
+
message:
|
|
14
|
+
`Draft id "${match.value}" is still visible debt. ` +
|
|
15
|
+
'Established trailheads, lock export, and OpenAPI generation will reject it until it is promoted.',
|
|
16
|
+
rule: 'draft-visible-debt',
|
|
17
|
+
severity: 'warn',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const collectDraftVisibleDebtDiagnostics = (
|
|
21
|
+
sourceCode: string,
|
|
22
|
+
filePath: string,
|
|
23
|
+
ast: NonNullable<ReturnType<typeof parse>>
|
|
24
|
+
): WardenDiagnostic[] => {
|
|
25
|
+
const seen = new Set<string>();
|
|
26
|
+
const diagnostics: WardenDiagnostic[] = [];
|
|
27
|
+
|
|
28
|
+
for (const match of findStringLiterals(ast, (value) => isDraftId(value))) {
|
|
29
|
+
const key = `${match.value}:${String(match.start)}`;
|
|
30
|
+
if (seen.has(key)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
seen.add(key);
|
|
34
|
+
diagnostics.push(createDiagnostic(sourceCode, filePath, match));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return diagnostics;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Warns when draft ids are still present so the debt stays visible during
|
|
42
|
+
* review even when the file is correctly marked.
|
|
43
|
+
*
|
|
44
|
+
* Severity is intentionally `warn`, not `error`. The hard rejection gate for
|
|
45
|
+
* draft state leaking into established outputs is `validateEstablishedTopo` at
|
|
46
|
+
* runtime — it blocks topo export, trailhead projection, and lockfile writes.
|
|
47
|
+
* This rule surfaces the debt for human reviewers without duplicating that gate.
|
|
48
|
+
*/
|
|
49
|
+
export const draftVisibleDebt: WardenRule = {
|
|
50
|
+
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
51
|
+
const ast = parse(filePath, sourceCode);
|
|
52
|
+
if (!ast) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return collectDraftVisibleDebtDiagnostics(sourceCode, filePath, ast);
|
|
57
|
+
},
|
|
58
|
+
description:
|
|
59
|
+
'Warn when draft ids remain in source so the debt stays visible during review.',
|
|
60
|
+
name: 'draft-visible-debt',
|
|
61
|
+
severity: 'warn',
|
|
62
|
+
};
|
package/src/rules/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { contextNoTrailheadTypes } from './context-no-trailhead-types.js';
|
|
2
2
|
import { crossDeclarations } from './cross-declarations.js';
|
|
3
|
+
import { draftFileMarking } from './draft-file-marking.js';
|
|
4
|
+
import { draftVisibleDebt } from './draft-visible-debt.js';
|
|
3
5
|
import { implementationReturnsResult } from './implementation-returns-result.js';
|
|
4
6
|
import { noDirectImplInRoute } from './no-direct-impl-in-route.js';
|
|
5
7
|
import { noDirectImplementationCall } from './no-direct-implementation-call.js';
|
|
@@ -24,6 +26,8 @@ export type {
|
|
|
24
26
|
export { noThrowInImplementation } from './no-throw-in-implementation.js';
|
|
25
27
|
export { contextNoTrailheadTypes } from './context-no-trailhead-types.js';
|
|
26
28
|
export { crossDeclarations } from './cross-declarations.js';
|
|
29
|
+
export { draftFileMarking } from './draft-file-marking.js';
|
|
30
|
+
export { draftVisibleDebt } from './draft-visible-debt.js';
|
|
27
31
|
export { validDetourRefs } from './valid-detour-refs.js';
|
|
28
32
|
export { noDirectImplInRoute } from './no-direct-impl-in-route.js';
|
|
29
33
|
export { noDirectImplementationCall } from './no-direct-implementation-call.js';
|
|
@@ -43,6 +47,8 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
43
47
|
[noThrowInImplementation.name, noThrowInImplementation],
|
|
44
48
|
[contextNoTrailheadTypes.name, contextNoTrailheadTypes],
|
|
45
49
|
[crossDeclarations.name, crossDeclarations],
|
|
50
|
+
[draftFileMarking.name, draftFileMarking],
|
|
51
|
+
[draftVisibleDebt.name, draftVisibleDebt],
|
|
46
52
|
[provisionDeclarations.name, provisionDeclarations],
|
|
47
53
|
[provisionExists.name, provisionExists],
|
|
48
54
|
[preferSchemaInference.name, preferSchemaInference],
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isDraftId } from '@ontrails/core';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
collectNamedProvisionIds,
|
|
3
5
|
collectProvisionDefinitionIds,
|
|
@@ -94,7 +96,7 @@ const reportMissingProvisions = (
|
|
|
94
96
|
def.config,
|
|
95
97
|
provisionIdsByName
|
|
96
98
|
)) {
|
|
97
|
-
if (!knownProvisionIds.has(provisionId)) {
|
|
99
|
+
if (!knownProvisionIds.has(provisionId) && !isDraftId(provisionId)) {
|
|
98
100
|
diagnostics.push(
|
|
99
101
|
buildMissingProvisionDiagnostic(def.id, provisionId, filePath, line)
|
|
100
102
|
);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isDraftId } from '@ontrails/core';
|
|
2
|
+
|
|
1
3
|
import { collectTrailIds } from './specs.js';
|
|
2
4
|
import type {
|
|
3
5
|
ProjectAwareWardenRule,
|
|
@@ -44,7 +46,7 @@ const findMissingDetourTargets = (
|
|
|
44
46
|
const missing: string[] = [];
|
|
45
47
|
for (const m of text.matchAll(/target\s*:\s*["'`]([^"'`]+)["'`]/g)) {
|
|
46
48
|
const [, id] = m;
|
|
47
|
-
if (id && !knownIds.has(id)) {
|
|
49
|
+
if (id && !knownIds.has(id) && !isDraftId(id)) {
|
|
48
50
|
missing.push(id);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -59,7 +61,7 @@ const findMissingPlainDetours = (
|
|
|
59
61
|
const cleaned = text.replaceAll(/target\s*:\s*["'`][^"'`]+["'`]/g, '');
|
|
60
62
|
for (const m of cleaned.matchAll(/["'`]([^"'`]+)["'`]/g)) {
|
|
61
63
|
const [, id] = m;
|
|
62
|
-
if (id && id.includes('.') && !knownIds.has(id)) {
|
|
64
|
+
if (id && id.includes('.') && !knownIds.has(id) && !isDraftId(id)) {
|
|
63
65
|
missing.push(id);
|
|
64
66
|
}
|
|
65
67
|
}
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/cli.ts","./src/drift.ts","./src/formatters.ts","./src/index.ts","./src/rules/ast.ts","./src/rules/context-no-trailhead-types.ts","./src/rules/cross-declarations.ts","./src/rules/implementation-returns-result.ts","./src/rules/index.ts","./src/rules/no-direct-impl-in-route.ts","./src/rules/no-direct-implementation-call.ts","./src/rules/no-sync-result-assumption.ts","./src/rules/no-throw-in-detour-target.ts","./src/rules/no-throw-in-implementation.ts","./src/rules/prefer-schema-inference.ts","./src/rules/provision-declarations.ts","./src/rules/provision-exists.ts","./src/rules/scan.ts","./src/rules/specs.ts","./src/rules/structure.ts","./src/rules/types.ts","./src/rules/valid-describe-refs.ts","./src/rules/valid-detour-refs.ts","./src/trails/context-no-trailhead-types.trail.ts","./src/trails/cross-declarations.trail.ts","./src/trails/implementation-returns-result.trail.ts","./src/trails/index.ts","./src/trails/no-direct-impl-in-route.trail.ts","./src/trails/no-direct-implementation-call.trail.ts","./src/trails/no-sync-result-assumption.trail.ts","./src/trails/no-throw-in-detour-target.trail.ts","./src/trails/no-throw-in-implementation.trail.ts","./src/trails/prefer-schema-inference.trail.ts","./src/trails/provision-declarations.trail.ts","./src/trails/provision-exists.trail.ts","./src/trails/run.ts","./src/trails/schema.ts","./src/trails/topo.ts","./src/trails/valid-describe-refs.trail.ts","./src/trails/valid-detour-refs.trail.ts","./src/trails/wrap-rule.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/cli.ts","./src/draft.ts","./src/drift.ts","./src/formatters.ts","./src/index.ts","./src/rules/ast.ts","./src/rules/context-no-trailhead-types.ts","./src/rules/cross-declarations.ts","./src/rules/draft-file-marking.ts","./src/rules/draft-visible-debt.ts","./src/rules/implementation-returns-result.ts","./src/rules/index.ts","./src/rules/no-direct-impl-in-route.ts","./src/rules/no-direct-implementation-call.ts","./src/rules/no-sync-result-assumption.ts","./src/rules/no-throw-in-detour-target.ts","./src/rules/no-throw-in-implementation.ts","./src/rules/prefer-schema-inference.ts","./src/rules/provision-declarations.ts","./src/rules/provision-exists.ts","./src/rules/scan.ts","./src/rules/specs.ts","./src/rules/structure.ts","./src/rules/types.ts","./src/rules/valid-describe-refs.ts","./src/rules/valid-detour-refs.ts","./src/trails/context-no-trailhead-types.trail.ts","./src/trails/cross-declarations.trail.ts","./src/trails/implementation-returns-result.trail.ts","./src/trails/index.ts","./src/trails/no-direct-impl-in-route.trail.ts","./src/trails/no-direct-implementation-call.trail.ts","./src/trails/no-sync-result-assumption.trail.ts","./src/trails/no-throw-in-detour-target.trail.ts","./src/trails/no-throw-in-implementation.trail.ts","./src/trails/prefer-schema-inference.trail.ts","./src/trails/provision-declarations.trail.ts","./src/trails/provision-exists.trail.ts","./src/trails/run.ts","./src/trails/schema.ts","./src/trails/topo.ts","./src/trails/valid-describe-refs.trail.ts","./src/trails/valid-detour-refs.trail.ts","./src/trails/wrap-rule.ts"],"version":"5.9.3"}
|