@craft-ts/dev-tools 0.7.0-beta.13
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/LICENSE +21 -0
- package/README.md +154 -0
- package/generators.json +26 -0
- package/package.json +119 -0
- package/src/bin/craft-architecture-check.d.ts +2 -0
- package/src/bin/craft-architecture-check.js +51 -0
- package/src/bin/craft-architecture-check.js.map +1 -0
- package/src/bin/craft-brand.d.ts +2 -0
- package/src/bin/craft-brand.js +9 -0
- package/src/bin/craft-brand.js.map +1 -0
- package/src/bin/craft-graph.d.ts +2 -0
- package/src/bin/craft-graph.js +72 -0
- package/src/bin/craft-graph.js.map +1 -0
- package/src/bin/craft-migrate-architecture.d.ts +2 -0
- package/src/bin/craft-migrate-architecture.js +64 -0
- package/src/bin/craft-migrate-architecture.js.map +1 -0
- package/src/bin/craft-migrate-components.d.ts +2 -0
- package/src/bin/craft-migrate-components.js +67 -0
- package/src/bin/craft-migrate-components.js.map +1 -0
- package/src/bin/craft-migrate-primitives.d.ts +2 -0
- package/src/bin/craft-migrate-primitives.js +71 -0
- package/src/bin/craft-migrate-primitives.js.map +1 -0
- package/src/bin/craft-migrate-routes.d.ts +2 -0
- package/src/bin/craft-migrate-routes.js +77 -0
- package/src/bin/craft-migrate-routes.js.map +1 -0
- package/src/bin/craft-migrate-services.d.ts +2 -0
- package/src/bin/craft-migrate-services.js +75 -0
- package/src/bin/craft-migrate-services.js.map +1 -0
- package/src/bin/craft-migrate-template.d.ts +2 -0
- package/src/bin/craft-migrate-template.js +68 -0
- package/src/bin/craft-migrate-template.js.map +1 -0
- package/src/bin/craft-migrate.d.ts +2 -0
- package/src/bin/craft-migrate.js +91 -0
- package/src/bin/craft-migrate.js.map +1 -0
- package/src/bin/craft.d.ts +2 -0
- package/src/bin/craft.js +318 -0
- package/src/bin/craft.js.map +1 -0
- package/src/eslint-rules/anchor-has-href.cjs +35 -0
- package/src/eslint-rules/app-start-registry-match.cjs +355 -0
- package/src/eslint-rules/button-has-type.cjs +50 -0
- package/src/eslint-rules/control-has-accessible-name.cjs +56 -0
- package/src/eslint-rules/craft-component-name-match.cjs +8 -0
- package/src/eslint-rules/craft-computed-name-match.cjs +63 -0
- package/src/eslint-rules/craft-css-token-registry.cjs +17 -0
- package/src/eslint-rules/craft-css-var-naming.cjs +18 -0
- package/src/eslint-rules/craft-css-vars-contract.cjs +34 -0
- package/src/eslint-rules/craft-directive-name-match.cjs +8 -0
- package/src/eslint-rules/craft-method-name-match.cjs +8 -0
- package/src/eslint-rules/craft-name-match-utils.cjs +179 -0
- package/src/eslint-rules/craft-primitive-context.cjs +46 -0
- package/src/eslint-rules/craft-signal-source-name-match.cjs +8 -0
- package/src/eslint-rules/craft-source-name-match.cjs +8 -0
- package/src/eslint-rules/craft-styles-scope-safe.cjs +25 -0
- package/src/eslint-rules/css-rule-utils.cjs +109 -0
- package/src/eslint-rules/global-exception-registry-match.cjs +562 -0
- package/src/eslint-rules/heading-has-content.cjs +31 -0
- package/src/eslint-rules/html-helpers.cjs +115 -0
- package/src/eslint-rules/hyperscript-walk.cjs +249 -0
- package/src/eslint-rules/iframe-has-title.cjs +26 -0
- package/src/eslint-rules/img-has-alt.cjs +28 -0
- package/src/eslint-rules/index.cjs +209 -0
- package/src/eslint-rules/index.d.cts +5 -0
- package/src/eslint-rules/label-has-associated-control.cjs +67 -0
- package/src/eslint-rules/no-async-await.cjs +46 -0
- package/src/eslint-rules/no-craft-computed-side-effects.cjs +301 -0
- package/src/eslint-rules/no-craft-use-in-template.cjs +89 -0
- package/src/eslint-rules/no-craft-use.cjs +42 -0
- package/src/eslint-rules/no-direct-temporal-globals.cjs +73 -0
- package/src/eslint-rules/no-effect-in-params.cjs +265 -0
- package/src/eslint-rules/no-effect-outside-loaders.cjs +3 -0
- package/src/eslint-rules/no-ephemeral-template-form-state.cjs +131 -0
- package/src/eslint-rules/no-hardcoded-design-values.cjs +15 -0
- package/src/eslint-rules/no-heading-level-skip.cjs +101 -0
- package/src/eslint-rules/no-imperative-craft-resource-trigger.cjs +291 -0
- package/src/eslint-rules/no-important-in-component-styles.cjs +12 -0
- package/src/eslint-rules/no-injection-token.cjs +173 -0
- package/src/eslint-rules/no-invalid-insertion-pipe.cjs +162 -0
- package/src/eslint-rules/no-manual-route-provider-list.cjs +85 -0
- package/src/eslint-rules/no-noninteractive-element-interactions.cjs +66 -0
- package/src/eslint-rules/no-positive-tabindex.cjs +44 -0
- package/src/eslint-rules/no-redundant-primitive-insertion.cjs +361 -0
- package/src/eslint-rules/no-render-writes.cjs +106 -0
- package/src/eslint-rules/no-throw.cjs +132 -0
- package/src/eslint-rules/no-transition-actions.cjs +133 -0
- package/src/eslint-rules/no-type-assertions-in-template.cjs +211 -0
- package/src/eslint-rules/no-widened-route-provider-context.cjs +39 -0
- package/src/eslint-rules/prefer-browser-boundaries.cjs +92 -0
- package/src/eslint-rules/prefer-craft-http-transport.cjs +103 -0
- package/src/eslint-rules/prefer-craft-template-blocks.cjs +492 -0
- package/src/eslint-rules/prefer-direct-yieldable-callback.cjs +105 -0
- package/src/eslint-rules/prefer-named-html-helpers.cjs +33 -0
- package/src/eslint-rules/prefer-query-method-over-state-trigger.cjs +282 -0
- package/src/eslint-rules/prefer-relative-heading.cjs +43 -0
- package/src/eslint-rules/provide-host-name-match-component.cjs +455 -0
- package/src/eslint-rules/recommended-config.cjs +77 -0
- package/src/eslint-rules/require-assert-exhaustive-route-exceptions.cjs +259 -0
- package/src/eslint-rules/require-cascade-route-di-check.cjs +223 -0
- package/src/eslint-rules/require-child-route-mount-check.cjs +224 -0
- package/src/eslint-rules/require-component-monitoring.cjs +212 -0
- package/src/eslint-rules/require-craft-exception-handler.cjs +142 -0
- package/src/eslint-rules/require-craft-method-for-yieldable-callback.cjs +236 -0
- package/src/eslint-rules/require-craft-resource-trigger-yield.cjs +209 -0
- package/src/eslint-rules/require-effect-adapters.cjs +86 -0
- package/src/eslint-rules/require-exception-component-di-check.cjs +314 -0
- package/src/eslint-rules/require-focus-visible.cjs +36 -0
- package/src/eslint-rules/require-interactive-local-name.cjs +105 -0
- package/src/eslint-rules/require-lazy-load-with-retry.cjs +148 -0
- package/src/eslint-rules/require-outlet-heading-section.cjs +70 -0
- package/src/eslint-rules/require-pending-component-di-check.cjs +490 -0
- package/src/eslint-rules/require-primitive-context.cjs +156 -0
- package/src/eslint-rules/require-primitive-derived-property.cjs +598 -0
- package/src/eslint-rules/require-primitive-generator-unwrap.cjs +274 -0
- package/src/eslint-rules/require-reactive-template-bindings.cjs +305 -0
- package/src/eslint-rules/require-reduced-motion.cjs +38 -0
- package/src/eslint-rules/require-route-heading-outline.cjs +166 -0
- package/src/eslint-rules/require-yieldable-insertion-write.cjs +98 -0
- package/src/eslint-rules/require-yieldable-reactive-read.cjs +102 -0
- package/src/eslint-rules/require-yieldable-template-method.cjs +424 -0
- package/src/eslint-rules/role-has-required-aria.cjs +38 -0
- package/src/eslint-rules/server-function-client-match.cjs +283 -0
- package/src/eslint-rules/target-blank-noopener.cjs +31 -0
- package/src/eslint-rules/template-element-name-unique.cjs +124 -0
- package/src/eslint-rules/valid-aria.cjs +38 -0
- package/src/generators/route/compat.d.ts +3 -0
- package/src/generators/route/compat.js +6 -0
- package/src/generators/route/compat.js.map +1 -0
- package/src/generators/route/generator.d.ts +31 -0
- package/src/generators/route/generator.js +402 -0
- package/src/generators/route/generator.js.map +1 -0
- package/src/generators/route/schema.json +50 -0
- package/src/generators/route/split-schema.json +19 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +23 -0
- package/src/index.js.map +1 -0
- package/src/scripts/angular-brand-codemod.d.ts +139 -0
- package/src/scripts/angular-brand-codemod.js +2318 -0
- package/src/scripts/angular-brand-codemod.js.map +1 -0
- package/src/scripts/angular-brand-codemod.ts +3985 -0
- package/src/scripts/architecture/migrate-architecture.d.ts +14 -0
- package/src/scripts/architecture/migrate-architecture.js +534 -0
- package/src/scripts/architecture/migrate-architecture.js.map +1 -0
- package/src/scripts/architecture/migrate-architecture.ts +653 -0
- package/src/scripts/architecture-graph.d.ts +368 -0
- package/src/scripts/architecture-graph.js +2191 -0
- package/src/scripts/architecture-graph.js.map +1 -0
- package/src/scripts/architecture-graph.ts +3136 -0
- package/src/scripts/components/migrate-components.d.ts +20 -0
- package/src/scripts/components/migrate-components.js +163 -0
- package/src/scripts/components/migrate-components.js.map +1 -0
- package/src/scripts/components/migrate-components.ts +227 -0
- package/src/scripts/components/migration-diagnostic.d.ts +7 -0
- package/src/scripts/components/migration-diagnostic.js +2 -0
- package/src/scripts/components/migration-diagnostic.js.map +1 -0
- package/src/scripts/components/migration-diagnostic.ts +8 -0
- package/src/scripts/create/create-project.d.ts +20 -0
- package/src/scripts/create/create-project.js +783 -0
- package/src/scripts/create/create-project.js.map +1 -0
- package/src/scripts/create/create-project.ts +849 -0
- package/src/scripts/data-flow-graph.d.ts +36 -0
- package/src/scripts/data-flow-graph.js +186 -0
- package/src/scripts/data-flow-graph.js.map +1 -0
- package/src/scripts/data-flow-graph.ts +240 -0
- package/src/scripts/dependency-graph.d.ts +152 -0
- package/src/scripts/dependency-graph.js +4456 -0
- package/src/scripts/dependency-graph.js.map +1 -0
- package/src/scripts/dependency-graph.ts +5908 -0
- package/src/scripts/effect-dependency-graph.d.ts +89 -0
- package/src/scripts/effect-dependency-graph.js +487 -0
- package/src/scripts/effect-dependency-graph.js.map +1 -0
- package/src/scripts/effect-dependency-graph.ts +630 -0
- package/src/scripts/migrate.d.ts +37 -0
- package/src/scripts/migrate.js +98 -0
- package/src/scripts/migrate.js.map +1 -0
- package/src/scripts/migrate.ts +162 -0
- package/src/scripts/migration-workspace.d.ts +2 -0
- package/src/scripts/migration-workspace.js +67 -0
- package/src/scripts/migration-workspace.js.map +1 -0
- package/src/scripts/migration-workspace.ts +93 -0
- package/src/scripts/primitives/migrate-named-primitives.d.ts +50 -0
- package/src/scripts/primitives/migrate-named-primitives.js +487 -0
- package/src/scripts/primitives/migrate-named-primitives.js.map +1 -0
- package/src/scripts/primitives/migrate-named-primitives.ts +627 -0
- package/src/scripts/primitives/migrate-primitive-generators.d.ts +30 -0
- package/src/scripts/primitives/migrate-primitive-generators.js +309 -0
- package/src/scripts/primitives/migrate-primitive-generators.js.map +1 -0
- package/src/scripts/primitives/migrate-primitive-generators.ts +410 -0
- package/src/scripts/primitives/migrate-primitives.d.ts +27 -0
- package/src/scripts/primitives/migrate-primitives.js +427 -0
- package/src/scripts/primitives/migrate-primitives.js.map +1 -0
- package/src/scripts/primitives/migrate-primitives.ts +655 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.d.ts +23 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.js +382 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.js.map +1 -0
- package/src/scripts/primitives/migrate-yieldable-reactive-reads.ts +499 -0
- package/src/scripts/primitives/migration-diagnostic.d.ts +8 -0
- package/src/scripts/primitives/migration-diagnostic.js +2 -0
- package/src/scripts/primitives/migration-diagnostic.js.map +1 -0
- package/src/scripts/primitives/migration-diagnostic.ts +15 -0
- package/src/scripts/routes/migrate-routes.d.ts +34 -0
- package/src/scripts/routes/migrate-routes.js +657 -0
- package/src/scripts/routes/migrate-routes.js.map +1 -0
- package/src/scripts/routes/migrate-routes.ts +895 -0
- package/src/scripts/routes/migration-diagnostic.d.ts +16 -0
- package/src/scripts/routes/migration-diagnostic.js +2 -0
- package/src/scripts/routes/migration-diagnostic.js.map +1 -0
- package/src/scripts/routes/migration-diagnostic.ts +25 -0
- package/src/scripts/routes/route-command.d.ts +75 -0
- package/src/scripts/routes/route-command.js +1169 -0
- package/src/scripts/routes/route-command.js.map +1 -0
- package/src/scripts/routes/route-command.ts +1807 -0
- package/src/scripts/routes/verify-routes.d.ts +60 -0
- package/src/scripts/routes/verify-routes.js +1121 -0
- package/src/scripts/routes/verify-routes.js.map +1 -0
- package/src/scripts/routes/verify-routes.ts +1511 -0
- package/src/scripts/services/config.d.ts +2 -0
- package/src/scripts/services/config.js +38 -0
- package/src/scripts/services/config.js.map +1 -0
- package/src/scripts/services/config.ts +60 -0
- package/src/scripts/services/migrate-services.d.ts +29 -0
- package/src/scripts/services/migrate-services.js +924 -0
- package/src/scripts/services/migrate-services.js.map +1 -0
- package/src/scripts/services/migrate-services.ts +1348 -0
- package/src/scripts/services/migration-diagnostic.d.ts +8 -0
- package/src/scripts/services/migration-diagnostic.js +2 -0
- package/src/scripts/services/migration-diagnostic.js.map +1 -0
- package/src/scripts/services/migration-diagnostic.ts +28 -0
- package/src/scripts/template/migrate-template.d.ts +16 -0
- package/src/scripts/template/migrate-template.js +349 -0
- package/src/scripts/template/migrate-template.js.map +1 -0
- package/src/scripts/template/migrate-template.ts +418 -0
- package/src/template-migration.d.ts +1 -0
- package/src/template-migration.js +2 -0
- package/src/template-migration.js.map +1 -0
- package/src/testing.d.ts +19 -0
- package/src/testing.js +2 -0
- package/src/testing.js.map +1 -0
- package/src/tsconfig.codemod.json +13 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type SourceFile } from 'ts-morph';
|
|
2
|
+
import type { DependencyGraphEdge, DependencyGraphNode } from './dependency-graph';
|
|
3
|
+
export interface DataClassificationNodeDetails {
|
|
4
|
+
readonly classification: string;
|
|
5
|
+
readonly source: 'schema-annotation';
|
|
6
|
+
readonly schemaName: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ExternalOutputNodeDetails {
|
|
9
|
+
readonly boundary: 'server-function-response';
|
|
10
|
+
readonly serverFunctionId?: string;
|
|
11
|
+
readonly schemaName?: string;
|
|
12
|
+
readonly classifications: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface DataFlowRelationDetails {
|
|
15
|
+
readonly classification: string;
|
|
16
|
+
readonly resolution: 'static' | 'unknown';
|
|
17
|
+
}
|
|
18
|
+
declare module './dependency-graph' {
|
|
19
|
+
interface DependencyGraphNodeRegistry {
|
|
20
|
+
'data-classification': DataClassificationNodeDetails;
|
|
21
|
+
'external-output': ExternalOutputNodeDetails;
|
|
22
|
+
}
|
|
23
|
+
interface DependencyGraphEdgeRegistry {
|
|
24
|
+
'exposes-data': DataFlowRelationDetails;
|
|
25
|
+
'flows-data': DataFlowRelationDetails;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extracts schema annotations and server-function response boundaries. The
|
|
30
|
+
* propagation is intentionally conservative: only statically named schema
|
|
31
|
+
* variables are followed, and no transform is assumed to remove sensitivity.
|
|
32
|
+
*/
|
|
33
|
+
export declare function collectDataFlowGraph(sourceFiles: readonly SourceFile[]): {
|
|
34
|
+
nodes: DependencyGraphNode[];
|
|
35
|
+
edges: DependencyGraphEdge[];
|
|
36
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Node, SyntaxKind, } from 'ts-morph';
|
|
2
|
+
const classificationId = (filePath, name, value) => `data-classification:${filePath}#${name}:${value}`;
|
|
3
|
+
const outputId = (filePath, line) => `external-output:${filePath}:${line}`;
|
|
4
|
+
/**
|
|
5
|
+
* Extracts schema annotations and server-function response boundaries. The
|
|
6
|
+
* propagation is intentionally conservative: only statically named schema
|
|
7
|
+
* variables are followed, and no transform is assumed to remove sensitivity.
|
|
8
|
+
*/
|
|
9
|
+
export function collectDataFlowGraph(sourceFiles) {
|
|
10
|
+
const nodes = new Map();
|
|
11
|
+
const edges = new Map();
|
|
12
|
+
const schemas = new Map();
|
|
13
|
+
const schemaByName = new Map();
|
|
14
|
+
const addNode = (node) => {
|
|
15
|
+
if (!nodes.has(node.id))
|
|
16
|
+
nodes.set(node.id, node);
|
|
17
|
+
};
|
|
18
|
+
const addEdge = (edge) => {
|
|
19
|
+
const key = `${edge.from}:${edge.kind}:${edge.to}`;
|
|
20
|
+
if (!edges.has(key))
|
|
21
|
+
edges.set(key, edge);
|
|
22
|
+
};
|
|
23
|
+
const proof = (node, pattern) => ({
|
|
24
|
+
filePath: node.getSourceFile().getFilePath(),
|
|
25
|
+
line: node.getStartLineNumber(),
|
|
26
|
+
pattern,
|
|
27
|
+
});
|
|
28
|
+
for (const sourceFile of sourceFiles) {
|
|
29
|
+
for (const declaration of sourceFile.getVariableDeclarations()) {
|
|
30
|
+
const initializer = declaration.getInitializer();
|
|
31
|
+
if (!initializer || !containsSchema(initializer))
|
|
32
|
+
continue;
|
|
33
|
+
const name = declaration.getName();
|
|
34
|
+
const info = {
|
|
35
|
+
declaration,
|
|
36
|
+
filePath: sourceFile.getFilePath(),
|
|
37
|
+
name,
|
|
38
|
+
classifications: new Set(findSchemaAnnotations(initializer)),
|
|
39
|
+
};
|
|
40
|
+
schemas.set(`${sourceFile.getFilePath()}#${name}`, info);
|
|
41
|
+
schemaByName.set(name, [...(schemaByName.get(name) ?? []), info]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const classifications = new Map();
|
|
45
|
+
for (const info of schemas.values()) {
|
|
46
|
+
for (const value of info.classifications) {
|
|
47
|
+
const annotation = findSchemaAnnotation(info.declaration.getInitializer(), value);
|
|
48
|
+
if (!annotation)
|
|
49
|
+
continue;
|
|
50
|
+
classifications.set(`${info.filePath}#${info.name}:${value}`, {
|
|
51
|
+
schema: info,
|
|
52
|
+
value,
|
|
53
|
+
node: annotation,
|
|
54
|
+
});
|
|
55
|
+
addNode({
|
|
56
|
+
id: classificationId(info.filePath, info.name, value),
|
|
57
|
+
kind: 'data-classification',
|
|
58
|
+
label: `${info.name} [${value}]`,
|
|
59
|
+
filePath: info.filePath,
|
|
60
|
+
line: info.declaration.getStartLineNumber(),
|
|
61
|
+
details: {
|
|
62
|
+
classification: value,
|
|
63
|
+
source: 'schema-annotation',
|
|
64
|
+
schemaName: info.name,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const classificationsFor = (info, seen = new Set()) => {
|
|
70
|
+
const key = `${info.filePath}#${info.name}`;
|
|
71
|
+
if (seen.has(key))
|
|
72
|
+
return new Set();
|
|
73
|
+
const nextSeen = new Set([...seen, key]);
|
|
74
|
+
const result = new Set(info.classifications);
|
|
75
|
+
const initializer = info.declaration.getInitializer();
|
|
76
|
+
if (!initializer)
|
|
77
|
+
return result;
|
|
78
|
+
for (const identifier of initializer.getDescendantsOfKind(SyntaxKind.Identifier)) {
|
|
79
|
+
for (const target of schemaByName.get(identifier.getText()) ?? []) {
|
|
80
|
+
for (const value of classificationsFor(target, nextSeen))
|
|
81
|
+
result.add(value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
for (const sourceFile of sourceFiles) {
|
|
87
|
+
if (!sourceFile.getBaseName().endsWith('.fn-serveur.ts'))
|
|
88
|
+
continue;
|
|
89
|
+
const serverFunction = sourceFile
|
|
90
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
91
|
+
.find((call) => call.getExpression().getText() === 'serverFunction');
|
|
92
|
+
if (!serverFunction)
|
|
93
|
+
continue;
|
|
94
|
+
const id = serverFunction
|
|
95
|
+
.getArguments()[0]
|
|
96
|
+
?.asKind(SyntaxKind.StringLiteral)
|
|
97
|
+
?.getLiteralValue();
|
|
98
|
+
const options = serverFunction
|
|
99
|
+
.getArguments()[2]
|
|
100
|
+
?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
101
|
+
const output = options
|
|
102
|
+
?.getProperty('output')
|
|
103
|
+
?.asKind(SyntaxKind.PropertyAssignment)
|
|
104
|
+
?.getInitializer();
|
|
105
|
+
if (!output)
|
|
106
|
+
continue;
|
|
107
|
+
const schemaName = output.asKind(SyntaxKind.Identifier)?.getText();
|
|
108
|
+
const schema = schemaName
|
|
109
|
+
? [...(schemaByName.get(schemaName) ?? [])][0]
|
|
110
|
+
: undefined;
|
|
111
|
+
const values = schema ? [...classificationsFor(schema)] : [];
|
|
112
|
+
const outputNode = {
|
|
113
|
+
id: outputId(sourceFile.getFilePath(), serverFunction.getStartLineNumber()),
|
|
114
|
+
kind: 'external-output',
|
|
115
|
+
label: `${id ?? 'server-function'} response`,
|
|
116
|
+
filePath: sourceFile.getFilePath(),
|
|
117
|
+
line: serverFunction.getStartLineNumber(),
|
|
118
|
+
details: {
|
|
119
|
+
boundary: 'server-function-response',
|
|
120
|
+
...(id === undefined ? {} : { serverFunctionId: id }),
|
|
121
|
+
...(schemaName === undefined ? {} : { schemaName }),
|
|
122
|
+
classifications: values,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
addNode(outputNode);
|
|
126
|
+
for (const value of values) {
|
|
127
|
+
const source = [...classifications.values()].find((candidate) => candidate.value === value &&
|
|
128
|
+
classificationsFor(candidate.schema).has(value));
|
|
129
|
+
if (!source)
|
|
130
|
+
continue;
|
|
131
|
+
addEdge({
|
|
132
|
+
from: classificationId(source.schema.filePath, source.schema.name, value),
|
|
133
|
+
to: outputNode.id,
|
|
134
|
+
kind: 'exposes-data',
|
|
135
|
+
evidence: 'ast',
|
|
136
|
+
details: { classification: value, resolution: 'static' },
|
|
137
|
+
proof: proof(output, `server function output: ${schemaName ?? 'unknown schema'}`),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { nodes: [...nodes.values()], edges: [...edges.values()] };
|
|
142
|
+
}
|
|
143
|
+
function containsSchema(node) {
|
|
144
|
+
return [
|
|
145
|
+
...(Node.isCallExpression(node) ? [node] : []),
|
|
146
|
+
...node.getDescendantsOfKind(SyntaxKind.CallExpression),
|
|
147
|
+
].some((call) => call.getExpression().getText().startsWith('Schema.'));
|
|
148
|
+
}
|
|
149
|
+
function findSchemaAnnotations(node) {
|
|
150
|
+
const values = new Set();
|
|
151
|
+
for (const call of [
|
|
152
|
+
...(Node.isCallExpression(node) ? [node] : []),
|
|
153
|
+
...node.getDescendantsOfKind(SyntaxKind.CallExpression),
|
|
154
|
+
]) {
|
|
155
|
+
const value = readSensitivity(call);
|
|
156
|
+
if (value)
|
|
157
|
+
values.add(value);
|
|
158
|
+
}
|
|
159
|
+
return [...values];
|
|
160
|
+
}
|
|
161
|
+
function findSchemaAnnotation(node, value) {
|
|
162
|
+
if (!node)
|
|
163
|
+
return undefined;
|
|
164
|
+
return [
|
|
165
|
+
...(Node.isCallExpression(node) ? [node] : []),
|
|
166
|
+
...node.getDescendantsOfKind(SyntaxKind.CallExpression),
|
|
167
|
+
].find((call) => readSensitivity(call) === value);
|
|
168
|
+
}
|
|
169
|
+
function readSensitivity(call) {
|
|
170
|
+
// Effect expose l'annotation en méthode (`Schema.String.annotate({ … })`) ;
|
|
171
|
+
// `annotations` reste accepté pour les variantes qui l'orthographient ainsi.
|
|
172
|
+
const callee = call.getExpression().getText();
|
|
173
|
+
if (!callee.endsWith('annotate') && !callee.endsWith('annotations')) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const object = call
|
|
177
|
+
.getArguments()[0]
|
|
178
|
+
?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
179
|
+
return object
|
|
180
|
+
?.getProperty('sensitivity')
|
|
181
|
+
?.asKind(SyntaxKind.PropertyAssignment)
|
|
182
|
+
?.getInitializer()
|
|
183
|
+
?.asKind(SyntaxKind.StringLiteral)
|
|
184
|
+
?.getLiteralValue();
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=data-flow-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-flow-graph.js","sourceRoot":"","sources":["../../../../../libs/dev-tools/src/scripts/data-flow-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Node,
|
|
3
|
+
SyntaxKind,
|
|
4
|
+
type CallExpression,
|
|
5
|
+
type SourceFile,
|
|
6
|
+
type VariableDeclaration,
|
|
7
|
+
} from 'ts-morph';
|
|
8
|
+
import type {
|
|
9
|
+
DependencyGraphEdge,
|
|
10
|
+
DependencyGraphNode,
|
|
11
|
+
DependencyGraphProof,
|
|
12
|
+
} from './dependency-graph';
|
|
13
|
+
|
|
14
|
+
export interface DataClassificationNodeDetails {
|
|
15
|
+
readonly classification: string;
|
|
16
|
+
readonly source: 'schema-annotation';
|
|
17
|
+
readonly schemaName: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ExternalOutputNodeDetails {
|
|
21
|
+
readonly boundary: 'server-function-response';
|
|
22
|
+
readonly serverFunctionId?: string;
|
|
23
|
+
readonly schemaName?: string;
|
|
24
|
+
readonly classifications: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DataFlowRelationDetails {
|
|
28
|
+
readonly classification: string;
|
|
29
|
+
readonly resolution: 'static' | 'unknown';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare module './dependency-graph' {
|
|
33
|
+
interface DependencyGraphNodeRegistry {
|
|
34
|
+
'data-classification': DataClassificationNodeDetails;
|
|
35
|
+
'external-output': ExternalOutputNodeDetails;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface DependencyGraphEdgeRegistry {
|
|
39
|
+
'exposes-data': DataFlowRelationDetails;
|
|
40
|
+
'flows-data': DataFlowRelationDetails;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type SchemaInfo = {
|
|
45
|
+
readonly declaration: VariableDeclaration;
|
|
46
|
+
readonly filePath: string;
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly classifications: Set<string>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const classificationId = (filePath: string, name: string, value: string) =>
|
|
52
|
+
`data-classification:${filePath}#${name}:${value}`;
|
|
53
|
+
|
|
54
|
+
const outputId = (filePath: string, line: number) =>
|
|
55
|
+
`external-output:${filePath}:${line}`;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extracts schema annotations and server-function response boundaries. The
|
|
59
|
+
* propagation is intentionally conservative: only statically named schema
|
|
60
|
+
* variables are followed, and no transform is assumed to remove sensitivity.
|
|
61
|
+
*/
|
|
62
|
+
export function collectDataFlowGraph(
|
|
63
|
+
sourceFiles: readonly SourceFile[],
|
|
64
|
+
): { nodes: DependencyGraphNode[]; edges: DependencyGraphEdge[] } {
|
|
65
|
+
const nodes = new Map<string, DependencyGraphNode>();
|
|
66
|
+
const edges = new Map<string, DependencyGraphEdge>();
|
|
67
|
+
const schemas = new Map<string, SchemaInfo>();
|
|
68
|
+
const schemaByName = new Map<string, SchemaInfo[]>();
|
|
69
|
+
|
|
70
|
+
const addNode = (node: DependencyGraphNode) => {
|
|
71
|
+
if (!nodes.has(node.id)) nodes.set(node.id, node);
|
|
72
|
+
};
|
|
73
|
+
const addEdge = (edge: DependencyGraphEdge) => {
|
|
74
|
+
const key = `${edge.from}:${edge.kind}:${edge.to}`;
|
|
75
|
+
if (!edges.has(key)) edges.set(key, edge);
|
|
76
|
+
};
|
|
77
|
+
const proof = (node: Node, pattern: string): DependencyGraphProof => ({
|
|
78
|
+
filePath: node.getSourceFile().getFilePath(),
|
|
79
|
+
line: node.getStartLineNumber(),
|
|
80
|
+
pattern,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
for (const sourceFile of sourceFiles) {
|
|
84
|
+
for (const declaration of sourceFile.getVariableDeclarations()) {
|
|
85
|
+
const initializer = declaration.getInitializer();
|
|
86
|
+
if (!initializer || !containsSchema(initializer)) continue;
|
|
87
|
+
const name = declaration.getName();
|
|
88
|
+
const info: SchemaInfo = {
|
|
89
|
+
declaration,
|
|
90
|
+
filePath: sourceFile.getFilePath(),
|
|
91
|
+
name,
|
|
92
|
+
classifications: new Set(findSchemaAnnotations(initializer)),
|
|
93
|
+
};
|
|
94
|
+
schemas.set(`${sourceFile.getFilePath()}#${name}`, info);
|
|
95
|
+
schemaByName.set(name, [...(schemaByName.get(name) ?? []), info]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const classifications = new Map<string, { schema: SchemaInfo; value: string; node: Node }>();
|
|
100
|
+
for (const info of schemas.values()) {
|
|
101
|
+
for (const value of info.classifications) {
|
|
102
|
+
const annotation = findSchemaAnnotation(info.declaration.getInitializer(), value);
|
|
103
|
+
if (!annotation) continue;
|
|
104
|
+
classifications.set(`${info.filePath}#${info.name}:${value}`, {
|
|
105
|
+
schema: info,
|
|
106
|
+
value,
|
|
107
|
+
node: annotation,
|
|
108
|
+
});
|
|
109
|
+
addNode({
|
|
110
|
+
id: classificationId(info.filePath, info.name, value),
|
|
111
|
+
kind: 'data-classification',
|
|
112
|
+
label: `${info.name} [${value}]`,
|
|
113
|
+
filePath: info.filePath,
|
|
114
|
+
line: info.declaration.getStartLineNumber(),
|
|
115
|
+
details: {
|
|
116
|
+
classification: value,
|
|
117
|
+
source: 'schema-annotation',
|
|
118
|
+
schemaName: info.name,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const classificationsFor = (info: SchemaInfo, seen = new Set<string>()): Set<string> => {
|
|
125
|
+
const key = `${info.filePath}#${info.name}`;
|
|
126
|
+
if (seen.has(key)) return new Set();
|
|
127
|
+
const nextSeen = new Set([...seen, key]);
|
|
128
|
+
const result = new Set(info.classifications);
|
|
129
|
+
const initializer = info.declaration.getInitializer();
|
|
130
|
+
if (!initializer) return result;
|
|
131
|
+
for (const identifier of initializer.getDescendantsOfKind(SyntaxKind.Identifier)) {
|
|
132
|
+
for (const target of schemaByName.get(identifier.getText()) ?? []) {
|
|
133
|
+
for (const value of classificationsFor(target, nextSeen)) result.add(value);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
for (const sourceFile of sourceFiles) {
|
|
140
|
+
if (!sourceFile.getBaseName().endsWith('.fn-serveur.ts')) continue;
|
|
141
|
+
const serverFunction = sourceFile
|
|
142
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
143
|
+
.find((call) => call.getExpression().getText() === 'serverFunction');
|
|
144
|
+
if (!serverFunction) continue;
|
|
145
|
+
const id = serverFunction
|
|
146
|
+
.getArguments()[0]
|
|
147
|
+
?.asKind(SyntaxKind.StringLiteral)
|
|
148
|
+
?.getLiteralValue();
|
|
149
|
+
const options = serverFunction
|
|
150
|
+
.getArguments()[2]
|
|
151
|
+
?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
152
|
+
const output = options
|
|
153
|
+
?.getProperty('output')
|
|
154
|
+
?.asKind(SyntaxKind.PropertyAssignment)
|
|
155
|
+
?.getInitializer();
|
|
156
|
+
if (!output) continue;
|
|
157
|
+
const schemaName = output.asKind(SyntaxKind.Identifier)?.getText();
|
|
158
|
+
const schema = schemaName
|
|
159
|
+
? [...(schemaByName.get(schemaName) ?? [])][0]
|
|
160
|
+
: undefined;
|
|
161
|
+
const values = schema ? [...classificationsFor(schema)] : [];
|
|
162
|
+
const outputNode: DependencyGraphNode = {
|
|
163
|
+
id: outputId(sourceFile.getFilePath(), serverFunction.getStartLineNumber()),
|
|
164
|
+
kind: 'external-output',
|
|
165
|
+
label: `${id ?? 'server-function'} response`,
|
|
166
|
+
filePath: sourceFile.getFilePath(),
|
|
167
|
+
line: serverFunction.getStartLineNumber(),
|
|
168
|
+
details: {
|
|
169
|
+
boundary: 'server-function-response',
|
|
170
|
+
...(id === undefined ? {} : { serverFunctionId: id }),
|
|
171
|
+
...(schemaName === undefined ? {} : { schemaName }),
|
|
172
|
+
classifications: values,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
addNode(outputNode);
|
|
176
|
+
|
|
177
|
+
for (const value of values) {
|
|
178
|
+
const source = [...classifications.values()].find(
|
|
179
|
+
(candidate) => candidate.value === value &&
|
|
180
|
+
classificationsFor(candidate.schema).has(value),
|
|
181
|
+
);
|
|
182
|
+
if (!source) continue;
|
|
183
|
+
addEdge({
|
|
184
|
+
from: classificationId(source.schema.filePath, source.schema.name, value),
|
|
185
|
+
to: outputNode.id,
|
|
186
|
+
kind: 'exposes-data',
|
|
187
|
+
evidence: 'ast',
|
|
188
|
+
details: { classification: value, resolution: 'static' },
|
|
189
|
+
proof: proof(output, `server function output: ${schemaName ?? 'unknown schema'}`),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { nodes: [...nodes.values()], edges: [...edges.values()] };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function containsSchema(node: Node): boolean {
|
|
198
|
+
return [
|
|
199
|
+
...(Node.isCallExpression(node) ? [node] : []),
|
|
200
|
+
...node.getDescendantsOfKind(SyntaxKind.CallExpression),
|
|
201
|
+
].some((call) => call.getExpression().getText().startsWith('Schema.'));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function findSchemaAnnotations(node: Node): string[] {
|
|
205
|
+
const values = new Set<string>();
|
|
206
|
+
for (const call of [
|
|
207
|
+
...(Node.isCallExpression(node) ? [node] : []),
|
|
208
|
+
...node.getDescendantsOfKind(SyntaxKind.CallExpression),
|
|
209
|
+
]) {
|
|
210
|
+
const value = readSensitivity(call);
|
|
211
|
+
if (value) values.add(value);
|
|
212
|
+
}
|
|
213
|
+
return [...values];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function findSchemaAnnotation(node: Node | undefined, value: string): Node | undefined {
|
|
217
|
+
if (!node) return undefined;
|
|
218
|
+
return [
|
|
219
|
+
...(Node.isCallExpression(node) ? [node] : []),
|
|
220
|
+
...node.getDescendantsOfKind(SyntaxKind.CallExpression),
|
|
221
|
+
].find((call) => readSensitivity(call) === value);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function readSensitivity(call: CallExpression): string | undefined {
|
|
225
|
+
// Effect expose l'annotation en méthode (`Schema.String.annotate({ … })`) ;
|
|
226
|
+
// `annotations` reste accepté pour les variantes qui l'orthographient ainsi.
|
|
227
|
+
const callee = call.getExpression().getText();
|
|
228
|
+
if (!callee.endsWith('annotate') && !callee.endsWith('annotations')) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
const object = call
|
|
232
|
+
.getArguments()[0]
|
|
233
|
+
?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
234
|
+
return object
|
|
235
|
+
?.getProperty('sensitivity')
|
|
236
|
+
?.asKind(SyntaxKind.PropertyAssignment)
|
|
237
|
+
?.getInitializer()
|
|
238
|
+
?.asKind(SyntaxKind.StringLiteral)
|
|
239
|
+
?.getLiteralValue();
|
|
240
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Project, SourceFile } from 'ts-morph';
|
|
2
|
+
import './effect-dependency-graph.js';
|
|
3
|
+
import './data-flow-graph.js';
|
|
4
|
+
/**
|
|
5
|
+
* The graph's built-in vocabulary. Values are deliberately detail records
|
|
6
|
+
* rather than node classes: consumers can augment this interface with module
|
|
7
|
+
* augmentation without making the graph core import their runtime.
|
|
8
|
+
*/
|
|
9
|
+
export interface DependencyGraphNodeRegistry {
|
|
10
|
+
route: Record<string, unknown>;
|
|
11
|
+
'route-hook': Record<string, unknown>;
|
|
12
|
+
'route-check': Record<string, unknown>;
|
|
13
|
+
'app-config': Record<string, unknown>;
|
|
14
|
+
component: Record<string, unknown>;
|
|
15
|
+
service: Record<string, unknown>;
|
|
16
|
+
property: Record<string, unknown>;
|
|
17
|
+
primitive: Record<string, unknown>;
|
|
18
|
+
source: Record<string, unknown>;
|
|
19
|
+
'http-endpoint': Record<string, unknown>;
|
|
20
|
+
unique: Record<string, unknown>;
|
|
21
|
+
'template-element': Record<string, unknown>;
|
|
22
|
+
'server-function-family': Record<string, unknown>;
|
|
23
|
+
'server-function-contract': Record<string, unknown>;
|
|
24
|
+
'server-function-client': Record<string, unknown>;
|
|
25
|
+
'server-function-server': Record<string, unknown>;
|
|
26
|
+
'server-function-misnamed': Record<string, unknown>;
|
|
27
|
+
'server-function-middleware': Record<string, unknown>;
|
|
28
|
+
'server-function-middleware-misnamed': Record<string, unknown>;
|
|
29
|
+
'client-function-middleware': Record<string, unknown>;
|
|
30
|
+
'client-function-middleware-misnamed': Record<string, unknown>;
|
|
31
|
+
handshake: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
export type DependencyGraphNodeKind = keyof DependencyGraphNodeRegistry;
|
|
34
|
+
export type DependencyGraphNodeFor<K extends DependencyGraphNodeKind = DependencyGraphNodeKind> = {
|
|
35
|
+
id: string;
|
|
36
|
+
kind: K;
|
|
37
|
+
label: string;
|
|
38
|
+
filePath?: string;
|
|
39
|
+
line?: number;
|
|
40
|
+
details?: DependencyGraphNodeRegistry[K];
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The built-in relation vocabulary. Relation details are open for the same
|
|
44
|
+
* reason as node details: an adapter can add a typed relation without
|
|
45
|
+
* changing this module.
|
|
46
|
+
*/
|
|
47
|
+
export interface DependencyGraphEdgeRegistry {
|
|
48
|
+
loads: Record<string, unknown>;
|
|
49
|
+
renders: Record<string, unknown>;
|
|
50
|
+
contains: Record<string, unknown>;
|
|
51
|
+
checks: Record<string, unknown>;
|
|
52
|
+
'depends-on': Record<string, unknown>;
|
|
53
|
+
provides: Record<string, unknown>;
|
|
54
|
+
'uses-property': Record<string, unknown>;
|
|
55
|
+
calls: Record<string, unknown>;
|
|
56
|
+
reads: Record<string, unknown>;
|
|
57
|
+
writes: Record<string, unknown>;
|
|
58
|
+
subscribes: Record<string, unknown>;
|
|
59
|
+
triggers: Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
export type DependencyGraphEdgeKind = keyof DependencyGraphEdgeRegistry;
|
|
62
|
+
export type DependencyGraphEdgeFor<K extends DependencyGraphEdgeKind = DependencyGraphEdgeKind> = {
|
|
63
|
+
from: string;
|
|
64
|
+
to: string;
|
|
65
|
+
kind: K;
|
|
66
|
+
evidence: 'ast' | 'type';
|
|
67
|
+
details?: DependencyGraphEdgeRegistry[K];
|
|
68
|
+
proof?: DependencyGraphProof;
|
|
69
|
+
};
|
|
70
|
+
export type DependencyGraphProof = {
|
|
71
|
+
filePath: string;
|
|
72
|
+
line?: number;
|
|
73
|
+
symbol?: string;
|
|
74
|
+
pattern?: string;
|
|
75
|
+
};
|
|
76
|
+
export type DependencyGraphDiagnostic = {
|
|
77
|
+
code: string;
|
|
78
|
+
message: string;
|
|
79
|
+
proof?: DependencyGraphProof;
|
|
80
|
+
};
|
|
81
|
+
export type RouteCheckMechanism = 'ValidateCascadeRoutesFile' | 'RouteCheckedDI' | 'RouteExceptionComponentCheckedDI' | 'CanRun' | 'assertExhaustiveRouteExceptions';
|
|
82
|
+
export type DependencyGraphNode = {
|
|
83
|
+
id: string;
|
|
84
|
+
kind: DependencyGraphNodeKind;
|
|
85
|
+
label: string;
|
|
86
|
+
filePath?: string;
|
|
87
|
+
line?: number;
|
|
88
|
+
details?: Record<string, unknown>;
|
|
89
|
+
};
|
|
90
|
+
export type DependencyGraphHttpEndpoint = {
|
|
91
|
+
method: string;
|
|
92
|
+
url: string;
|
|
93
|
+
line: number;
|
|
94
|
+
};
|
|
95
|
+
export type DependencyGraphTemporalOperation = {
|
|
96
|
+
operation: string;
|
|
97
|
+
delay?: string;
|
|
98
|
+
line: number;
|
|
99
|
+
};
|
|
100
|
+
export type DependencyGraphEdge = {
|
|
101
|
+
from: string;
|
|
102
|
+
to: string;
|
|
103
|
+
kind: DependencyGraphEdgeKind;
|
|
104
|
+
evidence: 'ast' | 'type';
|
|
105
|
+
details?: Record<string, unknown>;
|
|
106
|
+
proof?: DependencyGraphProof;
|
|
107
|
+
};
|
|
108
|
+
export type DependencyGraph = {
|
|
109
|
+
version: 1;
|
|
110
|
+
rootDir: string;
|
|
111
|
+
tsConfigFilePath: string;
|
|
112
|
+
nodes: DependencyGraphNode[];
|
|
113
|
+
edges: DependencyGraphEdge[];
|
|
114
|
+
diagnostics?: DependencyGraphDiagnostic[];
|
|
115
|
+
};
|
|
116
|
+
export type AnalyzeDependencyGraphOptions = {
|
|
117
|
+
rootDir?: string;
|
|
118
|
+
tsConfigFilePath?: string;
|
|
119
|
+
include?: readonly string[];
|
|
120
|
+
/** Additional collectors are opt-in; importing their types has no effect. */
|
|
121
|
+
collectors?: readonly DependencyGraphCollector[];
|
|
122
|
+
/** Repository-declared middleware capabilities used by security rules. */
|
|
123
|
+
middlewareCapabilities?: Readonly<Record<string, readonly string[]>>;
|
|
124
|
+
};
|
|
125
|
+
export type DependencyGraphCollectorContext = {
|
|
126
|
+
readonly project: Project;
|
|
127
|
+
readonly rootDir: string;
|
|
128
|
+
readonly sourceFiles: readonly SourceFile[];
|
|
129
|
+
readonly graph: Readonly<DependencyGraph>;
|
|
130
|
+
};
|
|
131
|
+
export type DependencyGraphContribution = {
|
|
132
|
+
readonly nodes?: readonly DependencyGraphNode[];
|
|
133
|
+
readonly edges?: readonly DependencyGraphEdge[];
|
|
134
|
+
readonly diagnostics?: readonly DependencyGraphDiagnostic[];
|
|
135
|
+
};
|
|
136
|
+
export type DependencyGraphCollector = {
|
|
137
|
+
readonly name: string;
|
|
138
|
+
readonly collect: (context: DependencyGraphCollectorContext) => DependencyGraphContribution;
|
|
139
|
+
};
|
|
140
|
+
export type WriteDependencyGraphOptions = AnalyzeDependencyGraphOptions & {
|
|
141
|
+
outputPath: string;
|
|
142
|
+
format?: 'json' | 'mermaid' | 'html' | 'both' | 'all';
|
|
143
|
+
};
|
|
144
|
+
export declare function analyzeDependencyGraph(options?: AnalyzeDependencyGraphOptions): DependencyGraph;
|
|
145
|
+
export declare function writeDependencyGraph(options: WriteDependencyGraphOptions): Promise<DependencyGraph>;
|
|
146
|
+
export declare function dependencyGraphToMermaid(graph: DependencyGraph): string;
|
|
147
|
+
/**
|
|
148
|
+
* Creates a self-contained, static explorer. The graph is embedded in the file
|
|
149
|
+
* so opening the HTML never needs the source tree, a server, or application
|
|
150
|
+
* runtime instrumentation.
|
|
151
|
+
*/
|
|
152
|
+
export declare function dependencyGraphToHtml(graph: DependencyGraph): string;
|