@vlynk-studios/nodulus-core 1.2.0 → 1.2.5

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/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
- console.error(pc.red(`
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
- console.error(pc.red(`
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 fs3 from "fs";
126
- import path3 from "path";
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 isProduction = process.env.NODE_ENV === "production";
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 = path3.resolve(cwd, options.tsconfig);
239
- if (!fs3.existsSync(configPath)) {
240
- console.error(pc3.red(`
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 globPattern = config.modules.replace(/\\/g, "/");
247
- const moduleDirs = await fg(globPattern, {
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
- if (!tsconfig.compilerOptions.paths) {
285
- tsconfig.compilerOptions.paths = {};
351
+ const compilerOptions = tsconfig.compilerOptions;
352
+ if (!compilerOptions.paths) {
353
+ compilerOptions.paths = {};
286
354
  }
287
- for (const key of Object.keys(tsconfig.compilerOptions.paths)) {
288
- const val = tsconfig.compilerOptions.paths[key];
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 tsconfig.compilerOptions.paths[key];
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 tsconfig.compilerOptions.paths[key];
361
+ delete paths[key];
293
362
  }
294
363
  }
295
- Object.assign(tsconfig.compilerOptions.paths, pathsObj);
364
+ Object.assign(paths, pathsObj);
296
365
  const sortedPaths = {};
297
- Object.keys(tsconfig.compilerOptions.paths).sort().forEach((k) => {
298
- sortedPaths[k] = tsconfig.compilerOptions.paths[k];
366
+ Object.keys(paths).sort().forEach((k) => {
367
+ sortedPaths[k] = paths[k];
299
368
  });
300
- tsconfig.compilerOptions.paths = sortedPaths;
301
- fs3.writeFileSync(configPath, stringify(tsconfig, null, 2) + "\n", "utf8");
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, paths] of Object.entries(pathsObj)) {
377
+ for (const [key, paths2] of Object.entries(pathsObj)) {
309
378
  const paddedKey = key.padEnd(maxKeyLength);
310
- console.log(` ${pc3.cyan(paddedKey)} \u2192 ${paths[0]}`);
379
+ console.log(` ${pc3.cyan(paddedKey)} \u2192 ${paths2[0]}`);
311
380
  }
312
381
  console.log("");
313
382
  } catch (err) {
314
- console.error(pc3.red(`
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 path4 from "path";
329
- import fs5 from "fs";
396
+ import path5 from "path";
397
+ import fs6 from "fs";
330
398
 
331
399
  // src/cli/lib/ast-parser.ts
332
- import * as fs4 from "fs";
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 = fs4.readFileSync(filePath, "utf-8");
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
- if (node.source && typeof node.source.value === "string") {
6322
- const specifier = node.source.value;
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: node.loc?.start.line || 0,
6395
+ line: imp.loc?.start.line || 0,
6327
6396
  file: filePath
6328
6397
  });
6329
6398
  }
6330
6399
  }
6331
6400
  }
6332
6401
  });
