@echojs-ecosystem/architect 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,711 @@
1
+ #!/usr/bin/env node
2
+ import * as fs2 from 'fs';
3
+ import { parse, sep, join, extname, resolve, dirname, relative, basename } from 'path';
4
+ import { minimatch } from 'minimatch';
5
+ import ts from 'typescript';
6
+ import { loadConfig, watchConfig as watchConfig$1 } from 'c12';
7
+ import { from, map, switchMap, Observable, filter, debounceTime } from 'rxjs';
8
+ import * as process from 'process';
9
+ import { fromError } from 'zod-validation-error';
10
+ import { z } from 'zod';
11
+ import precinct from 'precinct';
12
+ import { parse as parse$1 } from 'tsconfck';
13
+ import chokidar from 'chokidar';
14
+ import { isGitIgnored } from 'globby';
15
+ import { produce } from 'immer';
16
+ import isGlob from 'is-glob';
17
+
18
+ var normalizeFileTemplate = (fileTemplate, fileTemplateUrl) => {
19
+ if (fileTemplateUrl) {
20
+ return () => fs2.readFileSync(fileTemplateUrl, "utf-8");
21
+ }
22
+ if (!fileTemplate) {
23
+ return void 0;
24
+ }
25
+ if (typeof fileTemplate === "string") {
26
+ return () => fileTemplate;
27
+ }
28
+ return fileTemplate;
29
+ };
30
+ var abstraction = (optionsOrName, optionalConfig) => {
31
+ if (typeof optionsOrName === "string") {
32
+ return {
33
+ name: optionsOrName,
34
+ children: optionalConfig?.children ?? {},
35
+ rules: optionalConfig?.rules ?? [],
36
+ fractal: optionalConfig?.fractal,
37
+ fileTemplate: normalizeFileTemplate(
38
+ optionalConfig?.fileTemplate,
39
+ optionalConfig?.fileTemplateUrl
40
+ )
41
+ };
42
+ }
43
+ return {
44
+ name: optionsOrName.name,
45
+ children: optionsOrName.children ?? {},
46
+ rules: optionsOrName.rules ?? [],
47
+ fractal: optionsOrName.fractal,
48
+ fileTemplate: normalizeFileTemplate(
49
+ optionsOrName.fileTemplate,
50
+ optionsOrName?.fileTemplateUrl
51
+ )
52
+ };
53
+ };
54
+ var getAbstractionInstanceLabel = (instance) => {
55
+ const { name } = parse(instance.path);
56
+ if (name === instance.abstraction.name) {
57
+ return name;
58
+ }
59
+ return `${name} (${instance.abstraction.name})`;
60
+ };
61
+
62
+ // src/shared/memoize.ts
63
+ var memoize = (fn) => {
64
+ const cache = /* @__PURE__ */ new WeakMap();
65
+ return function(arg) {
66
+ if (cache.has(arg)) {
67
+ return cache.get(arg);
68
+ }
69
+ const result = fn.call(this, arg);
70
+ cache.set(arg, result);
71
+ return result;
72
+ };
73
+ };
74
+ var resolveImport = (importedPath, importerPath, tsCompilerOptions, fileExists, directoryExists) => {
75
+ return ts.resolveModuleName(
76
+ importedPath,
77
+ importerPath,
78
+ normalizeCompilerOptions(tsCompilerOptions),
79
+ {
80
+ ...ts.sys,
81
+ fileExists,
82
+ directoryExists
83
+ }
84
+ ).resolvedModule?.resolvedFileName?.replaceAll("/", sep) ?? null;
85
+ };
86
+ var imperfectKeys = {
87
+ module: ts.ModuleKind,
88
+ moduleResolution: {
89
+ ...ts.ModuleResolutionKind,
90
+ node: ts.ModuleResolutionKind.Node10
91
+ },
92
+ moduleDetection: ts.ModuleDetectionKind,
93
+ newLine: ts.NewLineKind,
94
+ target: ts.ScriptTarget
95
+ };
96
+ var normalizeCompilerOptions = (compilerOptions) => {
97
+ return Object.fromEntries(
98
+ Object.entries(compilerOptions).map(([key, value]) => {
99
+ if (Object.keys(imperfectKeys).includes(key) && typeof value === "string") {
100
+ for (const [enumKey, enumValue] of Object.entries(
101
+ imperfectKeys[key]
102
+ )) {
103
+ if (enumKey.toLowerCase() === value.toLowerCase()) {
104
+ return [key, enumValue];
105
+ }
106
+ }
107
+ }
108
+ return [key, value];
109
+ })
110
+ );
111
+ };
112
+
113
+ // src/abstraction/instance/parse.ts
114
+ var parseAbstractionInstance = memoize(
115
+ (abstraction2) => memoize((node) => {
116
+ if (node.type === "file") {
117
+ return {
118
+ abstraction: abstraction2,
119
+ path: node.path,
120
+ children: [],
121
+ childNodes: []
122
+ };
123
+ }
124
+ const children = {};
125
+ for (const [pattern, childAbstraction] of Object.entries(
126
+ abstraction2.children
127
+ )) {
128
+ const nodeAbstractionInstance = parseAbstractionInstance(childAbstraction);
129
+ const nodesStack = [node];
130
+ while (nodesStack.length) {
131
+ const currentNode = nodesStack.pop();
132
+ if (minimatch(relative(node.path, currentNode.path), pattern)) {
133
+ children[currentNode.path] = nodeAbstractionInstance(currentNode);
134
+ continue;
135
+ }
136
+ if (children[currentNode.path]) {
137
+ continue;
138
+ }
139
+ if (currentNode.type === "folder") {
140
+ nodesStack.push(...currentNode.children);
141
+ }
142
+ }
143
+ }
144
+ const childNodes = [];
145
+ const childrenNodesStack = [node];
146
+ while (childrenNodesStack.length) {
147
+ const currentNode = childrenNodesStack.pop();
148
+ if (children[currentNode.path]) {
149
+ continue;
150
+ }
151
+ if (currentNode.path !== node.path) {
152
+ childNodes.push(currentNode.path);
153
+ }
154
+ if (currentNode.type === "folder") {
155
+ childrenNodesStack.push(...currentNode.children);
156
+ }
157
+ }
158
+ return {
159
+ abstraction: abstraction2,
160
+ path: node.path,
161
+ childNodes: Object.values(childNodes),
162
+ children: Object.values(children)
163
+ };
164
+ })
165
+ );
166
+
167
+ // src/config/define-config.ts
168
+ var defineConfig = (config) => config;
169
+ var ConfigurationNotFoundError = class extends Error {
170
+ constructor() {
171
+ super(`Configuration not found in ${process.cwd()}`);
172
+ }
173
+ };
174
+ var ConfigurationInvalidError = class extends Error {
175
+ constructor(error, filepath) {
176
+ super(
177
+ fromError(error, {
178
+ prefix: `Invalid configuration in ${relative(process.cwd(), filepath)}`
179
+ }).toString()
180
+ );
181
+ this.error = error;
182
+ }
183
+ error;
184
+ };
185
+ var RuleSchema = z.object({
186
+ name: z.string(),
187
+ check: z.custom(),
188
+ severity: z.enum(["off", "warn", "error"]),
189
+ descriptionUrl: z.string().optional()
190
+ });
191
+ var AbstractionSchema = z.object({
192
+ name: z.string(),
193
+ children: z.record(z.lazy(() => AbstractionSchema)),
194
+ rules: z.array(RuleSchema),
195
+ fractal: z.string().optional(),
196
+ fileTemplate: z.custom()
197
+ });
198
+ var EvolutionConfigSchema = z.object({
199
+ root: AbstractionSchema,
200
+ baseUrl: z.string().optional(),
201
+ files: z.array(z.string()).optional(),
202
+ ignores: z.array(z.string()).optional()
203
+ });
204
+
205
+ // src/config/load.ts
206
+ var CONFIG_NAME = "architect";
207
+ var parseConfigResult = (filepath, data) => {
208
+ const parseResult = EvolutionConfigSchema.safeParse(data);
209
+ if (!parseResult.success) {
210
+ throw new ConfigurationInvalidError(parseResult.error, filepath);
211
+ }
212
+ return {
213
+ config: parseResult.data,
214
+ configPath: filepath
215
+ };
216
+ };
217
+ var watchConfig = ({
218
+ cwd: cwd2,
219
+ onlyOne
220
+ }) => {
221
+ const config$ = from(
222
+ loadConfig({
223
+ cwd: cwd2,
224
+ name: CONFIG_NAME
225
+ })
226
+ ).pipe(
227
+ map(({ configFile, config }) => {
228
+ if (!configFile) {
229
+ throw new ConfigurationNotFoundError();
230
+ }
231
+ return parseConfigResult(configFile, config);
232
+ })
233
+ );
234
+ if (onlyOne) {
235
+ return config$;
236
+ }
237
+ return config$.pipe(
238
+ switchMap(
239
+ ({ configPath, config }) => new Observable((subscriber) => {
240
+ subscriber.next({ configPath, config });
241
+ let unwatchCallback = () => {
242
+ };
243
+ watchConfig$1({
244
+ cwd: cwd2,
245
+ name: CONFIG_NAME,
246
+ onUpdate: (config2) => {
247
+ subscriber.next(
248
+ parseConfigResult(configPath, config2.newConfig.config)
249
+ );
250
+ }
251
+ }).then(({ unwatch }) => {
252
+ unwatchCallback = unwatch;
253
+ });
254
+ return () => unwatchCallback();
255
+ })
256
+ )
257
+ );
258
+ };
259
+
260
+ // src/vfs/get-flatten-files.ts
261
+ var getFlattenFiles = memoize((node) => {
262
+ if (node.type === "file") {
263
+ return [node];
264
+ }
265
+ return node.children.reduce((acc, child) => {
266
+ if (child.type === "file") {
267
+ return [...acc, child];
268
+ }
269
+ return [...acc, ...getFlattenFiles(child)];
270
+ }, []);
271
+ });
272
+
273
+ // src/dependencies/parse.ts
274
+ var { paperwork } = precinct;
275
+ var parseDependenciesMap = async (vfs) => {
276
+ const dependenciesMap = {
277
+ dependencies: {},
278
+ dependencyFor: {}
279
+ };
280
+ const basePath = vfs.path;
281
+ const { tsconfig } = await parse$1(basePath);
282
+ const files = getFlattenFiles(vfs);
283
+ for (const file of files) {
284
+ const dependencies = paperwork(file.path, {
285
+ includeCore: false,
286
+ fileSystem: fs2
287
+ });
288
+ const resolvedDependencies = dependencies.map(
289
+ (dependency) => resolveImport(
290
+ dependency,
291
+ file.path,
292
+ tsconfig?.compilerOptions ?? {},
293
+ fs2.existsSync,
294
+ fs2.existsSync
295
+ )
296
+ ).filter((dependency) => dependency !== null);
297
+ dependenciesMap.dependencies[file.path] = new Set(resolvedDependencies);
298
+ for (const dependency of resolvedDependencies) {
299
+ if (!dependenciesMap.dependencyFor[dependency]) {
300
+ dependenciesMap.dependencyFor[dependency] = /* @__PURE__ */ new Set();
301
+ }
302
+ dependenciesMap.dependencyFor[dependency].add(file.path);
303
+ }
304
+ }
305
+ return dependenciesMap;
306
+ };
307
+
308
+ // src/rule/rule.ts
309
+ var rule = (config) => {
310
+ const defaultCheck = () => ({ diagnostics: [] });
311
+ if (typeof config === "string") {
312
+ return {
313
+ name: config,
314
+ severity: "error",
315
+ descriptionUrl: void 0,
316
+ check: defaultCheck
317
+ };
318
+ }
319
+ return {
320
+ name: config.name,
321
+ severity: config.severity ?? "error",
322
+ descriptionUrl: config.descriptionUrl,
323
+ check: config.check ?? defaultCheck
324
+ };
325
+ };
326
+
327
+ // src/vfs/get-nodes-record.ts
328
+ var getNodesRecord = memoize(
329
+ (node) => {
330
+ if (node.type === "file") {
331
+ return { [node.path]: node };
332
+ }
333
+ return node.children.reduce(
334
+ (acc, child) => {
335
+ if (child.type === "file") {
336
+ return { ...acc, [child.path]: child };
337
+ }
338
+ return { ...acc, [child.path]: child, ...getNodesRecord(child) };
339
+ },
340
+ {}
341
+ );
342
+ }
343
+ );
344
+ var addDirectory = (tree, newDirectoryPath) => {
345
+ const rootPath = tree.path;
346
+ return produce(tree, (draft) => {
347
+ const pathSegments = relative(rootPath, newDirectoryPath).split(sep);
348
+ let currentFolder = draft;
349
+ for (const pathSegment of pathSegments.slice(0, -1)) {
350
+ const existingChild = currentFolder.children.find(
351
+ (child) => child.type === "folder" && basename(child.path) === pathSegment
352
+ );
353
+ if (existingChild === void 0) {
354
+ currentFolder.children.push({
355
+ type: "folder",
356
+ path: join(currentFolder.path, pathSegment),
357
+ children: []
358
+ });
359
+ currentFolder = currentFolder.children[currentFolder.children.length - 1];
360
+ } else {
361
+ currentFolder = existingChild;
362
+ }
363
+ }
364
+ currentFolder.children.push({
365
+ type: "folder",
366
+ path: newDirectoryPath,
367
+ children: []
368
+ });
369
+ });
370
+ };
371
+ var addFile = (tree, newFilePath) => {
372
+ const rootPath = tree.path;
373
+ return produce(tree, (draft) => {
374
+ const pathSegments = relative(rootPath, newFilePath).split(sep);
375
+ let currentFolder = draft;
376
+ for (const pathSegment of pathSegments.slice(0, -1)) {
377
+ const existingChild = currentFolder.children.find(
378
+ (child) => child.type === "folder" && basename(child.path) === pathSegment
379
+ );
380
+ if (existingChild === void 0) {
381
+ currentFolder.children.push({
382
+ type: "folder",
383
+ path: join(currentFolder.path, pathSegment),
384
+ children: []
385
+ });
386
+ currentFolder = currentFolder.children[currentFolder.children.length - 1];
387
+ } else {
388
+ currentFolder = existingChild;
389
+ }
390
+ }
391
+ currentFolder.children.push({ type: "file", path: newFilePath });
392
+ });
393
+ };
394
+
395
+ // src/vfs/create-root.ts
396
+ var createVfsRoot = (path) => ({
397
+ type: "folder",
398
+ path,
399
+ children: []
400
+ });
401
+ var removeNode = (tree, removedNodePath) => {
402
+ const rootPath = tree.path;
403
+ return produce(tree, (draft) => {
404
+ const pathSegments = relative(rootPath, removedNodePath).split(sep);
405
+ let currentFolder = draft;
406
+ for (const pathSegment of pathSegments.slice(0, -1)) {
407
+ const existingChild = currentFolder.children.find(
408
+ (child) => child.type === "folder" && basename(child.path) === pathSegment
409
+ );
410
+ if (existingChild === void 0) {
411
+ return tree;
412
+ } else {
413
+ currentFolder = existingChild;
414
+ }
415
+ }
416
+ const removedNodeIndex = currentFolder.children.findIndex(
417
+ (child) => child.path === removedNodePath
418
+ );
419
+ if (removedNodeIndex === -1) {
420
+ return tree;
421
+ }
422
+ currentFolder.children.splice(removedNodeIndex, 1);
423
+ });
424
+ };
425
+
426
+ // src/vfs/watch-fs.ts
427
+ var watchFs = (path, { onlyReady } = {}) => {
428
+ const isIgnored$ = from(isGitIgnored({ cwd: path }));
429
+ let vfs$ = isIgnored$.pipe(
430
+ switchMap((isIgnored) => createWatcherObservable({ path, isIgnored }))
431
+ );
432
+ if (onlyReady) {
433
+ vfs$ = vfs$.pipe(filter((e) => e.type === "ready"));
434
+ }
435
+ return vfs$;
436
+ };
437
+ var createWatcherObservable = ({
438
+ path,
439
+ isIgnored
440
+ }) => {
441
+ return new Observable((observer) => {
442
+ let vfs = createVfsRoot(path);
443
+ const watcher = chokidar.watch(path, {
444
+ ignored: (path2) => path2.split(sep).includes("node_modules") || isIgnored(path2),
445
+ ignoreInitial: false,
446
+ alwaysStat: true,
447
+ awaitWriteFinish: true,
448
+ disableGlobbing: true,
449
+ cwd: path
450
+ });
451
+ watcher.on("add", async (relativePath) => {
452
+ vfs = addFile(vfs, join(path, relativePath));
453
+ observer.next({ type: "add", vfs });
454
+ });
455
+ watcher.on("addDir", async (relativePath) => {
456
+ vfs = addDirectory(vfs, join(path, relativePath));
457
+ observer.next({ type: "addDir", vfs });
458
+ });
459
+ watcher.on("change", async () => {
460
+ observer.next({ type: "change", vfs });
461
+ });
462
+ watcher.on("unlink", async (relativePath) => {
463
+ vfs = removeNode(vfs, join(path, relativePath));
464
+ observer.next({ type: "unlink", vfs });
465
+ });
466
+ watcher.on("unlinkDir", async (relativePath) => {
467
+ vfs = removeNode(vfs, join(path, relativePath));
468
+ observer.next({ type: "unlinkDir", vfs });
469
+ });
470
+ watcher.on("ready", () => {
471
+ observer.next({ type: "ready", vfs });
472
+ });
473
+ return () => {
474
+ watcher.close();
475
+ };
476
+ });
477
+ };
478
+ var matchesAllowDownward = (dependencyPath, patterns) => {
479
+ const normalized = dependencyPath.replace(/\\/g, "/");
480
+ return patterns.some(
481
+ (pattern) => minimatch(normalized, pattern) || minimatch(normalized, `**/${pattern}`)
482
+ );
483
+ };
484
+ var off = (rule2) => {
485
+ if (Array.isArray(rule2)) {
486
+ return rule2.map((r) => ({ ...r, severity: "off" }));
487
+ }
488
+ return { ...rule2, severity: "off" };
489
+ };
490
+ var warn = (rule2) => {
491
+ if (Array.isArray(rule2)) {
492
+ return rule2.map((r) => ({ ...r, severity: "warn" }));
493
+ }
494
+ return { ...rule2, severity: "warn" };
495
+ };
496
+ var requiredChildren = (abstractions) => rule({
497
+ name: "default/required-children",
498
+ severity: "error",
499
+ check: ({ instance, root }) => {
500
+ const diagnostics = [];
501
+ const nodesRecord = getNodesRecord(root);
502
+ const reuqiredAbstractions = Object.entries(instance.abstraction.children).filter(([ext]) => !isGlob(ext)).filter(
503
+ ([, abstraction2]) => !abstractions || abstractions.includes(abstraction2.name)
504
+ );
505
+ for (const [ext, abstraction2] of reuqiredAbstractions) {
506
+ const path = join(instance.path, ext);
507
+ const instanceNode = nodesRecord[path];
508
+ if (instanceNode !== void 0) {
509
+ continue;
510
+ }
511
+ const message = `Required abstraction "${abstraction2.name}" in "${getAbstractionInstanceLabel(instance)}"`;
512
+ if (extname(path) === "") {
513
+ diagnostics.push({
514
+ message,
515
+ location: { path },
516
+ fixes: [
517
+ {
518
+ type: "create-folder",
519
+ path
520
+ }
521
+ ]
522
+ });
523
+ } else {
524
+ diagnostics.push({
525
+ message,
526
+ location: { path },
527
+ fixes: [
528
+ {
529
+ type: "create-file",
530
+ path,
531
+ content: abstraction2.fileTemplate?.(path) ?? ""
532
+ }
533
+ ]
534
+ });
535
+ }
536
+ }
537
+ return {
538
+ diagnostics
539
+ };
540
+ }
541
+ });
542
+ var noUnabstractionFiles = () => rule({
543
+ name: "default/no-unabstraction-files",
544
+ severity: "error",
545
+ check: ({ instance, root }) => {
546
+ const record = getNodesRecord(root);
547
+ const files = instance.childNodes.filter(
548
+ (node) => record[node]?.type === "file"
549
+ );
550
+ if (files.length > 0) {
551
+ return {
552
+ diagnostics: files.map((node) => ({
553
+ message: ` 'Unabstraction files are not allowed in ${instance.abstraction.name}'`,
554
+ location: { path: node }
555
+ }))
556
+ };
557
+ }
558
+ return {
559
+ diagnostics: []
560
+ };
561
+ }
562
+ });
563
+ var publicAbstraction = (name) => ({
564
+ name: "default/public-abstraction",
565
+ severity: "error",
566
+ check: ({ instance, dependenciesMap, root }) => {
567
+ const diagnostics = [];
568
+ const nodesRecord = getNodesRecord(root);
569
+ const childFilesEntires = instance.children.flatMap((childInstance) => {
570
+ const instanceNode = nodesRecord[childInstance.path];
571
+ const files = getFlattenFiles(instanceNode);
572
+ return files.map((file) => [file.path, childInstance]);
573
+ });
574
+ const childFilesIndex = Object.fromEntries(childFilesEntires);
575
+ for (const [path, childInstance] of childFilesEntires) {
576
+ const importers = dependenciesMap.dependencyFor[path];
577
+ if (!importers) {
578
+ continue;
579
+ }
580
+ if (childInstance.abstraction.name === name) {
581
+ continue;
582
+ }
583
+ for (const importer of importers) {
584
+ const dependencyInstance = childFilesIndex[importer];
585
+ if (dependencyInstance === void 0) {
586
+ diagnostics.push({
587
+ message: `Imports of "${getAbstractionInstanceLabel(instance)}" bypassing the public api are forbidden`,
588
+ location: { path }
589
+ });
590
+ }
591
+ }
592
+ }
593
+ return { diagnostics };
594
+ }
595
+ });
596
+ var restrictCrossImports = () => rule({
597
+ name: "default/restrict-cross-imports",
598
+ severity: "error",
599
+ check: async ({ root, instance, dependenciesMap }) => {
600
+ const diagnostics = [];
601
+ const nodesRecord = getNodesRecord(root);
602
+ const childFilesEntires = instance.children.flatMap((childInstance) => {
603
+ const instanceNode = nodesRecord[childInstance.path];
604
+ const files = getFlattenFiles(instanceNode);
605
+ return files.map((file) => [file.path, childInstance]);
606
+ });
607
+ const childFilesIndex = Object.fromEntries(childFilesEntires);
608
+ for (const [path, instance2] of childFilesEntires) {
609
+ const dependencies = dependenciesMap.dependencies[path];
610
+ for (const dependency of dependencies) {
611
+ const dependencyInstance = childFilesIndex[dependency];
612
+ if (dependencyInstance === void 0) {
613
+ continue;
614
+ }
615
+ if (dependencyInstance.path !== instance2.path) {
616
+ diagnostics.push({
617
+ message: `Forbidden dependency "${getAbstractionInstanceLabel(instance2)}" <= "${getAbstractionInstanceLabel(dependencyInstance)}".
618
+ cross imports are not allowed!`,
619
+ location: { path }
620
+ });
621
+ }
622
+ }
623
+ }
624
+ return { diagnostics };
625
+ }
626
+ });
627
+ var dependenciesDirection = (order, options) => rule({
628
+ name: `default/dependencies-direction`,
629
+ severity: "error",
630
+ check: async ({ root, instance, dependenciesMap }) => {
631
+ const diagnostics = [];
632
+ const nodesRecord = getNodesRecord(root);
633
+ const allowDownward = options?.allowDownward ?? [];
634
+ const childFilesEntires = instance.children.flatMap((childInstance) => {
635
+ const instanceNode = nodesRecord[childInstance.path];
636
+ const files = getFlattenFiles(instanceNode);
637
+ return files.map((file) => [file.path, childInstance]);
638
+ });
639
+ const childFilesIndex = Object.fromEntries(childFilesEntires);
640
+ for (const [path, instance2] of childFilesEntires) {
641
+ const dependencies = dependenciesMap.dependencies[path];
642
+ const instanceNameIndex = order.indexOf(instance2.abstraction.name);
643
+ for (const dependency of dependencies) {
644
+ if (allowDownward.length > 0 && matchesAllowDownward(dependency, allowDownward)) {
645
+ continue;
646
+ }
647
+ const dependencyInstance = childFilesIndex[dependency];
648
+ if (dependencyInstance === void 0) {
649
+ continue;
650
+ }
651
+ const dependencyInstanceNameIndex = order.indexOf(
652
+ dependencyInstance.abstraction.name
653
+ );
654
+ if (dependencyInstanceNameIndex < instanceNameIndex) {
655
+ diagnostics.push({
656
+ message: `Forbidden dependency "${instance2.abstraction.name}" <= "${dependencyInstance.abstraction.name}".
657
+ allowed dependencies order: ${order.join(" <= ")}`,
658
+ location: { path }
659
+ });
660
+ }
661
+ }
662
+ }
663
+ return { diagnostics };
664
+ }
665
+ });
666
+
667
+ // src/linter/run-rules.ts
668
+ var runRules = async ({
669
+ root,
670
+ instance,
671
+ dependenciesMap
672
+ }) => {
673
+ const ruleDiagnostics = (currentInstance) => async (rule2) => {
674
+ if (rule2.severity === "off") {
675
+ return [];
676
+ }
677
+ const { diagnostics } = await rule2.check({
678
+ root,
679
+ instance: currentInstance,
680
+ dependenciesMap
681
+ });
682
+ return diagnostics.map((d) => ({ ...d, rule: rule2 }));
683
+ };
684
+ const runAbstractionRules = (currentInstance) => {
685
+ return currentInstance.abstraction.rules.map(ruleDiagnostics(currentInstance)).concat(...currentInstance.children.flatMap(runAbstractionRules));
686
+ };
687
+ return await Promise.all(runAbstractionRules(instance)).then((r) => r.flat());
688
+ };
689
+
690
+ // src/linter/lint.ts
691
+ var lint = ({
692
+ watch,
693
+ config,
694
+ configPath
695
+ }) => {
696
+ const rootPath = resolve(dirname(configPath), config.baseUrl ?? "./");
697
+ const parseNode = parseAbstractionInstance(config.root);
698
+ return watchFs(rootPath, { onlyReady: !watch }).pipe(
699
+ debounceTime(500),
700
+ switchMap(async ({ vfs }) => ({
701
+ root: vfs,
702
+ instance: parseNode(vfs),
703
+ dependenciesMap: await parseDependenciesMap(vfs)
704
+ })),
705
+ switchMap(runRules)
706
+ );
707
+ };
708
+
709
+ export { abstraction, defineConfig, dependenciesDirection, getAbstractionInstanceLabel, getFlattenFiles, getNodesRecord, lint, noUnabstractionFiles, off, parseAbstractionInstance, parseDependenciesMap, publicAbstraction, requiredChildren, restrictCrossImports, rule, warn, watchConfig, watchFs };
710
+ //# sourceMappingURL=index.js.map
711
+ //# sourceMappingURL=index.js.map