@supacloud/compiler 0.2.0 → 0.3.1
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/dist/index.d.ts +1 -1
- package/dist/index.js +38 -2
- package/dist/types.d.ts +12 -0
- package/dist/validate.d.ts +5 -2
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export { generateApplication } from "./generate";
|
|
|
4
4
|
export type { GenerateOptions } from "./generate";
|
|
5
5
|
export { validateGraph } from "./validate";
|
|
6
6
|
export { camelName } from "./util";
|
|
7
|
-
export type { ApplicationGraph, CommandNode, CompileOptions, CompileResult, ControllerNode, Diagnostic, ModuleNode, ProviderKind, ProviderNode, QueryNode, RouteNode, Scope, TokenKind, } from "./types";
|
|
7
|
+
export type { ApplicationGraph, CommandNode, CompileOptions, CompileResult, ControllerNode, Diagnostic, ModuleBoundaryRule, ModuleNode, ProviderKind, ProviderNode, QueryNode, RouteNode, Scope, TokenKind, } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -137,6 +137,7 @@ function parseTokenVariable(decl, file) {
|
|
|
137
137
|
function parseModule(candidate, nameByNode, ctx) {
|
|
138
138
|
const { options, className, file, line } = candidate;
|
|
139
139
|
const name = nameByNode.get(candidate.node) ?? className;
|
|
140
|
+
const tags = arrayProp(options, "tags").map((el) => Node.isStringLiteral(el) ? el.getLiteralText() : el.getText().replace(/['"]/g, "")).filter(Boolean);
|
|
140
141
|
const imports = arrayProp(options, "imports").map((el) => {
|
|
141
142
|
const decl = Node.isIdentifier(el) ? resolveDeclaration(el)[0] : undefined;
|
|
142
143
|
if (decl) {
|
|
@@ -221,6 +222,7 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
221
222
|
return {
|
|
222
223
|
name,
|
|
223
224
|
className,
|
|
225
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
224
226
|
file: sourcePath(ctx.rootDir, file),
|
|
225
227
|
line,
|
|
226
228
|
imports,
|
|
@@ -1012,7 +1014,9 @@ var SCOPE_LIFETIME_RANK = {
|
|
|
1012
1014
|
request: 1,
|
|
1013
1015
|
job: 1
|
|
1014
1016
|
};
|
|
1015
|
-
function validateGraph(graph,
|
|
1017
|
+
function validateGraph(graph, options = false) {
|
|
1018
|
+
const strict = typeof options === "boolean" ? options : options.strict ?? false;
|
|
1019
|
+
const moduleBoundaries = typeof options === "object" ? options.moduleBoundaries : undefined;
|
|
1016
1020
|
const diagnostics = [];
|
|
1017
1021
|
const globalProviders = new Map;
|
|
1018
1022
|
for (const module of graph.modules) {
|
|
@@ -1113,6 +1117,35 @@ function validateGraph(graph, strict = false) {
|
|
|
1113
1117
|
}
|
|
1114
1118
|
}
|
|
1115
1119
|
}
|
|
1120
|
+
if (moduleBoundaries && moduleBoundaries.length > 0) {
|
|
1121
|
+
for (const module of graph.modules) {
|
|
1122
|
+
const sourceTags = module.tags ?? [];
|
|
1123
|
+
for (const importName of module.imports) {
|
|
1124
|
+
const targetModule = graph.modules.find((m) => m.name === importName);
|
|
1125
|
+
if (!targetModule)
|
|
1126
|
+
continue;
|
|
1127
|
+
const targetTags = targetModule.tags ?? [];
|
|
1128
|
+
for (const rule of moduleBoundaries) {
|
|
1129
|
+
const matchesSource = rule.sourceTag === "*" || sourceTags.includes(rule.sourceTag);
|
|
1130
|
+
if (!matchesSource)
|
|
1131
|
+
continue;
|
|
1132
|
+
if (rule.bannedDependenciesWithTags) {
|
|
1133
|
+
for (const bannedTag of rule.bannedDependenciesWithTags) {
|
|
1134
|
+
if (targetTags.includes(bannedTag)) {
|
|
1135
|
+
error("module-boundary-violation", `模块 ${module.name} (tags: [${sourceTags.join(", ")}]) 禁止依赖带有标签 '${bannedTag}' 的模块 ${targetModule.name} (tags: [${targetTags.join(", ")}])`, module.file, module.line);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (rule.onlyDependOnLibsWithTags && rule.onlyDependOnLibsWithTags.length > 0) {
|
|
1140
|
+
const hasAllowed = targetTags.some((t) => rule.onlyDependOnLibsWithTags.includes(t));
|
|
1141
|
+
if (!hasAllowed && targetTags.length > 0) {
|
|
1142
|
+
error("module-boundary-violation", `模块 ${module.name} (tags: [${sourceTags.join(", ")}]) 仅允许依赖带有 [${rule.onlyDependOnLibsWithTags.join(", ")}] 标签的模块,但模块 ${targetModule.name} 的标签为 [${targetTags.join(", ")}]`, module.file, module.line);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1116
1149
|
diagnostics.push(...detectCycles(graph, resolveDep));
|
|
1117
1150
|
return diagnostics;
|
|
1118
1151
|
}
|
|
@@ -1169,7 +1202,10 @@ async function compileProject(options) {
|
|
|
1169
1202
|
const graph = await analyzeProject(options.rootDir, options.include);
|
|
1170
1203
|
const diagnostics = [
|
|
1171
1204
|
...graph.diagnostics ?? [],
|
|
1172
|
-
...validateGraph(graph,
|
|
1205
|
+
...validateGraph(graph, {
|
|
1206
|
+
strict: options.strict,
|
|
1207
|
+
moduleBoundaries: options.moduleBoundaries
|
|
1208
|
+
})
|
|
1173
1209
|
];
|
|
1174
1210
|
if (options.strict) {
|
|
1175
1211
|
for (const diagnostic of diagnostics) {
|
package/dist/types.d.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface ModuleNode {
|
|
|
68
68
|
/** @Module({ name }) 或 defineModule 的 name。 */
|
|
69
69
|
name: string;
|
|
70
70
|
className: string;
|
|
71
|
+
/** 模块标签(如 ['scope:case', 'type:feature']),用于架构边界治理。 */
|
|
72
|
+
tags?: string[];
|
|
71
73
|
file: string;
|
|
72
74
|
line: number;
|
|
73
75
|
/** 被 import 模块的 name。 */
|
|
@@ -104,6 +106,16 @@ export interface CompileOptions {
|
|
|
104
106
|
outDir: string;
|
|
105
107
|
/** warn 级诊断升级为 error。 */
|
|
106
108
|
strict?: boolean;
|
|
109
|
+
/** 模块边界与架构治理规则(受 Nx enforce-module-boundaries 启发)。 */
|
|
110
|
+
moduleBoundaries?: ModuleBoundaryRule[];
|
|
111
|
+
}
|
|
112
|
+
export interface ModuleBoundaryRule {
|
|
113
|
+
/** 源模块标签模式或标签(如 'type:ui', 'scope:case', '*')。 */
|
|
114
|
+
sourceTag: string;
|
|
115
|
+
/** 源模块仅允许依赖具有这些标签的模块。 */
|
|
116
|
+
onlyDependOnLibsWithTags?: string[];
|
|
117
|
+
/** 源模块禁止依赖具有这些标签的模块。 */
|
|
118
|
+
bannedDependenciesWithTags?: string[];
|
|
107
119
|
}
|
|
108
120
|
export interface CompileResult {
|
|
109
121
|
diagnostics: Diagnostic[];
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { ApplicationGraph, Diagnostic } from "./types";
|
|
1
|
+
import type { ApplicationGraph, Diagnostic, ModuleBoundaryRule } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* 校验 ApplicationGraph,产出诊断列表。
|
|
4
4
|
* strict 时 warn 级诊断升级为 error。
|
|
5
5
|
*/
|
|
6
|
-
export declare function validateGraph(graph: ApplicationGraph,
|
|
6
|
+
export declare function validateGraph(graph: ApplicationGraph, options?: boolean | {
|
|
7
|
+
strict?: boolean;
|
|
8
|
+
moduleBoundaries?: ModuleBoundaryRule[];
|
|
9
|
+
}): Diagnostic[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"directory": "packages/compiler"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"ts-morph": "^
|
|
44
|
+
"ts-morph": "^28.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/bun": "^1.4.0",
|