6333
- } catch (_error) {
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 extractModuleDeclaration(indexPath) {
6412
+ function extractIdentifierCall(filePath, calleeName) {
6339
6413
  let found = null;
6340
6414
  try {
6341
- const code = fs4.readFileSync(indexPath, "utf-8");
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
- if (node.callee && node.callee.name === "Module") {
6350
- const nameArg = node.arguments[0];
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 imports = [];
6354
- const optionsArg = node.arguments[1];
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
- const isImportsKey = prop.key.type === "Identifier" && prop.key.name === "imports" || prop.key.type === "Literal" && prop.key.value === "imports";
6358
- if (isImportsKey && prop.value.type === "ArrayExpression") {
6359
- for (const elem of prop.value.elements) {
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
- imports.push(elem.value);
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, imports };
6453
+ found = { name, options };
6368
6454
  }
6369
6455
  }
6370
6456
  }
6371
6457
  });
6372
- } catch (_error) {
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(modulesGlob, cwd) {
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 = path4.join(dirPath, "index.ts");
6384
- if (!fs5.existsSync(indexPath)) {
6385
- indexPath = path4.join(dirPath, "index.js");
6386
- if (!fs5.existsSync(indexPath)) {
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 nodes;
6544
+ return {
6545
+ domains: [],
6546
+ modules: nodes
6547
+ };
6413
6548
  }
6414
6549
 
6415
- // src/core/registry.ts
6416
- import { AsyncLocalStorage } from "async_hooks";
6417
-
6418
- // src/core/errors.ts
6419
- var NodulusError = class extends Error {
6420
- code;
6421
- details;
6422
- constructor(code, message, details) {
6423
- super(message);
6424
- this.name = "NodulusError";
6425
- this.code = code;
6426
- this.details = details;
6427
- }
6428
- };
6429
-
6430
- // src/core/registry.ts
6431
- var toRegisteredModule = (entry) => ({
6432
- name: entry.name,
6433
- path: entry.path,
6434
- imports: entry.imports,
6435
- exports: entry.exports,
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
- function detectViolations(nodes) {
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
- const registry = createRegistry();
6618
- for (const node of nodes) {
6619
- try {
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 parts = imp.specifier.split("/");
6627
- let isPrivate = false;
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: "private-import",
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: "@modules/${parts[1]}".`,
6622
+ suggestion: `Import only the public index: "${suggestion}".`,
6635
6623
  location: { file: imp.file, line: imp.line }
6636
6624
  });
6637
- }
6638
- if (!isPrivate) {
6639
- const targetModule = parts[1];
6640
- if (targetModule !== node.name && moduleNames.has(targetModule)) {
6641
- if (!node.declaredImports.includes(targetModule)) {
6642
- violations.push({
6643
- type: "undeclared-import",
6644
- module: node.name,
6645
- message: `Undeclared import: module "${node.name}" imports from "${targetModule}" but it is not declared.`,
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 cycles = registry.findCircularDependencies();
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: "circular-dependency",
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
- try {
6673
- const cwd = process.cwd();
6674
- const config = await loadConfig();
6675
- let nodes = await buildModuleGraph(config.modules, cwd);
6676
- if (options.module) {
6677
- nodes = nodes.filter((n) => n.name === options.module);
6678
- if (nodes.length === 0) {
6679
- console.error(pc4.red(`\u2717 Error: Module "${options.module}" does not exist.`));
6680
- process.exit(1);
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
- let violations = detectViolations(nodes);
6684
- if (options.circular === false) {
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
- if (options.format === "json") {
6688
- console.log(JSON.stringify({ modules: nodes, violations }, null, 2));
6689
- if (options.strict && violations.length > 0) {
6690
- process.exit(1);
6691
- }
6692
- return;
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
- console.log(pc4.bold(pc4.cyan("\nNodulus Architecture Analysis\n")));
6695
- for (const node of nodes) {
6696
- const moduleViolations = violations.filter((v) => v.module === node.name);
6697
- if (moduleViolations.length === 0) {
6698
- console.log(pc4.green(`\u2714 ${node.name} \u2014 OK`));
6699
- } else {
6700
- console.log(pc4.red(`\u2717 ${node.name} \u2014 ${moduleViolations.length} problem(s)`));
6701
- for (const v of moduleViolations) {
6702
- const prefix = pc4.yellow(" WARN ");
6703
- if (v.type === "circular-dependency" && v.cycle) {
6704
- console.log(`${prefix} ${v.message}`);
6705
- console.log(pc4.gray(` Suggestion: ${v.suggestion}`));
6706
- } else {
6707
- const loc = v.location ? `${v.location.file}:${v.location.line}` : "Unknown location";
6708
- console.log(`${prefix} ${v.message} ${pc4.gray(`(${loc})`)}`);
6709
- console.log(pc4.gray(` Suggestion: ${v.suggestion}`));
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
- console.log(`
6867
+ }
6868
+ console.log(`
6715
6869
  ${violations.length} problem(s) found.`);
6716
- if (options.strict && violations.length > 0) {
6717
- process.exit(1);
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
- program.addCommand(createModuleCommand());
6735
- program.addCommand(syncTsconfigCommand());
6736
- program.addCommand(checkCommand());
6737
- program.parse();
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