@ultrst-armada/krnl 1.0.0 → 1.0.2
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/README.md +35 -24
- package/package.json +12 -12
- package/packages/cli/dist/bin/krnl.cjs +1026 -9462
- package/packages/cli/dist/bin/krnl.js +14 -1859
- package/packages/cli/dist/chunk-ESTKOQ7T.js +1146 -0
- package/packages/cli/dist/chunk-FZ6X7PHR.js +1146 -0
- package/packages/cli/dist/index.cjs +735 -7348
- package/packages/cli/dist/index.js +1 -20
- package/packages/cli/package.json +2 -2
- package/packages/core/dist/index.cjs +140 -5
- package/packages/core/dist/index.d.cts +24 -2
- package/packages/core/dist/index.d.ts +24 -2
- package/packages/core/dist/index.js +137 -3
- package/packages/core/package.json +2 -2
- package/packages/create-krnl/dist/index.cjs +277 -308
- package/packages/create-krnl/dist/index.js +277 -308
- package/packages/create-krnl/package.json +2 -2
- package/packages/eslint-plugin/package.json +2 -2
- package/packages/validate/dist/index.cjs +407 -369
- package/packages/validate/dist/index.d.cts +1 -13
- package/packages/validate/dist/index.d.ts +1 -13
- package/packages/validate/dist/index.js +408 -370
- package/packages/validate/package.json +2 -2
|
@@ -35,455 +35,493 @@ __export(index_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
37
37
|
// src/KrnlValidatorEngine.ts
|
|
38
|
-
var
|
|
38
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
39
|
+
var path4 = __toESM(require("path"), 1);
|
|
40
|
+
var import_ts_morph4 = require("ts-morph");
|
|
41
|
+
|
|
42
|
+
// src/rules/fileLimits.ts
|
|
43
|
+
function checkFileLimits(sf, options) {
|
|
44
|
+
const errors = [];
|
|
45
|
+
const warnings = [];
|
|
46
|
+
const filePath = sf.getFilePath();
|
|
47
|
+
const sourceText = sf.getText();
|
|
48
|
+
const lines = sourceText.split("\n");
|
|
49
|
+
if (lines.length > options.maxFileLineLimit) {
|
|
50
|
+
errors.push({
|
|
51
|
+
ruleId: "krnl/file-line-limit",
|
|
52
|
+
filePath,
|
|
53
|
+
message: `File exceeds maximum length of ${options.maxFileLineLimit} lines (currently ${lines.length} lines). Decompose into sub-components, custom hooks, quad-units, or concept libraries.`,
|
|
54
|
+
severity: "error"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
58
|
+
const moduleSpec = decl.getModuleSpecifierValue();
|
|
59
|
+
if (moduleSpec.startsWith(".") && !moduleSpec.endsWith(".js") && !moduleSpec.endsWith(".json") && !moduleSpec.endsWith(".css") && !moduleSpec.endsWith(".svg") && !moduleSpec.endsWith(".png")) {
|
|
60
|
+
errors.push({
|
|
61
|
+
ruleId: "krnl/import-suffix",
|
|
62
|
+
filePath,
|
|
63
|
+
line: decl.getStartLineNumber(),
|
|
64
|
+
message: `Relative import '${moduleSpec}' must end with '.js' for NodeNext module resolution.`,
|
|
65
|
+
severity: "error"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const isHelper = filePath.includes("helper_functions") || filePath.includes("/helpers/") || filePath.includes("\\helpers\\");
|
|
70
|
+
if (isHelper) {
|
|
71
|
+
for (const fn of sf.getFunctions()) {
|
|
72
|
+
const fnLines = fn.getText().split("\n").length;
|
|
73
|
+
if (fnLines > options.maxHelperLineLimit && !sourceText.includes("Concepts.")) {
|
|
74
|
+
errors.push({
|
|
75
|
+
ruleId: "krnl/helper-line-limit",
|
|
76
|
+
filePath,
|
|
77
|
+
line: fn.getStartLineNumber(),
|
|
78
|
+
message: `Helper function '${fn.getName() || "anonymous"}' exceeds ${options.maxHelperLineLimit} lines (${fnLines} lines). Must delegate to a root libraries/{Domain}Concepts.ts library.`,
|
|
79
|
+
severity: "error"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const isMainLogic = filePath.endsWith("logic.ts") || filePath.endsWith("logic.js");
|
|
85
|
+
if (isMainLogic) {
|
|
86
|
+
const substantiveLines = lines.filter((l) => {
|
|
87
|
+
const trimmed = l.trim();
|
|
88
|
+
return trimmed.length > 0 && !trimmed.startsWith("//") && !trimmed.startsWith("/*") && !trimmed.startsWith("*");
|
|
89
|
+
});
|
|
90
|
+
if (substantiveLines.length > options.maxMainLogicLimit && !sourceText.includes("helpers") && !sourceText.includes("Concepts") && !sourceText.includes("state")) {
|
|
91
|
+
errors.push({
|
|
92
|
+
ruleId: "krnl/main-delegation",
|
|
93
|
+
filePath,
|
|
94
|
+
message: `Main logic function has ${substantiveLines.length} substantive lines without delegating to Layer 1 helper functions or Layer 0 libraries.`,
|
|
95
|
+
severity: "error"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { errors, warnings };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/rules/componentRules.ts
|
|
39
103
|
var path = __toESM(require("path"), 1);
|
|
40
104
|
var import_ts_morph = require("ts-morph");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (fs.existsSync(tsConfigPath)) {
|
|
55
|
-
this.project = new import_ts_morph.Project({ tsConfigFilePath: tsConfigPath, skipAddingFilesFromTsConfig: true });
|
|
56
|
-
} else {
|
|
57
|
-
this.project = new import_ts_morph.Project({ compilerOptions: {
|
|
58
|
-
target: 99
|
|
59
|
-
/* ESNext */
|
|
60
|
-
} });
|
|
105
|
+
function checkComponentRules(sf) {
|
|
106
|
+
const errors = [];
|
|
107
|
+
const warnings = [];
|
|
108
|
+
const filePath = sf.getFilePath();
|
|
109
|
+
const baseName = path.basename(filePath);
|
|
110
|
+
if (filePath.endsWith(".tsx") || filePath.endsWith(".jsx")) {
|
|
111
|
+
const componentNames = [];
|
|
112
|
+
for (const fn of sf.getFunctions()) {
|
|
113
|
+
const name = fn.getName();
|
|
114
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
115
|
+
const hasJsx = fn.getDescendantsOfKind(import_ts_morph.SyntaxKind.JsxElement).length > 0 || fn.getDescendantsOfKind(import_ts_morph.SyntaxKind.JsxSelfClosingElement).length > 0 || fn.getDescendantsOfKind(import_ts_morph.SyntaxKind.JsxFragment).length > 0;
|
|
116
|
+
if (hasJsx) componentNames.push(name);
|
|
117
|
+
}
|
|
61
118
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
119
|
+
for (const stmt of sf.getVariableStatements()) {
|
|
120
|
+
for (const decl of stmt.getDeclarations()) {
|
|
121
|
+
const name = decl.getName();
|
|
122
|
+
if (/^[A-Z]/.test(name)) {
|
|
123
|
+
const typeText = decl.getTypeNode()?.getText() || "";
|
|
124
|
+
const isFcType = typeText.includes("FC") || typeText.includes("FunctionComponent") || typeText.includes("ComponentType");
|
|
125
|
+
const init = decl.getInitializer();
|
|
126
|
+
if (init) {
|
|
127
|
+
const hasJsx = init.getDescendantsOfKind(import_ts_morph.SyntaxKind.JsxElement).length > 0 || init.getDescendantsOfKind(import_ts_morph.SyntaxKind.JsxSelfClosingElement).length > 0 || init.getDescendantsOfKind(import_ts_morph.SyntaxKind.JsxFragment).length > 0;
|
|
128
|
+
if (isFcType || hasJsx) {
|
|
129
|
+
if (!componentNames.includes(name)) componentNames.push(name);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const cls of sf.getClasses()) {
|
|
136
|
+
const name = cls.getName();
|
|
137
|
+
const ext = cls.getExtends()?.getText() || "";
|
|
138
|
+
if (name && (ext.includes("Component") || ext.includes("PureComponent"))) {
|
|
139
|
+
if (!componentNames.includes(name)) componentNames.push(name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (componentNames.length > 1) {
|
|
143
|
+
errors.push({
|
|
144
|
+
ruleId: "krnl/one-component-per-file",
|
|
145
|
+
filePath,
|
|
146
|
+
message: `File '${baseName}' defines multiple React components (${componentNames.join(
|
|
147
|
+
", "
|
|
148
|
+
)}). Krnl strictly enforces ONE component per file. Decompose into sub-component files.`,
|
|
149
|
+
severity: "error"
|
|
150
|
+
});
|
|
76
151
|
}
|
|
77
|
-
return {
|
|
78
|
-
valid: errors.length === 0,
|
|
79
|
-
totalFiles: files.length,
|
|
80
|
-
errors,
|
|
81
|
-
warnings
|
|
82
|
-
};
|
|
83
152
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
153
|
+
const isHelper = filePath.includes("helper_functions") || filePath.includes("/helpers/") || filePath.includes("\\helpers\\");
|
|
154
|
+
if (isHelper) {
|
|
155
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
156
|
+
const spec = decl.getModuleSpecifierValue();
|
|
157
|
+
if (["fs", "node:fs", "http", "node:http", "ws"].includes(spec)) {
|
|
158
|
+
errors.push({
|
|
159
|
+
ruleId: "krnl/helper-purity",
|
|
160
|
+
filePath,
|
|
161
|
+
line: decl.getStartLineNumber(),
|
|
162
|
+
message: `Helper functions (Layer 1) cannot directly import I/O modules ('${spec}'). Delegate to Layer 0 libraries or Entity context.`,
|
|
163
|
+
severity: "error"
|
|
164
|
+
});
|
|
165
|
+
}
|
|
95
166
|
}
|
|
96
|
-
|
|
97
|
-
|
|
167
|
+
}
|
|
168
|
+
return { errors, warnings };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/rules/entityRules.ts
|
|
172
|
+
var path2 = __toESM(require("path"), 1);
|
|
173
|
+
var import_ts_morph2 = require("ts-morph");
|
|
174
|
+
function checkEntityRules(sf) {
|
|
175
|
+
const errors = [];
|
|
176
|
+
const warnings = [];
|
|
177
|
+
const filePath = sf.getFilePath();
|
|
178
|
+
const sourceText = sf.getText();
|
|
179
|
+
const baseName = path2.basename(filePath);
|
|
180
|
+
const calls = sf.getDescendantsOfKind(import_ts_morph2.SyntaxKind.CallExpression);
|
|
181
|
+
for (const call of calls) {
|
|
182
|
+
const exprText = call.getExpression().getText();
|
|
183
|
+
if (exprText === "setInterval" || exprText === "window.setInterval") {
|
|
98
184
|
errors.push({
|
|
99
|
-
ruleId: "krnl/
|
|
185
|
+
ruleId: "krnl/no-polling",
|
|
100
186
|
filePath,
|
|
101
|
-
|
|
187
|
+
line: call.getStartLineNumber(),
|
|
188
|
+
message: `Prohibited use of 'setInterval'. Krnl applications must use reactive event streams (entity.emitEvent / entity.on).`,
|
|
102
189
|
severity: "error"
|
|
103
190
|
});
|
|
104
191
|
}
|
|
192
|
+
}
|
|
193
|
+
if (sourceText.includes("main = async") || sourceText.includes("export const main = async")) {
|
|
194
|
+
const match = sourceText.match(/main\s*=\s*async\s*\([^,]+,\s*\{([^}]*)\}\s*\)/);
|
|
195
|
+
if (match) {
|
|
196
|
+
const destructured = match[1];
|
|
197
|
+
if (/\bsystem\b/.test(destructured) && !/\bentity\b/.test(destructured)) {
|
|
198
|
+
errors.push({
|
|
199
|
+
ruleId: "krnl/context-destructuring",
|
|
200
|
+
filePath,
|
|
201
|
+
message: `Functionality context parameter destructures 'system'. Functionality passes { state, entity }. Always destructure 'entity'.`,
|
|
202
|
+
severity: "error"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const isEntityClass = sf.getClasses().some((c) => {
|
|
208
|
+
const ext = c.getExtends();
|
|
209
|
+
return ext && (ext.getText().includes("Entity") || ext.getText().includes("krnlEntityBase"));
|
|
210
|
+
});
|
|
211
|
+
if (isEntityClass) {
|
|
105
212
|
for (const decl of sf.getImportDeclarations()) {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
const line = decl.getStartLineNumber();
|
|
213
|
+
const spec = decl.getModuleSpecifierValue();
|
|
214
|
+
if (spec.includes("libraries/") || spec.includes("Concepts")) {
|
|
109
215
|
errors.push({
|
|
110
|
-
ruleId: "krnl/
|
|
216
|
+
ruleId: "krnl/entity-no-libraries",
|
|
111
217
|
filePath,
|
|
112
|
-
line,
|
|
113
|
-
message: `
|
|
218
|
+
line: decl.getStartLineNumber(),
|
|
219
|
+
message: `Entity class violates Library Calling Invariant. Entities must not call or import libraries directly ('${spec}'). Only functionalities in 'src/functionalities/' are permitted to call shared libraries.`,
|
|
114
220
|
severity: "error"
|
|
115
221
|
});
|
|
116
222
|
}
|
|
117
223
|
}
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
severity: "error"
|
|
133
|
-
});
|
|
134
|
-
}
|
|
224
|
+
let loadAllLine = -1;
|
|
225
|
+
const addLocalLines = [];
|
|
226
|
+
for (const call of calls) {
|
|
227
|
+
const txt = call.getText();
|
|
228
|
+
if (txt.includes("MagneticLoader.loadAll") || txt.includes("KrnlMagneticLoader.loadAll")) {
|
|
229
|
+
loadAllLine = call.getStartLineNumber();
|
|
230
|
+
} else if (txt.startsWith("this.addLocal(")) {
|
|
231
|
+
const hasUiSpec = txt.includes("uiSpec");
|
|
232
|
+
const idMatch = txt.match(/id\s*:\s*["']([^"']+)["']/);
|
|
233
|
+
addLocalLines.push({
|
|
234
|
+
line: call.getStartLineNumber(),
|
|
235
|
+
hasUiSpec,
|
|
236
|
+
id: idMatch ? idMatch[1] : "unknown"
|
|
237
|
+
});
|
|
135
238
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (
|
|
239
|
+
}
|
|
240
|
+
if (loadAllLine !== -1) {
|
|
241
|
+
for (const al of addLocalLines) {
|
|
242
|
+
if (al.hasUiSpec && al.line > loadAllLine) {
|
|
140
243
|
errors.push({
|
|
141
|
-
ruleId: "krnl/
|
|
244
|
+
ruleId: "krnl/boot-order",
|
|
142
245
|
filePath,
|
|
143
|
-
line:
|
|
144
|
-
message: `
|
|
246
|
+
line: al.line,
|
|
247
|
+
message: `addLocal('${al.id}') with uiSpec at line ${al.line} appears AFTER loadAll() at line ${loadAllLine}. All addLocal() with UI must precede loadAll().`,
|
|
145
248
|
severity: "error"
|
|
146
249
|
});
|
|
147
250
|
}
|
|
148
251
|
}
|
|
149
252
|
}
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
253
|
+
for (const cls of sf.getClasses()) {
|
|
254
|
+
const ext = cls.getExtends();
|
|
255
|
+
if (ext && (ext.getText().includes("Entity") || ext.getText().includes("krnlEntityBase"))) {
|
|
256
|
+
const loops = [
|
|
257
|
+
...cls.getDescendantsOfKind(import_ts_morph2.SyntaxKind.ForStatement),
|
|
258
|
+
...cls.getDescendantsOfKind(import_ts_morph2.SyntaxKind.ForOfStatement),
|
|
259
|
+
...cls.getDescendantsOfKind(import_ts_morph2.SyntaxKind.ForInStatement),
|
|
260
|
+
...cls.getDescendantsOfKind(import_ts_morph2.SyntaxKind.WhileStatement),
|
|
261
|
+
...cls.getDescendantsOfKind(import_ts_morph2.SyntaxKind.DoStatement)
|
|
262
|
+
];
|
|
263
|
+
for (const loop of loops) {
|
|
155
264
|
errors.push({
|
|
156
|
-
ruleId: "krnl/
|
|
265
|
+
ruleId: "krnl/entity-purity",
|
|
157
266
|
filePath,
|
|
158
|
-
line:
|
|
159
|
-
message: `
|
|
267
|
+
line: loop.getStartLineNumber(),
|
|
268
|
+
message: `Entity '${cls.getName() || "Anonymous"}' violates Entity Purity Law. Loops and procedural algorithms are prohibited inside entities. Move logic to a functionality or a global library function.`,
|
|
160
269
|
severity: "error"
|
|
161
270
|
});
|
|
162
271
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
272
|
+
for (const method of cls.getMethods()) {
|
|
273
|
+
const methodName = method.getName();
|
|
274
|
+
if (!["boot", "init", "dispose"].includes(methodName)) {
|
|
275
|
+
errors.push({
|
|
276
|
+
ruleId: "krnl/entity-purity",
|
|
277
|
+
filePath,
|
|
278
|
+
line: method.getStartLineNumber(),
|
|
279
|
+
message: `Entity '${cls.getName() || "Anonymous"}' defines custom method '${methodName}'. Entities are purely declarative compositors/orchestrators. Define capabilities as functionalities or global library functions.`,
|
|
280
|
+
severity: "error"
|
|
281
|
+
});
|
|
173
282
|
}
|
|
174
283
|
}
|
|
175
284
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
285
|
+
}
|
|
286
|
+
for (const call of calls) {
|
|
287
|
+
const callText = call.getText();
|
|
288
|
+
if (callText.startsWith("this.orchestration(") || callText.startsWith("this.flow(")) {
|
|
289
|
+
let currentExpr = call;
|
|
290
|
+
const chainCalls = [];
|
|
291
|
+
while (currentExpr && import_ts_morph2.Node.isCallExpression(currentExpr)) {
|
|
292
|
+
const propAccess = currentExpr.getExpression();
|
|
293
|
+
if (import_ts_morph2.Node.isPropertyAccessExpression(propAccess)) {
|
|
294
|
+
const methodName = propAccess.getName();
|
|
295
|
+
const args = currentExpr.getArguments();
|
|
296
|
+
const argText = args.length > 0 ? args[0].getText() : void 0;
|
|
297
|
+
chainCalls.unshift({ name: methodName, node: currentExpr, argText });
|
|
298
|
+
currentExpr = propAccess.getExpression();
|
|
299
|
+
} else {
|
|
300
|
+
break;
|
|
191
301
|
}
|
|
192
302
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
303
|
+
let chainId = "orchestration";
|
|
304
|
+
for (const step of chainCalls) {
|
|
305
|
+
if (step.name === "orchestration" || step.name === "flow") {
|
|
306
|
+
chainId = step.argText ? step.argText.replace(/['"]/g, "") : "orchestration";
|
|
307
|
+
} else if (step.name === "thenFunction") {
|
|
308
|
+
errors.push({
|
|
309
|
+
ruleId: "krnl/entity-no-direct-library-calls",
|
|
310
|
+
filePath,
|
|
311
|
+
line: step.node.getStartLineNumber(),
|
|
312
|
+
message: `Orchestration chain '${chainId}' violates Entity Purity Law. Entities cannot chain direct library functions via thenFunction(). Entities must only orchestrate functionalities (thenFunctionality) and orchestrations from other entities (thenEntityOrchestration).`,
|
|
313
|
+
severity: "error"
|
|
314
|
+
});
|
|
200
315
|
}
|
|
201
316
|
}
|
|
202
317
|
}
|
|
203
|
-
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return { errors, warnings };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/rules/workspaceRules.ts
|
|
324
|
+
var fs = __toESM(require("fs"), 1);
|
|
325
|
+
var path3 = __toESM(require("path"), 1);
|
|
326
|
+
var import_ts_morph3 = require("ts-morph");
|
|
327
|
+
function checkTopLevelPurity(sf) {
|
|
328
|
+
const errors = [];
|
|
329
|
+
const warnings = [];
|
|
330
|
+
const filePath = sf.getFilePath();
|
|
331
|
+
const baseName = path3.basename(filePath);
|
|
332
|
+
const isUiTopLevel = /^(App|main|index|_app|root)\.(tsx|jsx)$/i.test(baseName);
|
|
333
|
+
const isBackendTopLevel = /^(server|app)\.(ts|js)$/i.test(baseName) && !filePath.includes("/components/") && !filePath.includes("/entities/") && !filePath.includes("/libraries/");
|
|
334
|
+
if (isUiTopLevel) {
|
|
335
|
+
const jsxElements = sf.getDescendantsOfKind(import_ts_morph3.SyntaxKind.JsxOpeningElement);
|
|
336
|
+
const jsxSelfClosing = sf.getDescendantsOfKind(import_ts_morph3.SyntaxKind.JsxSelfClosingElement);
|
|
337
|
+
const allJsxTags = [...jsxElements, ...jsxSelfClosing];
|
|
338
|
+
for (const jsx of allJsxTags) {
|
|
339
|
+
const tagName = jsx.getTagNameNode().getText();
|
|
340
|
+
if (/^[a-z]/.test(tagName)) {
|
|
204
341
|
errors.push({
|
|
205
|
-
ruleId: "krnl/
|
|
342
|
+
ruleId: "krnl/top-level-purity",
|
|
206
343
|
filePath,
|
|
207
|
-
|
|
344
|
+
line: jsx.getStartLineNumber(),
|
|
345
|
+
message: `Top-level file '${baseName}' violates Top-Level Purity Law. Raw JSX/HTML element '<${tagName}>' is strictly forbidden. Top-level files must ONLY import and compose React components.`,
|
|
208
346
|
severity: "error"
|
|
209
347
|
});
|
|
210
348
|
}
|
|
211
349
|
}
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
for (const fn of sf.getFunctions()) {
|
|
229
|
-
const fnLines = fn.getText().split("\n").length;
|
|
230
|
-
if (fnLines > this.maxHelperLineLimit && !sourceText.includes("Concepts.")) {
|
|
231
|
-
errors.push({
|
|
232
|
-
ruleId: "krnl/helper-line-limit",
|
|
233
|
-
filePath,
|
|
234
|
-
line: fn.getStartLineNumber(),
|
|
235
|
-
message: `Helper function '${fn.getName() || "anonymous"}' exceeds ${this.maxHelperLineLimit} lines (${fnLines} lines). Must delegate to a root libraries/{Domain}Concepts.ts library.`,
|
|
236
|
-
severity: "error"
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (isMainLogic) {
|
|
242
|
-
const substantiveLines = lines.filter((l) => {
|
|
243
|
-
const trimmed = l.trim();
|
|
244
|
-
return trimmed.length > 0 && !trimmed.startsWith("//") && !trimmed.startsWith("/*") && !trimmed.startsWith("*");
|
|
245
|
-
});
|
|
246
|
-
if (substantiveLines.length > this.maxMainLogicLimit && !sourceText.includes("helpers") && !sourceText.includes("Concepts") && !sourceText.includes("state")) {
|
|
350
|
+
const calls = sf.getDescendantsOfKind(import_ts_morph3.SyntaxKind.CallExpression);
|
|
351
|
+
for (const call of calls) {
|
|
352
|
+
const exprText = call.getExpression().getText();
|
|
353
|
+
if ([
|
|
354
|
+
"useState",
|
|
355
|
+
"useEffect",
|
|
356
|
+
"useRef",
|
|
357
|
+
"useMemo",
|
|
358
|
+
"useCallback",
|
|
359
|
+
"useReducer",
|
|
360
|
+
"useContext",
|
|
361
|
+
"useLayoutEffect"
|
|
362
|
+
].includes(exprText) || /^use[A-Z]/.test(exprText) && exprText !== "use") {
|
|
247
363
|
errors.push({
|
|
248
|
-
ruleId: "krnl/
|
|
364
|
+
ruleId: "krnl/top-level-purity",
|
|
249
365
|
filePath,
|
|
250
|
-
|
|
366
|
+
line: call.getStartLineNumber(),
|
|
367
|
+
message: `Top-level file '${baseName}' violates Top-Level Purity Law. React hook '${exprText}' is forbidden in top-level files. Encapsulate state and effects inside dedicated component files.`,
|
|
251
368
|
severity: "error"
|
|
252
369
|
});
|
|
253
370
|
}
|
|
254
371
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const destructured = match[1];
|
|
259
|
-
if (/\bsystem\b/.test(destructured) && !/\bentity\b/.test(destructured)) {
|
|
260
|
-
errors.push({
|
|
261
|
-
ruleId: "krnl/context-destructuring",
|
|
262
|
-
filePath,
|
|
263
|
-
message: `Functionality context parameter destructures 'system'. Functionality passes { state, entity }. Always destructure 'entity'.`,
|
|
264
|
-
severity: "error"
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
const calls = sf.getDescendantsOfKind(import_ts_morph.SyntaxKind.CallExpression);
|
|
372
|
+
}
|
|
373
|
+
if (isBackendTopLevel) {
|
|
374
|
+
const calls = sf.getDescendantsOfKind(import_ts_morph3.SyntaxKind.CallExpression);
|
|
270
375
|
for (const call of calls) {
|
|
271
376
|
const exprText = call.getExpression().getText();
|
|
272
|
-
if (exprText
|
|
377
|
+
if (/^(app|router|server)\.(get|post|put|delete|patch|options|head)$/.test(exprText)) {
|
|
273
378
|
errors.push({
|
|
274
|
-
ruleId: "krnl/
|
|
379
|
+
ruleId: "krnl/top-level-purity",
|
|
275
380
|
filePath,
|
|
276
381
|
line: call.getStartLineNumber(),
|
|
277
|
-
message: `
|
|
382
|
+
message: `Backend entry file '${baseName}' violates Entry Purity Law. Inline route definition '${exprText}' is forbidden. Delegate to dedicated route/controller modules.`,
|
|
278
383
|
severity: "error"
|
|
279
384
|
});
|
|
280
385
|
}
|
|
281
386
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
387
|
+
}
|
|
388
|
+
return { errors, warnings };
|
|
389
|
+
}
|
|
390
|
+
function checkWorkspaceLayout(projectRoot, errors) {
|
|
391
|
+
const krnlDir = path3.join(projectRoot, "krnl");
|
|
392
|
+
const srcDir = path3.join(projectRoot, "src");
|
|
393
|
+
const targetDir = fs.existsSync(krnlDir) ? krnlDir : fs.existsSync(srcDir) ? srcDir : null;
|
|
394
|
+
if (!targetDir) return;
|
|
395
|
+
const backendDir = path3.join(targetDir, "backend");
|
|
396
|
+
const frontendDir = path3.join(targetDir, "frontend");
|
|
397
|
+
if (fs.existsSync(backendDir)) {
|
|
398
|
+
const bLibs = path3.join(backendDir, "libraries");
|
|
399
|
+
if (fs.existsSync(bLibs)) {
|
|
400
|
+
for (const entry of fs.readdirSync(bLibs, { withFileTypes: true })) {
|
|
401
|
+
if (!entry.isDirectory()) continue;
|
|
402
|
+
const libPath = path3.join(bLibs, entry.name);
|
|
403
|
+
if (!fs.existsSync(path3.join(libPath, "manifest.json"))) {
|
|
290
404
|
errors.push({
|
|
291
|
-
ruleId: "krnl/
|
|
292
|
-
filePath,
|
|
293
|
-
|
|
294
|
-
message: `Entity class violates Library Calling Invariant. Entities must not call or import libraries directly ('${spec}'). Only functionalities in 'src/functionalities/' are permitted to call shared libraries.`,
|
|
405
|
+
ruleId: "krnl/concept-manifest-required",
|
|
406
|
+
filePath: libPath,
|
|
407
|
+
message: `Backend concept library "${entry.name}" is missing a manifest.json file.`,
|
|
295
408
|
severity: "error"
|
|
296
409
|
});
|
|
297
410
|
}
|
|
298
411
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
id: idMatch ? idMatch[1] : "unknown"
|
|
412
|
+
}
|
|
413
|
+
const bFuncs = path3.join(backendDir, "functionalities");
|
|
414
|
+
if (fs.existsSync(bFuncs)) {
|
|
415
|
+
for (const entry of fs.readdirSync(bFuncs, { withFileTypes: true })) {
|
|
416
|
+
if (!entry.isDirectory()) continue;
|
|
417
|
+
const funcPath = path3.join(bFuncs, entry.name);
|
|
418
|
+
if (!fs.existsSync(path3.join(funcPath, "manifest.json"))) {
|
|
419
|
+
errors.push({
|
|
420
|
+
ruleId: "krnl/functionality-manifest-required",
|
|
421
|
+
filePath: funcPath,
|
|
422
|
+
message: `Backend functionality "${entry.name}" is missing a manifest.json file.`,
|
|
423
|
+
severity: "error"
|
|
312
424
|
});
|
|
313
425
|
}
|
|
314
426
|
}
|
|
315
|
-
if (loadAllLine !== -1) {
|
|
316
|
-
for (const al of addLocalLines) {
|
|
317
|
-
if (al.hasUiSpec && al.line > loadAllLine) {
|
|
318
|
-
errors.push({
|
|
319
|
-
ruleId: "krnl/boot-order",
|
|
320
|
-
filePath,
|
|
321
|
-
line: al.line,
|
|
322
|
-
message: `addLocal('${al.id}') with uiSpec at line ${al.line} appears AFTER loadAll() at line ${loadAllLine}. All addLocal() with UI must precede loadAll().`,
|
|
323
|
-
severity: "error"
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
for (const cls of sf.getClasses()) {
|
|
329
|
-
const ext = cls.getExtends();
|
|
330
|
-
if (ext && (ext.getText().includes("Entity") || ext.getText().includes("krnlEntityBase"))) {
|
|
331
|
-
const loops = [
|
|
332
|
-
...cls.getDescendantsOfKind(import_ts_morph.SyntaxKind.ForStatement),
|
|
333
|
-
...cls.getDescendantsOfKind(import_ts_morph.SyntaxKind.ForOfStatement),
|
|
334
|
-
...cls.getDescendantsOfKind(import_ts_morph.SyntaxKind.ForInStatement),
|
|
335
|
-
...cls.getDescendantsOfKind(import_ts_morph.SyntaxKind.WhileStatement),
|
|
336
|
-
...cls.getDescendantsOfKind(import_ts_morph.SyntaxKind.DoStatement)
|
|
337
|
-
];
|
|
338
|
-
for (const loop of loops) {
|
|
339
|
-
errors.push({
|
|
340
|
-
ruleId: "krnl/entity-purity",
|
|
341
|
-
filePath,
|
|
342
|
-
line: loop.getStartLineNumber(),
|
|
343
|
-
message: `Entity '${cls.getName() || "Anonymous"}' violates Entity Purity Law. Loops and procedural algorithms are prohibited inside entities. Move logic to a functionality or a global library function.`,
|
|
344
|
-
severity: "error"
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
for (const method of cls.getMethods()) {
|
|
348
|
-
const methodName = method.getName();
|
|
349
|
-
if (!["boot", "init", "dispose"].includes(methodName)) {
|
|
350
|
-
errors.push({
|
|
351
|
-
ruleId: "krnl/entity-purity",
|
|
352
|
-
filePath,
|
|
353
|
-
line: method.getStartLineNumber(),
|
|
354
|
-
message: `Entity '${cls.getName() || "Anonymous"}' defines custom method '${methodName}'. Entities are purely declarative compositors/orchestrators. Define capabilities as functionalities or global library functions.`,
|
|
355
|
-
severity: "error"
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
for (const call of calls) {
|
|
362
|
-
const callText = call.getText();
|
|
363
|
-
if (callText.startsWith("this.orchestration(") || callText.startsWith("this.flow(")) {
|
|
364
|
-
let currentExpr = call;
|
|
365
|
-
const chainCalls = [];
|
|
366
|
-
while (currentExpr && import_ts_morph.Node.isCallExpression(currentExpr)) {
|
|
367
|
-
const propAccess = currentExpr.getExpression();
|
|
368
|
-
if (import_ts_morph.Node.isPropertyAccessExpression(propAccess)) {
|
|
369
|
-
const methodName = propAccess.getName();
|
|
370
|
-
const args = currentExpr.getArguments();
|
|
371
|
-
const argText = args.length > 0 ? args[0].getText() : void 0;
|
|
372
|
-
chainCalls.unshift({ name: methodName, node: currentExpr, argText });
|
|
373
|
-
currentExpr = propAccess.getExpression();
|
|
374
|
-
} else {
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
let consecutiveFunctions = 0;
|
|
379
|
-
let chainId = "orchestration";
|
|
380
|
-
for (const step of chainCalls) {
|
|
381
|
-
if (step.name === "orchestration" || step.name === "flow") {
|
|
382
|
-
chainId = step.argText ? step.argText.replace(/['"]/g, "") : "orchestration";
|
|
383
|
-
} else if (step.name === "thenFunctionality" || step.name === "then") {
|
|
384
|
-
consecutiveFunctions = 0;
|
|
385
|
-
} else if (step.name === "thenEntityOrchestration" || step.name === "thenEntity") {
|
|
386
|
-
consecutiveFunctions = 0;
|
|
387
|
-
} else if (step.name === "thenFunction") {
|
|
388
|
-
errors.push({
|
|
389
|
-
ruleId: "krnl/entity-no-direct-library-calls",
|
|
390
|
-
filePath,
|
|
391
|
-
line: step.node.getStartLineNumber(),
|
|
392
|
-
message: `Orchestration chain '${chainId}' violates Entity Purity Law. Entities cannot chain direct library functions via thenFunction(). Entities must only orchestrate functionalities (thenFunctionality) and orchestrations from other entities (thenEntityOrchestration).`,
|
|
393
|
-
severity: "error"
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
427
|
}
|
|
400
|
-
return { errors, warnings };
|
|
401
428
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
for (const entry of fs.readdirSync(bLibs, { withFileTypes: true })) {
|
|
416
|
-
if (!entry.isDirectory()) continue;
|
|
417
|
-
const libPath = path.join(bLibs, entry.name);
|
|
418
|
-
const manifestPath = path.join(libPath, "manifest.json");
|
|
419
|
-
if (!fs.existsSync(manifestPath)) {
|
|
420
|
-
errors.push({
|
|
421
|
-
ruleId: "krnl/concept-manifest-required",
|
|
422
|
-
filePath: libPath,
|
|
423
|
-
message: `Backend concept library "${entry.name}" is missing a manifest.json file.`,
|
|
424
|
-
severity: "error"
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
const bFuncs = path.join(backendDir, "functionalities");
|
|
430
|
-
if (fs.existsSync(bFuncs)) {
|
|
431
|
-
for (const entry of fs.readdirSync(bFuncs, { withFileTypes: true })) {
|
|
432
|
-
if (!entry.isDirectory()) continue;
|
|
433
|
-
const funcPath = path.join(bFuncs, entry.name);
|
|
434
|
-
const manifestPath = path.join(funcPath, "manifest.json");
|
|
435
|
-
if (!fs.existsSync(manifestPath)) {
|
|
436
|
-
errors.push({
|
|
437
|
-
ruleId: "krnl/functionality-manifest-required",
|
|
438
|
-
filePath: funcPath,
|
|
439
|
-
message: `Backend functionality "${entry.name}" is missing a manifest.json file.`,
|
|
440
|
-
severity: "error"
|
|
441
|
-
});
|
|
442
|
-
}
|
|
429
|
+
if (fs.existsSync(frontendDir)) {
|
|
430
|
+
const fLibs = path3.join(frontendDir, "libraries");
|
|
431
|
+
if (fs.existsSync(fLibs)) {
|
|
432
|
+
for (const entry of fs.readdirSync(fLibs, { withFileTypes: true })) {
|
|
433
|
+
if (!entry.isDirectory()) continue;
|
|
434
|
+
const libPath = path3.join(fLibs, entry.name);
|
|
435
|
+
if (!fs.existsSync(path3.join(libPath, "manifest.json"))) {
|
|
436
|
+
errors.push({
|
|
437
|
+
ruleId: "krnl/frontend-concept-manifest-required",
|
|
438
|
+
filePath: libPath,
|
|
439
|
+
message: `Frontend concept library "${entry.name}" is missing a manifest.json file.`,
|
|
440
|
+
severity: "error"
|
|
441
|
+
});
|
|
443
442
|
}
|
|
444
443
|
}
|
|
445
444
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
severity: "error"
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
const fFuncs = path.join(frontendDir, "functionalities");
|
|
464
|
-
if (fs.existsSync(fFuncs)) {
|
|
465
|
-
for (const entry of fs.readdirSync(fFuncs, { withFileTypes: true })) {
|
|
466
|
-
if (!entry.isDirectory()) continue;
|
|
467
|
-
const funcPath = path.join(fFuncs, entry.name);
|
|
468
|
-
const manifestPath = path.join(funcPath, "manifest.json");
|
|
469
|
-
if (!fs.existsSync(manifestPath)) {
|
|
470
|
-
errors.push({
|
|
471
|
-
ruleId: "krnl/frontend-functionality-manifest-required",
|
|
472
|
-
filePath: funcPath,
|
|
473
|
-
message: `Frontend functionality "${entry.name}" is missing a manifest.json file.`,
|
|
474
|
-
severity: "error"
|
|
475
|
-
});
|
|
476
|
-
}
|
|
445
|
+
const fFuncs = path3.join(frontendDir, "functionalities");
|
|
446
|
+
if (fs.existsSync(fFuncs)) {
|
|
447
|
+
for (const entry of fs.readdirSync(fFuncs, { withFileTypes: true })) {
|
|
448
|
+
if (!entry.isDirectory()) continue;
|
|
449
|
+
const funcPath = path3.join(fFuncs, entry.name);
|
|
450
|
+
if (!fs.existsSync(path3.join(funcPath, "manifest.json"))) {
|
|
451
|
+
errors.push({
|
|
452
|
+
ruleId: "krnl/frontend-functionality-manifest-required",
|
|
453
|
+
filePath: funcPath,
|
|
454
|
+
message: `Frontend functionality "${entry.name}" is missing a manifest.json file.`,
|
|
455
|
+
severity: "error"
|
|
456
|
+
});
|
|
477
457
|
}
|
|
478
458
|
}
|
|
479
459
|
}
|
|
480
460
|
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/KrnlValidatorEngine.ts
|
|
464
|
+
var KrnlValidatorEngine = class {
|
|
465
|
+
project;
|
|
466
|
+
projectRoot;
|
|
467
|
+
limitOptions;
|
|
468
|
+
constructor(optionsOrRoot) {
|
|
469
|
+
const options = typeof optionsOrRoot === "string" ? { projectRoot: optionsOrRoot } : optionsOrRoot;
|
|
470
|
+
this.projectRoot = path4.resolve(options.projectRoot || process.cwd());
|
|
471
|
+
this.limitOptions = {
|
|
472
|
+
maxFileLineLimit: options.maxFileLineLimit ?? 200,
|
|
473
|
+
maxMainLogicLimit: options.maxMainLogicLimit ?? 20,
|
|
474
|
+
maxHelperLineLimit: options.maxHelperLineLimit ?? 30
|
|
475
|
+
};
|
|
476
|
+
const tsConfigPath = path4.join(this.projectRoot, "tsconfig.json");
|
|
477
|
+
if (fs2.existsSync(tsConfigPath)) {
|
|
478
|
+
this.project = new import_ts_morph4.Project({ tsConfigFilePath: tsConfigPath, skipAddingFilesFromTsConfig: true });
|
|
479
|
+
} else {
|
|
480
|
+
this.project = new import_ts_morph4.Project({ compilerOptions: {
|
|
481
|
+
target: 99
|
|
482
|
+
/* ESNext */
|
|
483
|
+
} });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
validateAll() {
|
|
487
|
+
const files = this.collectSourceFiles(this.projectRoot);
|
|
488
|
+
const errors = [];
|
|
489
|
+
const warnings = [];
|
|
490
|
+
checkWorkspaceLayout(this.projectRoot, errors);
|
|
491
|
+
for (const f of files) {
|
|
492
|
+
const sf = this.project.addSourceFileAtPath(f);
|
|
493
|
+
const fileReport = this.validateSourceFile(sf);
|
|
494
|
+
errors.push(...fileReport.errors);
|
|
495
|
+
warnings.push(...fileReport.warnings);
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
valid: errors.length === 0,
|
|
499
|
+
totalFiles: files.length,
|
|
500
|
+
errors,
|
|
501
|
+
warnings
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
validateSourceFile(sf) {
|
|
505
|
+
const filePath = sf.getFilePath();
|
|
506
|
+
const baseName = path4.basename(filePath);
|
|
507
|
+
if (baseName.includes("_pb.") || baseName.endsWith(".d.ts")) {
|
|
508
|
+
return { errors: [], warnings: [] };
|
|
509
|
+
}
|
|
510
|
+
const limits = checkFileLimits(sf, this.limitOptions);
|
|
511
|
+
const purity = checkTopLevelPurity(sf);
|
|
512
|
+
const components = checkComponentRules(sf);
|
|
513
|
+
const entity = checkEntityRules(sf);
|
|
514
|
+
return {
|
|
515
|
+
errors: [...limits.errors, ...purity.errors, ...components.errors, ...entity.errors],
|
|
516
|
+
warnings: [...limits.warnings, ...purity.warnings, ...components.warnings, ...entity.warnings]
|
|
517
|
+
};
|
|
518
|
+
}
|
|
481
519
|
collectSourceFiles(dir, collected = []) {
|
|
482
|
-
if (!
|
|
483
|
-
const entries =
|
|
520
|
+
if (!fs2.existsSync(dir)) return collected;
|
|
521
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
484
522
|
for (const entry of entries) {
|
|
485
523
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
|
|
486
|
-
const full =
|
|
524
|
+
const full = path4.join(dir, entry.name);
|
|
487
525
|
if (entry.isDirectory()) {
|
|
488
526
|
this.collectSourceFiles(full, collected);
|
|
489
527
|
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js") || entry.name.endsWith(".tsx") || entry.name.endsWith(".jsx"))) {
|