@vlynk-studios/nodulus-core 1.2.0 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +25 -0
- package/dist/cli/index.js +534 -370
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +22 -31
- package/dist/index.js +6265 -159
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/cli/index.js
CHANGED
|
@@ -8,13 +8,26 @@ import { Command } from "commander";
|
|
|
8
8
|
import fs from "fs";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import pc from "picocolors";
|
|
11
|
+
|
|
12
|
+
// src/core/errors.ts
|
|
13
|
+
var NodulusError = class extends Error {
|
|
14
|
+
code;
|
|
15
|
+
details;
|
|
16
|
+
constructor(code, message, details) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "NodulusError";
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.details = details;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/cli/commands/create-module.ts
|
|
11
25
|
function createModuleCommand() {
|
|
12
26
|
return new Command("create-module").description("Scaffolds a new Nodulus module").argument("<name>", "Module name (lowercase, no spaces/special chars)").option("-p, --path <path>", "Destination folder path (default: src/modules/<name>)").option("--no-repository", "Skip generating a repository file").option("--no-schema", "Skip generating a schema file").option("--js", "Force generate JavaScript (.js) files").option("--ts", "Force generate TypeScript (.ts) files").action((name, options) => {
|
|
13
27
|
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
14
|
-
|
|
28
|
+
throw new NodulusError("CLI_ERROR", pc.red(`
|
|
15
29
|
Error: Invalid module name "${name}". Module names must be lowercase and contain only letters, numbers, or hyphens.
|
|
16
30
|
`));
|
|
17
|
-
process.exit(1);
|
|
18
31
|
}
|
|
19
32
|
let ext;
|
|
20
33
|
if (options.js) {
|
|
@@ -27,10 +40,9 @@ Error: Invalid module name "${name}". Module names must be lowercase and contain
|
|
|
27
40
|
}
|
|
28
41
|
const modulePath = options.path ? path.resolve(process.cwd(), options.path) : path.resolve(process.cwd(), `src/modules/${name}`);
|
|
29
42
|
if (fs.existsSync(modulePath)) {
|
|
30
|
-
|
|
43
|
+
throw new NodulusError("CLI_ERROR", pc.red(`
|
|
31
44
|
Error: The directory "${modulePath}" already exists. Cannot scaffold module here.
|
|
32
45
|
`));
|
|
33
|
-
process.exit(1);
|
|
34
46
|
}
|
|
35
47
|
fs.mkdirSync(modulePath, { recursive: true });
|
|
36
48
|
const files = {
|
|
@@ -122,10 +134,9 @@ Schema('${capName}Schema', { module: '${name}' })
|
|
|
122
134
|
|
|
123
135
|
// src/cli/commands/sync-tsconfig.ts
|
|
124
136
|
import { Command as Command2 } from "commander";
|
|
125
|
-
import
|
|
126
|
-
import
|
|
137
|
+
import fs4 from "fs";
|
|
138
|
+
import path4 from "path";
|
|
127
139
|
import pc3 from "picocolors";
|
|
128
|
-
import fg from "fast-glob";
|
|
129
140
|
import { parse, stringify } from "comment-json";
|
|
130
141
|
|
|
131
142
|
// src/core/config.ts
|
|
@@ -171,25 +182,27 @@ function resolveLogLevel(explicit) {
|
|
|
171
182
|
var defaultStrict = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
172
183
|
var DEFAULTS = {
|
|
173
184
|
modules: "src/modules/*",
|
|
185
|
+
domains: void 0,
|
|
186
|
+
shared: void 0,
|
|
174
187
|
prefix: "",
|
|
175
188
|
aliases: {},
|
|
176
189
|
strict: defaultStrict,
|
|
177
190
|
resolveAliases: true,
|
|
178
191
|
logger: defaultLogHandler,
|
|
179
|
-
logLevel: resolveLogLevel()
|
|
192
|
+
logLevel: resolveLogLevel(),
|
|
193
|
+
nits: {
|
|
194
|
+
enabled: true,
|
|
195
|
+
similarityThreshold: void 0,
|
|
196
|
+
// Use dynamic by default
|
|
197
|
+
registryPath: ".nodulus/registry.json"
|
|
198
|
+
}
|
|
180
199
|
};
|
|
181
200
|
var loadConfig = async (options = {}) => {
|
|
182
201
|
const cwd = process.cwd();
|
|
183
202
|
let fileConfig = {};
|
|
184
203
|
const tsPath = path2.join(cwd, "nodulus.config.ts");
|
|
185
204
|
const jsPath = path2.join(cwd, "nodulus.config.js");
|
|
186
|
-
const
|
|
187
|
-
const hasTsLoader = process.execArgv.some((arg) => arg.includes("ts-node") || arg.includes("tsx")) || process._preload_modules?.some((m) => m.includes("ts-node") || m.includes("tsx"));
|
|
188
|
-
const candidates = [];
|
|
189
|
-
if (!isProduction || hasTsLoader) {
|
|
190
|
-
candidates.push(tsPath);
|
|
191
|
-
}
|
|
192
|
-
candidates.push(jsPath);
|
|
205
|
+
const candidates = [tsPath, jsPath];
|
|
193
206
|
let configPathToLoad = null;
|
|
194
207
|
for (const candidate of candidates) {
|
|
195
208
|
if (fs2.existsSync(candidate)) {
|
|
@@ -216,6 +229,8 @@ var loadConfig = async (options = {}) => {
|
|
|
216
229
|
}
|
|
217
230
|
return {
|
|
218
231
|
modules: options.modules ?? fileConfig.modules ?? DEFAULTS.modules,
|
|
232
|
+
domains: options.domains ?? fileConfig.domains ?? DEFAULTS.domains,
|
|
233
|
+
shared: options.shared ?? fileConfig.shared ?? DEFAULTS.shared,
|
|
219
234
|
prefix: options.prefix ?? fileConfig.prefix ?? DEFAULTS.prefix,
|
|
220
235
|
aliases: {
|
|
221
236
|
...DEFAULTS.aliases,
|
|
@@ -226,95 +241,148 @@ var loadConfig = async (options = {}) => {
|
|
|
226
241
|
strict: options.strict ?? fileConfig.strict ?? DEFAULTS.strict,
|
|
227
242
|
resolveAliases: options.resolveAliases ?? fileConfig.resolveAliases ?? DEFAULTS.resolveAliases,
|
|
228
243
|
logger: options.logger ?? fileConfig.logger ?? DEFAULTS.logger,
|
|
229
|
-
logLevel: resolveLogLevel(options.logLevel ?? fileConfig.logLevel)
|
|
244
|
+
logLevel: resolveLogLevel(options.logLevel ?? fileConfig.logLevel),
|
|
245
|
+
nits: {
|
|
246
|
+
enabled: options.nits?.enabled ?? fileConfig.nits?.enabled ?? DEFAULTS.nits.enabled,
|
|
247
|
+
similarityThreshold: options.nits?.similarityThreshold ?? fileConfig.nits?.similarityThreshold ?? DEFAULTS.nits.similarityThreshold,
|
|
248
|
+
registryPath: options.nits?.registryPath ?? fileConfig.nits?.registryPath ?? DEFAULTS.nits.registryPath
|
|
249
|
+
}
|
|
230
250
|
};
|
|
231
251
|
};
|
|
232
252
|
|
|
253
|
+
// src/cli/lib/tsconfig-generator.ts
|
|
254
|
+
import fs3 from "fs";
|
|
255
|
+
import path3 from "path";
|
|
256
|
+
import fg from "fast-glob";
|
|
257
|
+
async function generatePathAliases(config, cwd) {
|
|
258
|
+
const pathsObj = {};
|
|
259
|
+
if (config.modules) {
|
|
260
|
+
const globPattern = config.modules.replace(/\\/g, "/");
|
|
261
|
+
const moduleDirs = await fg(globPattern, {
|
|
262
|
+
onlyDirectories: true,
|
|
263
|
+
absolute: true,
|
|
264
|
+
cwd
|
|
265
|
+
});
|
|
266
|
+
moduleDirs.sort();
|
|
267
|
+
for (const dirPath of moduleDirs) {
|
|
268
|
+
const modName = path3.basename(dirPath);
|
|
269
|
+
const aliasKey = `@modules/${modName}`;
|
|
270
|
+
let indexPath = path3.join(dirPath, "index.ts");
|
|
271
|
+
if (!fs3.existsSync(indexPath)) {
|
|
272
|
+
indexPath = path3.join(dirPath, "index.js");
|
|
273
|
+
}
|
|
274
|
+
let relativeIndexPath = path3.relative(cwd, indexPath).replace(/\\/g, "/");
|
|
275
|
+
if (!relativeIndexPath.startsWith("./") && !relativeIndexPath.startsWith("../")) {
|
|
276
|
+
relativeIndexPath = "./" + relativeIndexPath;
|
|
277
|
+
}
|
|
278
|
+
let relativeDirPath = path3.relative(cwd, dirPath).replace(/\\/g, "/");
|
|
279
|
+
if (!relativeDirPath.startsWith("./") && !relativeDirPath.startsWith("../")) {
|
|
280
|
+
relativeDirPath = "./" + relativeDirPath;
|
|
281
|
+
}
|
|
282
|
+
pathsObj[aliasKey] = [relativeIndexPath];
|
|
283
|
+
pathsObj[`${aliasKey}/*`] = [`${relativeDirPath}/*`];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (config.domains) {
|
|
287
|
+
const globPattern = config.domains.replace(/\\/g, "/");
|
|
288
|
+
const domainDirs = await fg(globPattern, {
|
|
289
|
+
onlyDirectories: true,
|
|
290
|
+
absolute: true,
|
|
291
|
+
cwd
|
|
292
|
+
});
|
|
293
|
+
domainDirs.sort();
|
|
294
|
+
for (const dirPath of domainDirs) {
|
|
295
|
+
const domainName = path3.basename(dirPath);
|
|
296
|
+
let indexPath = path3.join(dirPath, "index.ts");
|
|
297
|
+
if (!fs3.existsSync(indexPath)) {
|
|
298
|
+
indexPath = path3.join(dirPath, "index.js");
|
|
299
|
+
}
|
|
300
|
+
let relativeIndexPath = path3.relative(cwd, indexPath).replace(/\\/g, "/");
|
|
301
|
+
if (!relativeIndexPath.startsWith("./") && !relativeIndexPath.startsWith("../")) {
|
|
302
|
+
relativeIndexPath = "./" + relativeIndexPath;
|
|
303
|
+
}
|
|
304
|
+
let relativeDirPath = path3.relative(cwd, dirPath).replace(/\\/g, "/");
|
|
305
|
+
if (!relativeDirPath.startsWith("./") && !relativeDirPath.startsWith("../")) {
|
|
306
|
+
relativeDirPath = "./" + relativeDirPath;
|
|
307
|
+
}
|
|
308
|
+
pathsObj[`@${domainName}`] = [relativeIndexPath];
|
|
309
|
+
pathsObj[`@${domainName}/*`] = [`${relativeDirPath}/*`];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (config.aliases) {
|
|
313
|
+
for (const [alias, target] of Object.entries(config.aliases)) {
|
|
314
|
+
let normalizedTarget = path3.isAbsolute(target) ? path3.relative(cwd, target) : target;
|
|
315
|
+
normalizedTarget = normalizedTarget.replace(/\\/g, "/");
|
|
316
|
+
if (!normalizedTarget.startsWith("./") && !normalizedTarget.startsWith("../")) {
|
|
317
|
+
normalizedTarget = "./" + normalizedTarget;
|
|
318
|
+
}
|
|
319
|
+
const isExplicitFile = /\.(ts|js|mts|mjs|cts|cjs|json)$/.test(normalizedTarget);
|
|
320
|
+
const cleanAlias = alias.replace(/\/\*$/, "");
|
|
321
|
+
const cleanTarget = normalizedTarget.replace(/\/\*$/, "");
|
|
322
|
+
if (isExplicitFile) {
|
|
323
|
+
pathsObj[cleanAlias] = [cleanTarget];
|
|
324
|
+
} else {
|
|
325
|
+
pathsObj[cleanAlias] = [cleanTarget];
|
|
326
|
+
pathsObj[`${cleanAlias}/*`] = [`${cleanTarget}/*`];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return pathsObj;
|
|
331
|
+
}
|
|
332
|
+
|
|
233
333
|
// src/cli/commands/sync-tsconfig.ts
|
|
234
334
|
function syncTsconfigCommand() {
|
|
235
335
|
return new Command2("sync-tsconfig").description("Syncs Nodulus aliases into tsconfig.json paths array for IDE support").option("--tsconfig <path>", "Path to tsconfig.json", "tsconfig.json").action(async (options) => {
|
|
236
336
|
try {
|
|
237
337
|
const cwd = process.cwd();
|
|
238
|
-
const configPath =
|
|
239
|
-
if (!
|
|
240
|
-
|
|
338
|
+
const configPath = path4.resolve(cwd, options.tsconfig);
|
|
339
|
+
if (!fs4.existsSync(configPath)) {
|
|
340
|
+
throw new Error(pc3.red(`
|
|
241
341
|
Error: Could not find ${options.tsconfig} at ${configPath}
|
|
242
342
|
`));
|
|
243
|
-
process.exit(1);
|
|
244
343
|
}
|
|
245
344
|
const config = await loadConfig();
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
onlyDirectories: true,
|
|
249
|
-
absolute: true,
|
|
250
|
-
cwd
|
|
251
|
-
});
|
|
252
|
-
moduleDirs.sort();
|
|
253
|
-
const pathsObj = {};
|
|
254
|
-
for (const dirPath of moduleDirs) {
|
|
255
|
-
const modName = path3.basename(dirPath);
|
|
256
|
-
const aliasKey = `@modules/${modName}`;
|
|
257
|
-
let indexPath = path3.join(dirPath, "index.ts");
|
|
258
|
-
if (!fs3.existsSync(indexPath)) {
|
|
259
|
-
indexPath = path3.join(dirPath, "index.js");
|
|
260
|
-
}
|
|
261
|
-
let relativePosixPath = path3.relative(cwd, indexPath).replace(/\\/g, "/");
|
|
262
|
-
if (!relativePosixPath.startsWith("./") && !relativePosixPath.startsWith("../")) {
|
|
263
|
-
relativePosixPath = "./" + relativePosixPath;
|
|
264
|
-
}
|
|
265
|
-
pathsObj[aliasKey] = [relativePosixPath];
|
|
266
|
-
}
|
|
267
|
-
if (config.aliases) {
|
|
268
|
-
for (const [alias, target] of Object.entries(config.aliases)) {
|
|
269
|
-
let relativePosixPath = path3.isAbsolute(target) ? path3.relative(cwd, target) : target;
|
|
270
|
-
relativePosixPath = relativePosixPath.replace(/\\/g, "/");
|
|
271
|
-
if (!relativePosixPath.startsWith("./") && !relativePosixPath.startsWith("../")) {
|
|
272
|
-
relativePosixPath = "./" + relativePosixPath;
|
|
273
|
-
}
|
|
274
|
-
const key = alias.endsWith("/*") ? alias : `${alias}/*`;
|
|
275
|
-
const val = relativePosixPath.endsWith("/*") ? relativePosixPath : `${relativePosixPath}/*`;
|
|
276
|
-
pathsObj[key] = [val];
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
const rawContent = fs3.readFileSync(configPath, "utf8");
|
|
345
|
+
const pathsObj = await generatePathAliases(config, cwd);
|
|
346
|
+
const rawContent = await fs4.promises.readFile(configPath, "utf8");
|
|
280
347
|
const tsconfig = parse(rawContent);
|
|
281
348
|
if (!tsconfig.compilerOptions) {
|
|
282
349
|
tsconfig.compilerOptions = {};
|
|
283
350
|
}
|
|
284
|
-
|
|
285
|
-
|
|
351
|
+
const compilerOptions = tsconfig.compilerOptions;
|
|
352
|
+
if (!compilerOptions.paths) {
|
|
353
|
+
compilerOptions.paths = {};
|
|
286
354
|
}
|
|
287
|
-
|
|
288
|
-
|
|
355
|
+
const paths = compilerOptions.paths;
|
|
356
|
+
for (const key of Object.keys(paths)) {
|
|
357
|
+
const val = paths[key];
|
|
289
358
|
if (key.startsWith("@modules/") && !pathsObj[key]) {
|
|
290
|
-
delete
|
|
359
|
+
delete paths[key];
|
|
291
360
|
} else if (!pathsObj[key] && key.endsWith("/*") && Array.isArray(val) && val.length === 1 && typeof val[0] === "string" && (val[0].startsWith("./") || val[0].startsWith("../")) && val[0].endsWith("/*")) {
|
|
292
|
-
delete
|
|
361
|
+
delete paths[key];
|
|
293
362
|
}
|
|
294
363
|
}
|
|
295
|
-
Object.assign(
|
|
364
|
+
Object.assign(paths, pathsObj);
|
|
296
365
|
const sortedPaths = {};
|
|
297
|
-
Object.keys(
|
|
298
|
-
sortedPaths[k] =
|
|
366
|
+
Object.keys(paths).sort().forEach((k) => {
|
|
367
|
+
sortedPaths[k] = paths[k];
|
|
299
368
|
});
|
|
300
|
-
|
|
301
|
-
|
|
369
|
+
compilerOptions.paths = sortedPaths;
|
|
370
|
+
fs4.writeFileSync(configPath, stringify(tsconfig, null, 2) + "\n", "utf8");
|
|
302
371
|
const moduleCount = Object.keys(pathsObj).filter((k) => k.startsWith("@modules/")).length;
|
|
303
372
|
const aliasCount = Object.keys(pathsObj).length - moduleCount;
|
|
304
373
|
console.log(pc3.green(`
|
|
305
374
|
\u2714 tsconfig.json updated \u2014 ${moduleCount} module(s), ${aliasCount} folder alias(es)`));
|
|
306
375
|
console.log(`Added paths:`);
|
|
307
376
|
const maxKeyLength = Math.max(...Object.keys(pathsObj).map((k) => k.length));
|
|
308
|
-
for (const [key,
|
|
377
|
+
for (const [key, aliasPaths] of Object.entries(pathsObj)) {
|
|
309
378
|
const paddedKey = key.padEnd(maxKeyLength);
|
|
310
|
-
console.log(` ${pc3.cyan(paddedKey)} \u2192 ${
|
|
379
|
+
console.log(` ${pc3.cyan(paddedKey)} \u2192 ${aliasPaths[0]}`);
|
|
311
380
|
}
|
|
312
381
|
console.log("");
|
|
313
382
|
} catch (err) {
|
|
314
|
-
|
|
383
|
+
throw new Error(pc3.red(`
|
|
315
384
|
Error synchronizing tsconfig: ${err.message}
|
|
316
|
-
`));
|
|
317
|
-
process.exit(1);
|
|
385
|
+
`), { cause: err });
|
|
318
386
|
}
|
|
319
387
|
});
|
|
320
388
|
}
|
|
@@ -325,13 +393,13 @@ import pc4 from "picocolors";
|
|
|
325
393
|
|
|
326
394
|
// src/cli/lib/graph-builder.ts
|
|
327
395
|
import fg2 from "fast-glob";
|
|
328
|
-
import
|
|
329
|
-
import
|
|
396
|
+
import path5 from "path";
|
|
397
|
+
import fs6 from "fs";
|
|
330
398
|
|
|
331
399
|
// src/cli/lib/ast-parser.ts
|
|
332
|
-
import * as
|
|
400
|
+
import * as fs5 from "fs";
|
|
333
401
|
|
|
334
|
-
// node_modules/acorn/dist/acorn.mjs
|
|
402
|
+
// ../../node_modules/acorn/dist/acorn.mjs
|
|
335
403
|
var astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 78, 5, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 199, 7, 137, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 55, 9, 266, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 233, 0, 3, 0, 8, 1, 6, 0, 475, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
|
|
336
404
|
var astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 7, 25, 39, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 5, 57, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 24, 43, 261, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 33, 24, 3, 24, 45, 74, 6, 0, 67, 12, 65, 1, 2, 0, 15, 4, 10, 7381, 42, 31, 98, 114, 8702, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 208, 30, 2, 2, 2, 1, 2, 6, 3, 4, 10, 1, 225, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4381, 3, 5773, 3, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 8489];
|
|
337
405
|
var nonASCIIidentifierChars = "\u200C\u200D\xB7\u0300-\u036F\u0387\u0483-\u0487\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u0669\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0897-\u089F\u08CA-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0966-\u096F\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09E6-\u09EF\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B62\u0B63\u0B66-\u0B6F\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BEF\u0C00-\u0C04\u0C3C\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D6F\u0D81-\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0E50-\u0E59\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECE\u0ED0-\u0ED9\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1040-\u1049\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u109D\u135D-\u135F\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u17E0-\u17E9\u180B-\u180D\u180F-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1946-\u194F\u19D0-\u19DA\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AB0-\u1ABD\u1ABF-\u1ADD\u1AE0-\u1AEB\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BB0-\u1BB9\u1BE6-\u1BF3\u1C24-\u1C37\u1C40-\u1C49\u1C50-\u1C59\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DFF\u200C\u200D\u203F\u2040\u2054\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\u30FB\uA620-\uA629\uA66F\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA82C\uA880\uA881\uA8B4-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F1\uA8FF-\uA909\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9D0-\uA9D9\uA9E5\uA9F0-\uA9F9\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uABF0-\uABF9\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFF10-\uFF19\uFF3F\uFF65";
|
|
@@ -5982,7 +6050,7 @@ function parse4(input, options) {
|
|
|
5982
6050
|
return Parser.parse(input, options);
|
|
5983
6051
|
}
|
|
5984
6052
|
|
|
5985
|
-
// node_modules/acorn-walk/dist/walk.mjs
|
|
6053
|
+
// ../../node_modules/acorn-walk/dist/walk.mjs
|
|
5986
6054
|
function simple(node, visitors, baseVisitor, state, override) {
|
|
5987
6055
|
if (!baseVisitor) {
|
|
5988
6056
|
baseVisitor = base;
|
|
@@ -6310,7 +6378,7 @@ base.MethodDefinition = base.PropertyDefinition = base.Property = function(node,
|
|
|
6310
6378
|
function extractModuleImports(filePath) {
|
|
6311
6379
|
const imports = [];
|
|
6312
6380
|
try {
|
|
6313
|
-
const code =
|
|
6381
|
+
const code = fs5.readFileSync(filePath, "utf-8");
|
|
6314
6382
|
const ast = parse4(code, {
|
|
6315
6383
|
ecmaVersion: "latest",
|
|
6316
6384
|
sourceType: "module",
|
|
@@ -6318,27 +6386,33 @@ function extractModuleImports(filePath) {
|
|
|
6318
6386
|
});
|
|
6319
6387
|
simple(ast, {
|
|
6320
6388
|
ImportDeclaration(node) {
|
|
6321
|
-
|
|
6322
|
-
|
|
6389
|
+
const imp = node;
|
|
6390
|
+
if (imp.source && typeof imp.source.value === "string") {
|
|
6391
|
+
const specifier = imp.source.value;
|
|
6323
6392
|
if (specifier.startsWith("@modules/")) {
|
|
6324
6393
|
imports.push({
|
|
6325
6394
|
specifier,
|
|
6326
|
-
line:
|
|
6395
|
+
line: imp.loc?.start.line || 0,
|
|
6327
6396
|
file: filePath
|
|
6328
6397
|
});
|
|
6329
6398
|
}
|
|
6330
6399
|
}
|
|
6331
6400
|
}
|
|
6332
6401
|
});
|
|
6333
|
-
} catch (
|
|
6402
|
+
} catch (error) {
|
|
6403
|
+
if (error.code === "ENOENT") {
|
|
6404
|
+
return [];
|
|
6405
|
+
}
|
|
6406
|
+
console.warn(`[Nodulus] [Parser] Warning: Failed to parse imports in "${filePath}".`);
|
|
6407
|
+
console.debug(` Detail: ${error.message}`);
|
|
6334
6408
|
return [];
|
|
6335
6409
|
}
|
|
6336
6410
|
return imports;
|
|
6337
6411
|
}
|
|
6338
|
-
function
|
|
6412
|
+
function extractIdentifierCall(filePath, calleeName) {
|
|
6339
6413
|
let found = null;
|
|
6340
6414
|
try {
|
|
6341
|
-
const code =
|
|
6415
|
+
const code = fs5.readFileSync(filePath, "utf-8");
|
|
6342
6416
|
const ast = parse4(code, {
|
|
6343
6417
|
ecmaVersion: "latest",
|
|
6344
6418
|
sourceType: "module",
|
|
@@ -6346,44 +6420,97 @@ function extractModuleDeclaration(indexPath) {
|
|
|
6346
6420
|
});
|
|
6347
6421
|
simple(ast, {
|
|
6348
6422
|
CallExpression(node) {
|
|
6349
|
-
|
|
6350
|
-
|
|
6423
|
+
const call = node;
|
|
6424
|
+
if (call.callee.type === "Identifier" && call.callee.name === calleeName) {
|
|
6425
|
+
const nameArg = call.arguments[0];
|
|
6351
6426
|
if (nameArg && nameArg.type === "Literal") {
|
|
6352
6427
|
const name = nameArg.value;
|
|
6353
|
-
const
|
|
6354
|
-
const optionsArg =
|
|
6428
|
+
const options = {};
|
|
6429
|
+
const optionsArg = call.arguments[1];
|
|
6355
6430
|
if (optionsArg && optionsArg.type === "ObjectExpression") {
|
|
6356
6431
|
for (const prop of optionsArg.properties) {
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6432
|
+
if (prop.type !== "Property") continue;
|
|
6433
|
+
let keyName = "";
|
|
6434
|
+
if (prop.key.type === "Identifier") {
|
|
6435
|
+
keyName = prop.key.name;
|
|
6436
|
+
} else if (prop.key.type === "Literal") {
|
|
6437
|
+
keyName = String(prop.key.value);
|
|
6438
|
+
}
|
|
6439
|
+
if (keyName && prop.value.type === "ArrayExpression") {
|
|
6440
|
+
const arr = [];
|
|
6441
|
+
const arrayVal = prop.value;
|
|
6442
|
+
for (const elem of arrayVal.elements) {
|
|
6360
6443
|
if (elem && elem.type === "Literal") {
|
|
6361
|
-
|
|
6444
|
+
arr.push(String(elem.value));
|
|
6362
6445
|
}
|
|
6363
6446
|
}
|
|
6447
|
+
options[keyName] = arr;
|
|
6448
|
+
} else if (keyName && prop.value.type === "Literal") {
|
|
6449
|
+
options[keyName] = prop.value.value;
|
|
6364
6450
|
}
|
|
6365
6451
|
}
|
|
6366
6452
|
}
|
|
6367
|
-
found = { name,
|
|
6453
|
+
found = { name, options };
|
|
6368
6454
|
}
|
|
6369
6455
|
}
|
|
6370
6456
|
}
|
|
6371
6457
|
});
|
|
6372
|
-
} catch (
|
|
6458
|
+
} catch (error) {
|
|
6459
|
+
if (error.code !== "ENOENT") {
|
|
6460
|
+
console.warn(`[Nodulus] [Parser] Warning: Failed to parse identifier call in "${filePath}".`);
|
|
6461
|
+
console.debug(` Detail: ${error.message}`);
|
|
6462
|
+
}
|
|
6373
6463
|
return null;
|
|
6374
6464
|
}
|
|
6375
6465
|
return found;
|
|
6376
6466
|
}
|
|
6467
|
+
function extractModuleDeclaration(indexPath) {
|
|
6468
|
+
const result = extractIdentifierCall(indexPath, "Module");
|
|
6469
|
+
if (!result) return null;
|
|
6470
|
+
return {
|
|
6471
|
+
name: result.name,
|
|
6472
|
+
imports: Array.isArray(result.options.imports) ? result.options.imports : []
|
|
6473
|
+
};
|
|
6474
|
+
}
|
|
6475
|
+
function extractInternalIdentifiers(filePath) {
|
|
6476
|
+
const names = [];
|
|
6477
|
+
const targetCallees = ["Service", "Controller", "Repository", "Schema"];
|
|
6478
|
+
try {
|
|
6479
|
+
const code = fs5.readFileSync(filePath, "utf-8");
|
|
6480
|
+
const ast = parse4(code, {
|
|
6481
|
+
ecmaVersion: "latest",
|
|
6482
|
+
sourceType: "module"
|
|
6483
|
+
});
|
|
6484
|
+
simple(ast, {
|
|
6485
|
+
CallExpression(node) {
|
|
6486
|
+
const call = node;
|
|
6487
|
+
if (call.callee.type === "Identifier" && targetCallees.includes(call.callee.name)) {
|
|
6488
|
+
const nameArg = call.arguments[0];
|
|
6489
|
+
if (nameArg && nameArg.type === "Literal" && typeof nameArg.value === "string") {
|
|
6490
|
+
names.push(nameArg.value);
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
}
|
|
6494
|
+
});
|
|
6495
|
+
} catch (error) {
|
|
6496
|
+
if (error.code !== "ENOENT") {
|
|
6497
|
+
console.warn(`[Nodulus] [Parser] Warning: Failed to parse internal identifiers in "${filePath}".`);
|
|
6498
|
+
console.debug(` Detail: ${error.message}`);
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
return names;
|
|
6502
|
+
}
|
|
6377
6503
|
|
|
6378
6504
|
// src/cli/lib/graph-builder.ts
|
|
6379
|
-
async function buildModuleGraph(
|
|
6505
|
+
async function buildModuleGraph(config, cwd) {
|
|
6506
|
+
const modulesGlob = config.modules || "src/modules/*";
|
|
6380
6507
|
const dirs = await fg2(modulesGlob, { cwd, onlyDirectories: true, absolute: true });
|
|
6381
6508
|
const nodes = [];
|
|
6382
6509
|
for (const dirPath of dirs) {
|
|
6383
|
-
let indexPath =
|
|
6384
|
-
if (!
|
|
6385
|
-
indexPath =
|
|
6386
|
-
if (!
|
|
6510
|
+
let indexPath = path5.join(dirPath, "index.ts");
|
|
6511
|
+
if (!fs6.existsSync(indexPath)) {
|
|
6512
|
+
indexPath = path5.join(dirPath, "index.js");
|
|
6513
|
+
if (!fs6.existsSync(indexPath)) {
|
|
6387
6514
|
continue;
|
|
6388
6515
|
}
|
|
6389
6516
|
}
|
|
@@ -6392,6 +6519,8 @@ async function buildModuleGraph(modulesGlob, cwd) {
|
|
|
6392
6519
|
continue;
|
|
6393
6520
|
}
|
|
6394
6521
|
const actualImports = [];
|
|
6522
|
+
const internalIdentifiers = [];
|
|
6523
|
+
internalIdentifiers.push(...extractInternalIdentifiers(indexPath));
|
|
6395
6524
|
const moduleFiles = await fg2("**/*.{ts,js,mts,mjs}", {
|
|
6396
6525
|
cwd: dirPath,
|
|
6397
6526
|
absolute: true,
|
|
@@ -6400,262 +6529,121 @@ async function buildModuleGraph(modulesGlob, cwd) {
|
|
|
6400
6529
|
for (const file of moduleFiles) {
|
|
6401
6530
|
const fileImports = extractModuleImports(file);
|
|
6402
6531
|
actualImports.push(...fileImports);
|
|
6532
|
+
const fileIdentifiers = extractInternalIdentifiers(file);
|
|
6533
|
+
internalIdentifiers.push(...fileIdentifiers);
|
|
6403
6534
|
}
|
|
6404
6535
|
nodes.push({
|
|
6405
6536
|
name: declaration.name,
|
|
6406
6537
|
dirPath,
|
|
6407
6538
|
indexPath,
|
|
6408
6539
|
declaredImports: declaration.imports,
|
|
6409
|
-
actualImports
|
|
6540
|
+
actualImports,
|
|
6541
|
+
internalIdentifiers
|
|
6410
6542
|
});
|
|
6411
6543
|
}
|
|
6412
|
-
return
|
|
6544
|
+
return {
|
|
6545
|
+
domains: [],
|
|
6546
|
+
modules: nodes
|
|
6547
|
+
};
|
|
6413
6548
|
}
|
|
6414
6549
|
|
|
6415
|
-
// src/core/
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
controllers: entry.controllers.map((c) => c.name)
|
|
6437
|
-
});
|
|
6438
|
-
function createRegistry() {
|
|
6439
|
-
const modules = /* @__PURE__ */ new Map();
|
|
6440
|
-
const aliases = /* @__PURE__ */ new Map();
|
|
6441
|
-
const controllers = /* @__PURE__ */ new Map();
|
|
6442
|
-
const services = /* @__PURE__ */ new Map();
|
|
6443
|
-
const repositories = /* @__PURE__ */ new Map();
|
|
6444
|
-
const schemas = /* @__PURE__ */ new Map();
|
|
6445
|
-
return {
|
|
6446
|
-
hasModule(name) {
|
|
6447
|
-
return modules.has(name);
|
|
6448
|
-
},
|
|
6449
|
-
getModule(name) {
|
|
6450
|
-
const entry = modules.get(name);
|
|
6451
|
-
return entry ? toRegisteredModule(entry) : void 0;
|
|
6452
|
-
},
|
|
6453
|
-
getAllModules() {
|
|
6454
|
-
return Array.from(modules.values()).map(toRegisteredModule);
|
|
6455
|
-
},
|
|
6456
|
-
resolveAlias(alias) {
|
|
6457
|
-
return aliases.get(alias);
|
|
6458
|
-
},
|
|
6459
|
-
getAllAliases() {
|
|
6460
|
-
return Object.fromEntries(aliases.entries());
|
|
6461
|
-
},
|
|
6462
|
-
getDependencyGraph() {
|
|
6463
|
-
const graph = /* @__PURE__ */ new Map();
|
|
6464
|
-
for (const [name, entry] of modules.entries()) {
|
|
6465
|
-
graph.set(name, entry.imports);
|
|
6466
|
-
}
|
|
6467
|
-
return graph;
|
|
6468
|
-
},
|
|
6469
|
-
findCircularDependencies() {
|
|
6470
|
-
const cycles = [];
|
|
6471
|
-
const visited = /* @__PURE__ */ new Set();
|
|
6472
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
6473
|
-
const path5 = [];
|
|
6474
|
-
const dfs = (node) => {
|
|
6475
|
-
visited.add(node);
|
|
6476
|
-
recStack.add(node);
|
|
6477
|
-
path5.push(node);
|
|
6478
|
-
const deps = modules.get(node)?.imports || [];
|
|
6479
|
-
for (const neighbor of deps) {
|
|
6480
|
-
if (!visited.has(neighbor)) {
|
|
6481
|
-
dfs(neighbor);
|
|
6482
|
-
} else if (recStack.has(neighbor)) {
|
|
6483
|
-
const cycleStart = path5.indexOf(neighbor);
|
|
6484
|
-
cycles.push([...path5.slice(cycleStart), neighbor]);
|
|
6485
|
-
}
|
|
6486
|
-
}
|
|
6487
|
-
recStack.delete(node);
|
|
6488
|
-
path5.pop();
|
|
6489
|
-
};
|
|
6490
|
-
for (const node of modules.keys()) {
|
|
6491
|
-
if (!visited.has(node)) {
|
|
6492
|
-
dfs(node);
|
|
6493
|
-
}
|
|
6494
|
-
}
|
|
6495
|
-
return cycles;
|
|
6496
|
-
},
|
|
6497
|
-
registerModule(name, options, dirPath, indexPath) {
|
|
6498
|
-
if (modules.has(name)) {
|
|
6499
|
-
throw new NodulusError(
|
|
6500
|
-
"DUPLICATE_MODULE",
|
|
6501
|
-
`A module with this name already exists. Each module must have a unique name.`,
|
|
6502
|
-
`Module name: ${name}`
|
|
6503
|
-
);
|
|
6504
|
-
}
|
|
6505
|
-
const existing = Array.from(modules.values()).find((m) => m.path === dirPath);
|
|
6506
|
-
if (existing) {
|
|
6507
|
-
throw new NodulusError(
|
|
6508
|
-
"DUPLICATE_MODULE",
|
|
6509
|
-
`A module is already registered for this folder. Call Module() only once per directory.`,
|
|
6510
|
-
`Existing: ${existing.name}, New: ${name}, Folder: ${dirPath}`
|
|
6511
|
-
);
|
|
6512
|
-
}
|
|
6513
|
-
const entry = {
|
|
6514
|
-
name,
|
|
6515
|
-
path: dirPath,
|
|
6516
|
-
indexPath,
|
|
6517
|
-
imports: options.imports || [],
|
|
6518
|
-
exports: options.exports || [],
|
|
6519
|
-
controllers: []
|
|
6520
|
-
};
|
|
6521
|
-
modules.set(name, entry);
|
|
6522
|
-
},
|
|
6523
|
-
registerAlias(alias, targetPath) {
|
|
6524
|
-
const existing = aliases.get(alias);
|
|
6525
|
-
if (existing && existing !== targetPath) {
|
|
6526
|
-
throw new NodulusError(
|
|
6527
|
-
"DUPLICATE_ALIAS",
|
|
6528
|
-
`An alias with this name is already registered to a different target path.`,
|
|
6529
|
-
`Alias: ${alias}, Existing: ${existing}, New: ${targetPath}`
|
|
6530
|
-
);
|
|
6531
|
-
}
|
|
6532
|
-
aliases.set(alias, targetPath);
|
|
6533
|
-
},
|
|
6534
|
-
registerControllerMetadata(entry) {
|
|
6535
|
-
if (controllers.has(entry.path)) {
|
|
6536
|
-
throw new NodulusError(
|
|
6537
|
-
"INVALID_CONTROLLER",
|
|
6538
|
-
`Controller() was called more than once in the same file.`,
|
|
6539
|
-
`File: ${entry.path}`
|
|
6540
|
-
);
|
|
6541
|
-
}
|
|
6542
|
-
controllers.set(entry.path, entry);
|
|
6543
|
-
},
|
|
6544
|
-
getControllerMetadata(filePath) {
|
|
6545
|
-
return controllers.get(filePath);
|
|
6546
|
-
},
|
|
6547
|
-
getAllControllersMetadata() {
|
|
6548
|
-
return Array.from(controllers.values());
|
|
6549
|
-
},
|
|
6550
|
-
getRawModule(name) {
|
|
6551
|
-
return modules.get(name);
|
|
6552
|
-
},
|
|
6553
|
-
registerFileMetadata(entry) {
|
|
6554
|
-
if (entry.type === "service") {
|
|
6555
|
-
if (services.has(entry.name)) {
|
|
6556
|
-
throw new NodulusError(
|
|
6557
|
-
"DUPLICATE_SERVICE",
|
|
6558
|
-
`A service named "${entry.name}" is already registered. Each Service() name must be unique within the registry.`,
|
|
6559
|
-
`Duplicate name: ${entry.name}`
|
|
6560
|
-
);
|
|
6561
|
-
}
|
|
6562
|
-
services.set(entry.name, entry);
|
|
6563
|
-
} else if (entry.type === "repository") {
|
|
6564
|
-
if (repositories.has(entry.name)) {
|
|
6565
|
-
throw new NodulusError(
|
|
6566
|
-
"DUPLICATE_REPOSITORY",
|
|
6567
|
-
`A repository named "${entry.name}" is already registered. Each Repository() name must be unique within the registry.`,
|
|
6568
|
-
`Duplicate name: ${entry.name}`
|
|
6569
|
-
);
|
|
6570
|
-
}
|
|
6571
|
-
repositories.set(entry.name, entry);
|
|
6572
|
-
} else if (entry.type === "schema") {
|
|
6573
|
-
if (schemas.has(entry.name)) {
|
|
6574
|
-
throw new NodulusError(
|
|
6575
|
-
"DUPLICATE_SCHEMA",
|
|
6576
|
-
`A schema named "${entry.name}" is already registered. Each Schema() name must be unique within the registry.`,
|
|
6577
|
-
`Duplicate name: ${entry.name}`
|
|
6578
|
-
);
|
|
6579
|
-
}
|
|
6580
|
-
schemas.set(entry.name, entry);
|
|
6581
|
-
}
|
|
6582
|
-
},
|
|
6583
|
-
getAllServices() {
|
|
6584
|
-
return Array.from(services.values());
|
|
6585
|
-
},
|
|
6586
|
-
getService(name) {
|
|
6587
|
-
return services.get(name);
|
|
6588
|
-
},
|
|
6589
|
-
getAllRepositories() {
|
|
6590
|
-
return Array.from(repositories.values());
|
|
6591
|
-
},
|
|
6592
|
-
getRepository(name) {
|
|
6593
|
-
return repositories.get(name);
|
|
6594
|
-
},
|
|
6595
|
-
getAllSchemas() {
|
|
6596
|
-
return Array.from(schemas.values());
|
|
6597
|
-
},
|
|
6598
|
-
getSchema(name) {
|
|
6599
|
-
return schemas.get(name);
|
|
6600
|
-
},
|
|
6601
|
-
clearRegistry() {
|
|
6602
|
-
modules.clear();
|
|
6603
|
-
aliases.clear();
|
|
6604
|
-
controllers.clear();
|
|
6605
|
-
services.clear();
|
|
6606
|
-
repositories.clear();
|
|
6607
|
-
schemas.clear();
|
|
6608
|
-
}
|
|
6550
|
+
// src/core/utils/cycle-detector.ts
|
|
6551
|
+
function findCircularDependencies(dependencyMap) {
|
|
6552
|
+
const cycles = [];
|
|
6553
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6554
|
+
const recStack = /* @__PURE__ */ new Set();
|
|
6555
|
+
const path8 = [];
|
|
6556
|
+
const dfs = (node) => {
|
|
6557
|
+
visited.add(node);
|
|
6558
|
+
recStack.add(node);
|
|
6559
|
+
path8.push(node);
|
|
6560
|
+
const deps = dependencyMap.get(node) || [];
|
|
6561
|
+
for (const neighbor of deps) {
|
|
6562
|
+
if (!visited.has(neighbor)) {
|
|
6563
|
+
dfs(neighbor);
|
|
6564
|
+
} else if (recStack.has(neighbor)) {
|
|
6565
|
+
const cycleStart = path8.indexOf(neighbor);
|
|
6566
|
+
cycles.push([...path8.slice(cycleStart), neighbor]);
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6569
|
+
recStack.delete(node);
|
|
6570
|
+
path8.pop();
|
|
6609
6571
|
};
|
|
6572
|
+
for (const node of dependencyMap.keys()) {
|
|
6573
|
+
if (!visited.has(node)) {
|
|
6574
|
+
dfs(node);
|
|
6575
|
+
}
|
|
6576
|
+
}
|
|
6577
|
+
return cycles;
|
|
6610
6578
|
}
|
|
6611
|
-
var registryContext = new AsyncLocalStorage();
|
|
6612
6579
|
|
|
6613
6580
|
// src/cli/lib/violations.ts
|
|
6614
|
-
|
|
6581
|
+
var ViolationType = {
|
|
6582
|
+
PRIVATE_IMPORT: "private-import",
|
|
6583
|
+
UNDECLARED_IMPORT: "undeclared-import",
|
|
6584
|
+
CIRCULAR_DEPENDENCY: "circular-dependency"
|
|
6585
|
+
};
|
|
6586
|
+
function analyzeImport(specifier) {
|
|
6587
|
+
const parts = specifier.split("/");
|
|
6588
|
+
const isModules = specifier.startsWith("@modules/");
|
|
6589
|
+
const isAtAlias = specifier.startsWith("@");
|
|
6590
|
+
if (isModules) {
|
|
6591
|
+
if (parts.length > 2) {
|
|
6592
|
+
return { isPrivate: true, suggestion: `${parts[0]}/${parts[1]}`, target: parts[1] };
|
|
6593
|
+
}
|
|
6594
|
+
return { isPrivate: false, suggestion: "", target: parts[1] };
|
|
6595
|
+
}
|
|
6596
|
+
if (isAtAlias) {
|
|
6597
|
+
if (parts.length > 2) {
|
|
6598
|
+
return { isPrivate: true, suggestion: `${parts[0]}/${parts[1]}`, target: parts[1] };
|
|
6599
|
+
}
|
|
6600
|
+
const target = (parts[1] || parts[0]).replace(/^@/, "");
|
|
6601
|
+
return { isPrivate: false, suggestion: "", target };
|
|
6602
|
+
}
|
|
6603
|
+
return { isPrivate: false, suggestion: "", target: "" };
|
|
6604
|
+
}
|
|
6605
|
+
function detectViolations(graph) {
|
|
6615
6606
|
const violations = [];
|
|
6607
|
+
const nodes = graph.modules;
|
|
6616
6608
|
const moduleNames = new Set(nodes.map((n) => n.name));
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
registry.registerModule(node.name, { imports: node.declaredImports }, node.dirPath, node.indexPath);
|
|
6621
|
-
} catch (_err) {
|
|
6609
|
+
if (graph.domains) {
|
|
6610
|
+
for (const d of graph.domains) {
|
|
6611
|
+
moduleNames.add(d.name);
|
|
6622
6612
|
}
|
|
6623
6613
|
}
|
|
6624
6614
|
for (const node of nodes) {
|
|
6625
6615
|
for (const imp of node.actualImports) {
|
|
6626
|
-
const
|
|
6627
|
-
|
|
6628
|
-
if (parts.length > 2) {
|
|
6629
|
-
isPrivate = true;
|
|
6616
|
+
const { isPrivate, suggestion, target } = analyzeImport(imp.specifier);
|
|
6617
|
+
if (isPrivate) {
|
|
6630
6618
|
violations.push({
|
|
6631
|
-
type:
|
|
6619
|
+
type: ViolationType.PRIVATE_IMPORT,
|
|
6632
6620
|
module: node.name,
|
|
6633
6621
|
message: `Private import detected: module "${node.name}" directly imports internal path from "${imp.specifier}".`,
|
|
6634
|
-
suggestion: `Import only the public index: "
|
|
6622
|
+
suggestion: `Import only the public index: "${suggestion}".`,
|
|
6635
6623
|
location: { file: imp.file, line: imp.line }
|
|
6636
6624
|
});
|
|
6637
|
-
}
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
suggestion: `Add "${targetModule}" to the imports array in the Module() declaration of "${node.name}".`,
|
|
6647
|
-
location: { file: imp.file, line: imp.line }
|
|
6648
|
-
});
|
|
6649
|
-
}
|
|
6625
|
+
} else if (target && target !== node.name && moduleNames.has(target)) {
|
|
6626
|
+
if (!node.declaredImports.includes(target)) {
|
|
6627
|
+
violations.push({
|
|
6628
|
+
type: ViolationType.UNDECLARED_IMPORT,
|
|
6629
|
+
module: node.name,
|
|
6630
|
+
message: `Undeclared import: module "${node.name}" imports from "${target}" but it is not declared.`,
|
|
6631
|
+
suggestion: `Add "${target}" to the imports array in the Module() declaration of "${node.name}".`,
|
|
6632
|
+
location: { file: imp.file, line: imp.line }
|
|
6633
|
+
});
|
|
6650
6634
|
}
|
|
6651
6635
|
}
|
|
6652
6636
|
}
|
|
6653
6637
|
}
|
|
6654
|
-
const
|
|
6638
|
+
const dependencyMap = /* @__PURE__ */ new Map();
|
|
6639
|
+
for (const node of nodes) {
|
|
6640
|
+
dependencyMap.set(node.name, node.declaredImports);
|
|
6641
|
+
}
|
|
6642
|
+
const cycles = findCircularDependencies(dependencyMap);
|
|
6655
6643
|
for (const cycle of cycles) {
|
|
6656
6644
|
const cycleStr = cycle.join(" -> ");
|
|
6657
6645
|
violations.push({
|
|
6658
|
-
type:
|
|
6646
|
+
type: ViolationType.CIRCULAR_DEPENDENCY,
|
|
6659
6647
|
module: cycle[0],
|
|
6660
6648
|
message: `Circular dependency detected: ${cycleStr}`,
|
|
6661
6649
|
suggestion: "Extract shared logic into a separate module to break the cycle.",
|
|
@@ -6665,61 +6653,222 @@ function detectViolations(nodes) {
|
|
|
6665
6653
|
return violations;
|
|
6666
6654
|
}
|
|
6667
6655
|
|
|
6656
|
+
// src/nits/nits-store.ts
|
|
6657
|
+
import fs7 from "fs";
|
|
6658
|
+
import path6 from "path";
|
|
6659
|
+
function loadNitsRegistry(cwd, registryPath) {
|
|
6660
|
+
const fullPath = path6.isAbsolute(registryPath) ? registryPath : path6.join(cwd, registryPath);
|
|
6661
|
+
if (!fs7.existsSync(fullPath)) {
|
|
6662
|
+
return { version: "1.0.0", modules: {} };
|
|
6663
|
+
}
|
|
6664
|
+
try {
|
|
6665
|
+
const content = fs7.readFileSync(fullPath, "utf-8");
|
|
6666
|
+
const data2 = JSON.parse(content);
|
|
6667
|
+
if (!data2.modules || typeof data2.modules !== "object") {
|
|
6668
|
+
throw new Error("Invalid registry format");
|
|
6669
|
+
}
|
|
6670
|
+
return data2;
|
|
6671
|
+
} catch (_err) {
|
|
6672
|
+
return { version: "1.0.0", modules: {} };
|
|
6673
|
+
}
|
|
6674
|
+
}
|
|
6675
|
+
function saveNitsRegistry(cwd, registry, registryPath) {
|
|
6676
|
+
const fullPath = path6.isAbsolute(registryPath) ? registryPath : path6.join(cwd, registryPath);
|
|
6677
|
+
const dir = path6.dirname(fullPath);
|
|
6678
|
+
if (!fs7.existsSync(dir)) {
|
|
6679
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
6680
|
+
}
|
|
6681
|
+
fs7.writeFileSync(fullPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
6682
|
+
}
|
|
6683
|
+
|
|
6684
|
+
// src/nits/nits-reconciler.ts
|
|
6685
|
+
import path7 from "path";
|
|
6686
|
+
|
|
6687
|
+
// src/nits/constants.ts
|
|
6688
|
+
var DEFAULT_SIMILARITY_THRESHOLD = 0.9;
|
|
6689
|
+
var MINIMUM_SIMILARITY_THRESHOLD = 0.5;
|
|
6690
|
+
|
|
6691
|
+
// src/nits/nits-hash.ts
|
|
6692
|
+
function calculateJaccardSimilarity(setA, setB) {
|
|
6693
|
+
if (setA.size === 0 && setB.size === 0) return 1;
|
|
6694
|
+
const intersectionSize = [...setA].filter((x) => setB.has(x)).length;
|
|
6695
|
+
const unionSize = (/* @__PURE__ */ new Set([...setA, ...setB])).size;
|
|
6696
|
+
return intersectionSize / unionSize;
|
|
6697
|
+
}
|
|
6698
|
+
function getDynamicThreshold(identifierCount) {
|
|
6699
|
+
if (identifierCount <= 0) return DEFAULT_SIMILARITY_THRESHOLD;
|
|
6700
|
+
const formulaValue = 1 - 1 / identifierCount;
|
|
6701
|
+
return Math.min(
|
|
6702
|
+
DEFAULT_SIMILARITY_THRESHOLD,
|
|
6703
|
+
Math.max(MINIMUM_SIMILARITY_THRESHOLD, formulaValue)
|
|
6704
|
+
);
|
|
6705
|
+
}
|
|
6706
|
+
function areIdentitiesSimilar(oldIdentifiers, newIdentifiers, configThreshold) {
|
|
6707
|
+
const oldSet = new Set(oldIdentifiers);
|
|
6708
|
+
const newSet = new Set(newIdentifiers);
|
|
6709
|
+
const similarity = calculateJaccardSimilarity(oldSet, newSet);
|
|
6710
|
+
const thresholdUsed = configThreshold ?? getDynamicThreshold(Math.max(oldSet.size, newSet.size));
|
|
6711
|
+
return {
|
|
6712
|
+
isSimilar: similarity >= thresholdUsed,
|
|
6713
|
+
similarity,
|
|
6714
|
+
thresholdUsed
|
|
6715
|
+
};
|
|
6716
|
+
}
|
|
6717
|
+
|
|
6718
|
+
// src/nits/nits-id.ts
|
|
6719
|
+
import { randomBytes } from "crypto";
|
|
6720
|
+
function generateNitsId() {
|
|
6721
|
+
const chars = randomBytes(4).toString("hex");
|
|
6722
|
+
return `mod_${chars}`;
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6725
|
+
// src/nits/nits-reconciler.ts
|
|
6726
|
+
function reconcile(graph, oldRegistry, cwd, configThreshold) {
|
|
6727
|
+
const newModulesRecord = {};
|
|
6728
|
+
const orphanedEntries = Object.entries(oldRegistry.modules);
|
|
6729
|
+
const summary = {
|
|
6730
|
+
newModules: 0,
|
|
6731
|
+
movedModules: 0,
|
|
6732
|
+
healedConflicts: 0
|
|
6733
|
+
};
|
|
6734
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
6735
|
+
const normalize = (p) => path7.relative(cwd, p).replace(/\\/g, "/");
|
|
6736
|
+
const unmatchedNodes = [...graph.modules];
|
|
6737
|
+
for (let i = unmatchedNodes.length - 1; i >= 0; i--) {
|
|
6738
|
+
const node = unmatchedNodes[i];
|
|
6739
|
+
const relPath = normalize(node.dirPath);
|
|
6740
|
+
const matchIdx = orphanedEntries.findIndex(([_, entry]) => entry.path === relPath);
|
|
6741
|
+
if (matchIdx !== -1) {
|
|
6742
|
+
const [_, entry] = orphanedEntries[matchIdx];
|
|
6743
|
+
if (usedIds.has(entry.id)) {
|
|
6744
|
+
entry.id = generateNitsId();
|
|
6745
|
+
summary.healedConflicts++;
|
|
6746
|
+
}
|
|
6747
|
+
usedIds.add(entry.id);
|
|
6748
|
+
newModulesRecord[node.name] = {
|
|
6749
|
+
id: entry.id,
|
|
6750
|
+
path: relPath,
|
|
6751
|
+
identifiers: node.internalIdentifiers
|
|
6752
|
+
};
|
|
6753
|
+
unmatchedNodes.splice(i, 1);
|
|
6754
|
+
orphanedEntries.splice(matchIdx, 1);
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
for (let i = unmatchedNodes.length - 1; i >= 0; i--) {
|
|
6758
|
+
const node = unmatchedNodes[i];
|
|
6759
|
+
let bestMatchIdx = -1;
|
|
6760
|
+
let highestSim = 0;
|
|
6761
|
+
for (let j = 0; j < orphanedEntries.length; j++) {
|
|
6762
|
+
const [_, entry] = orphanedEntries[j];
|
|
6763
|
+
const simResult = areIdentitiesSimilar(entry.identifiers, node.internalIdentifiers, configThreshold);
|
|
6764
|
+
if (simResult.isSimilar && simResult.similarity > highestSim) {
|
|
6765
|
+
highestSim = simResult.similarity;
|
|
6766
|
+
bestMatchIdx = j;
|
|
6767
|
+
}
|
|
6768
|
+
}
|
|
6769
|
+
if (bestMatchIdx !== -1) {
|
|
6770
|
+
const [_, entry] = orphanedEntries[bestMatchIdx];
|
|
6771
|
+
if (usedIds.has(entry.id)) {
|
|
6772
|
+
entry.id = generateNitsId();
|
|
6773
|
+
summary.healedConflicts++;
|
|
6774
|
+
}
|
|
6775
|
+
usedIds.add(entry.id);
|
|
6776
|
+
newModulesRecord[node.name] = {
|
|
6777
|
+
id: entry.id,
|
|
6778
|
+
path: normalize(node.dirPath),
|
|
6779
|
+
identifiers: node.internalIdentifiers
|
|
6780
|
+
};
|
|
6781
|
+
summary.movedModules++;
|
|
6782
|
+
unmatchedNodes.splice(i, 1);
|
|
6783
|
+
orphanedEntries.splice(bestMatchIdx, 1);
|
|
6784
|
+
}
|
|
6785
|
+
}
|
|
6786
|
+
for (const node of unmatchedNodes) {
|
|
6787
|
+
const id = generateNitsId();
|
|
6788
|
+
usedIds.add(id);
|
|
6789
|
+
newModulesRecord[node.name] = {
|
|
6790
|
+
id,
|
|
6791
|
+
path: normalize(node.dirPath),
|
|
6792
|
+
identifiers: node.internalIdentifiers
|
|
6793
|
+
};
|
|
6794
|
+
summary.newModules++;
|
|
6795
|
+
}
|
|
6796
|
+
return {
|
|
6797
|
+
registry: {
|
|
6798
|
+
version: oldRegistry.version || "1.0.0",
|
|
6799
|
+
modules: newModulesRecord
|
|
6800
|
+
},
|
|
6801
|
+
summary
|
|
6802
|
+
};
|
|
6803
|
+
}
|
|
6804
|
+
|
|
6668
6805
|
// src/cli/commands/check.ts
|
|
6669
6806
|
function checkCommand() {
|
|
6670
6807
|
const check = new Command3("check");
|
|
6671
6808
|
check.description("Analyzes the project structural integrity to detect architectural violations").option("--strict", "Exit with code 1 if any violation is found", false).option("--module <moduleName>", "Filter analysis by a specific module").option("--format <format>", "Output format: text or json", "text").option("--no-circular", "Skip circular dependency detection").action(async (options) => {
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6809
|
+
const cwd = process.cwd();
|
|
6810
|
+
const config = await loadConfig();
|
|
6811
|
+
const graph = await buildModuleGraph(config, cwd);
|
|
6812
|
+
if (config.nits.enabled) {
|
|
6813
|
+
const oldRegistry = loadNitsRegistry(cwd, config.nits.registryPath);
|
|
6814
|
+
const { registry: updatedRegistry, summary } = reconcile(
|
|
6815
|
+
graph,
|
|
6816
|
+
oldRegistry,
|
|
6817
|
+
cwd,
|
|
6818
|
+
config.nits.similarityThreshold
|
|
6819
|
+
);
|
|
6820
|
+
saveNitsRegistry(cwd, updatedRegistry, config.nits.registryPath);
|
|
6821
|
+
for (const node of graph.modules) {
|
|
6822
|
+
node.id = updatedRegistry.modules[node.name]?.id;
|
|
6682
6823
|
}
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
violations = violations.filter((v) => v.type !== "circular-dependency");
|
|
6824
|
+
if (summary.healedConflicts > 0 && options.format !== "json") {
|
|
6825
|
+
console.log(pc4.yellow(`\u26A0 NITS: Healed ${summary.healedConflicts} ID conflict(s) in registry.`));
|
|
6686
6826
|
}
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6827
|
+
}
|
|
6828
|
+
let nodes = graph.modules;
|
|
6829
|
+
if (options.module) {
|
|
6830
|
+
graph.modules = graph.modules.filter((n) => n.name === options.module);
|
|
6831
|
+
nodes = graph.modules;
|
|
6832
|
+
if (nodes.length === 0) {
|
|
6833
|
+
throw new Error(pc4.red(`\u2717 Error: Module "${options.module}" does not exist.`));
|
|
6693
6834
|
}
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6835
|
+
}
|
|
6836
|
+
let violations = detectViolations(graph);
|
|
6837
|
+
if (options.circular === false) {
|
|
6838
|
+
violations = violations.filter((v) => v.type !== ViolationType.CIRCULAR_DEPENDENCY);
|
|
6839
|
+
}
|
|
6840
|
+
if (options.format === "json") {
|
|
6841
|
+
console.log(JSON.stringify({ domains: graph.domains, modules: nodes, violations }, null, 2));
|
|
6842
|
+
if (options.strict && violations.length > 0) {
|
|
6843
|
+
throw new Error("Structural integrity violations found (JSON format)");
|
|
6844
|
+
}
|
|
6845
|
+
return;
|
|
6846
|
+
}
|
|
6847
|
+
console.log(pc4.bold(pc4.cyan("\nNodulus Architecture Analysis\n")));
|
|
6848
|
+
for (const node of nodes) {
|
|
6849
|
+
const moduleViolations = violations.filter((v) => v.module === node.name);
|
|
6850
|
+
const idStr = node.id ? pc4.gray(` [${node.id}]`) : "";
|
|
6851
|
+
if (moduleViolations.length === 0) {
|
|
6852
|
+
console.log(pc4.green(`\u2714 ${node.name}${idStr} \u2014 OK`));
|
|
6853
|
+
} else {
|
|
6854
|
+
console.log(pc4.red(`\u2717 ${node.name} \u2014 ${moduleViolations.length} problem(s)`));
|
|
6855
|
+
for (const v of moduleViolations) {
|
|
6856
|
+
const prefix = pc4.yellow(" WARN ");
|
|
6857
|
+
if (v.type === ViolationType.CIRCULAR_DEPENDENCY && v.cycle) {
|
|
6858
|
+
console.log(`${prefix} ${v.message}`);
|
|
6859
|
+
console.log(pc4.gray(` Suggestion: ${v.suggestion}`));
|
|
6860
|
+
} else {
|
|
6861
|
+
const loc = v.location ? `${v.location.file}:${v.location.line}` : "Unknown location";
|
|
6862
|
+
console.log(`${prefix} ${v.message} ${pc4.gray(`(${loc})`)}`);
|
|
6863
|
+
console.log(pc4.gray(` Suggestion: ${v.suggestion}`));
|
|
6711
6864
|
}
|
|
6712
6865
|
}
|
|
6713
6866
|
}
|
|
6714
|
-
|
|
6867
|
+
}
|
|
6868
|
+
console.log(`
|
|
6715
6869
|
${violations.length} problem(s) found.`);
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
}
|
|
6719
|
-
} catch (error) {
|
|
6720
|
-
console.error(pc4.red(`
|
|
6721
|
-
An error occurred during check: ${error.message}`));
|
|
6722
|
-
process.exit(1);
|
|
6870
|
+
if (options.strict && violations.length > 0) {
|
|
6871
|
+
throw new Error("Structural integrity violations found.");
|
|
6723
6872
|
}
|
|
6724
6873
|
});
|
|
6725
6874
|
return check;
|
|
@@ -6730,9 +6879,24 @@ import { createRequire } from "module";
|
|
|
6730
6879
|
var require2 = createRequire(import.meta.url);
|
|
6731
6880
|
var pkg = require2("../../package.json");
|
|
6732
6881
|
var program = new Command4();
|
|
6733
|
-
program.name("nodulus").description("Nodulus CLI").version(pkg.version);
|
|
6734
|
-
|
|
6735
|
-
program.
|
|
6736
|
-
|
|
6737
|
-
|
|
6882
|
+
program.name("nodulus").description("Nodulus CLI").version(pkg.version).addCommand(createModuleCommand()).addCommand(syncTsconfigCommand()).addCommand(checkCommand()).exitOverride();
|
|
6883
|
+
try {
|
|
6884
|
+
await program.parseAsync();
|
|
6885
|
+
} catch (err) {
|
|
6886
|
+
if (err.name === "CommanderError") {
|
|
6887
|
+
if (err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version") {
|
|
6888
|
+
process.exit(0);
|
|
6889
|
+
}
|
|
6890
|
+
console.error(err.message);
|
|
6891
|
+
process.exit(err.exitCode || 1);
|
|
6892
|
+
}
|
|
6893
|
+
if (err.message && err.message.includes("\nError:")) {
|
|
6894
|
+
console.error(err.message);
|
|
6895
|
+
} else {
|
|
6896
|
+
console.error(`
|
|
6897
|
+
${err.message || "An unknown error occurred"}
|
|
6898
|
+
`);
|
|
6899
|
+
}
|
|
6900
|
+
process.exit(typeof err.exitCode === "number" ? err.exitCode : 1);
|
|
6901
|
+
}
|
|
6738
6902
|
//# sourceMappingURL=index.js.map
|