laymos 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +740 -0
  3. package/dist/cli/cli.d.ts +3 -0
  4. package/dist/cli/cli.d.ts.map +1 -0
  5. package/dist/cli/cli.js +6 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +1 -0
  9. package/dist/cli/run.d.ts +11 -0
  10. package/dist/cli/run.d.ts.map +1 -0
  11. package/dist/cli/run.js +242 -0
  12. package/dist/config/define-config.d.ts +3 -0
  13. package/dist/config/define-config.d.ts.map +1 -0
  14. package/dist/config/define-config.js +228 -0
  15. package/dist/config/index.d.ts +5 -0
  16. package/dist/config/index.d.ts.map +1 -0
  17. package/dist/config/index.js +4 -0
  18. package/dist/config/layer-graph.d.ts +7 -0
  19. package/dist/config/layer-graph.d.ts.map +1 -0
  20. package/dist/config/layer-graph.js +53 -0
  21. package/dist/config/layer.d.ts +5 -0
  22. package/dist/config/layer.d.ts.map +1 -0
  23. package/dist/config/layer.js +21 -0
  24. package/dist/config/module.d.ts +9 -0
  25. package/dist/config/module.d.ts.map +1 -0
  26. package/dist/config/module.js +29 -0
  27. package/dist/config/path.d.ts +4 -0
  28. package/dist/config/path.d.ts.map +1 -0
  29. package/dist/config/path.js +32 -0
  30. package/dist/config/types.d.ts +36 -0
  31. package/dist/config/types.d.ts.map +1 -0
  32. package/dist/config/types.js +1 -0
  33. package/dist/engine/1-extract/extract.d.ts +12 -0
  34. package/dist/engine/1-extract/extract.d.ts.map +1 -0
  35. package/dist/engine/1-extract/extract.js +160 -0
  36. package/dist/engine/1-extract/index.d.ts +3 -0
  37. package/dist/engine/1-extract/index.d.ts.map +1 -0
  38. package/dist/engine/1-extract/index.js +1 -0
  39. package/dist/engine/2-resolve/index.d.ts +3 -0
  40. package/dist/engine/2-resolve/index.d.ts.map +1 -0
  41. package/dist/engine/2-resolve/index.js +1 -0
  42. package/dist/engine/2-resolve/resolve.d.ts +24 -0
  43. package/dist/engine/2-resolve/resolve.d.ts.map +1 -0
  44. package/dist/engine/2-resolve/resolve.js +74 -0
  45. package/dist/engine/3-evaluate/evaluate.d.ts +10 -0
  46. package/dist/engine/3-evaluate/evaluate.d.ts.map +1 -0
  47. package/dist/engine/3-evaluate/evaluate.js +80 -0
  48. package/dist/engine/3-evaluate/index.d.ts +3 -0
  49. package/dist/engine/3-evaluate/index.d.ts.map +1 -0
  50. package/dist/engine/3-evaluate/index.js +1 -0
  51. package/dist/engine/4-emit/emit.d.ts +7 -0
  52. package/dist/engine/4-emit/emit.d.ts.map +1 -0
  53. package/dist/engine/4-emit/emit.js +77 -0
  54. package/dist/engine/4-emit/index.d.ts +2 -0
  55. package/dist/engine/4-emit/index.d.ts.map +1 -0
  56. package/dist/engine/4-emit/index.js +1 -0
  57. package/dist/engine/errors.d.ts +19 -0
  58. package/dist/engine/errors.d.ts.map +1 -0
  59. package/dist/engine/errors.js +5 -0
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +1 -0
  63. package/dist/node.d.ts +24 -0
  64. package/dist/node.d.ts.map +1 -0
  65. package/dist/node.js +139 -0
  66. package/dist/report/architecture.d.ts +104 -0
  67. package/dist/report/architecture.d.ts.map +1 -0
  68. package/dist/report/architecture.js +1 -0
  69. package/dist/report/index.d.ts +3 -0
  70. package/dist/report/index.d.ts.map +1 -0
  71. package/dist/report/index.js +2 -0
  72. package/dist/report/stories.d.ts +22 -0
  73. package/dist/report/stories.d.ts.map +1 -0
  74. package/dist/report/stories.js +1 -0
  75. package/dist/story/artifact/artifact.d.ts +57 -0
  76. package/dist/story/artifact/artifact.d.ts.map +1 -0
  77. package/dist/story/artifact/artifact.js +234 -0
  78. package/dist/story/artifact/index.d.ts +2 -0
  79. package/dist/story/artifact/index.d.ts.map +1 -0
  80. package/dist/story/artifact/index.js +1 -0
  81. package/dist/story/artifact/storage.d.ts +10 -0
  82. package/dist/story/artifact/storage.d.ts.map +1 -0
  83. package/dist/story/artifact/storage.js +162 -0
  84. package/dist/story/artifact/types.d.ts +74 -0
  85. package/dist/story/artifact/types.d.ts.map +1 -0
  86. package/dist/story/artifact/types.js +1 -0
  87. package/dist/story/core/blocks.d.ts +20 -0
  88. package/dist/story/core/blocks.d.ts.map +1 -0
  89. package/dist/story/core/blocks.js +104 -0
  90. package/dist/story/core/declare.d.ts +63 -0
  91. package/dist/story/core/declare.d.ts.map +1 -0
  92. package/dist/story/core/declare.js +62 -0
  93. package/dist/story/core/index.d.ts +4 -0
  94. package/dist/story/core/index.d.ts.map +1 -0
  95. package/dist/story/core/index.js +1 -0
  96. package/dist/story/core/recorder.d.ts +38 -0
  97. package/dist/story/core/recorder.d.ts.map +1 -0
  98. package/dist/story/core/recorder.js +28 -0
  99. package/dist/story/core/runtime-context.d.ts +14 -0
  100. package/dist/story/core/runtime-context.d.ts.map +1 -0
  101. package/dist/story/core/runtime-context.js +19 -0
  102. package/dist/story/core/types.d.ts +21 -0
  103. package/dist/story/core/types.d.ts.map +1 -0
  104. package/dist/story/core/types.js +1 -0
  105. package/dist/story/effect/effect.d.ts +52 -0
  106. package/dist/story/effect/effect.d.ts.map +1 -0
  107. package/dist/story/effect/effect.js +99 -0
  108. package/dist/story/effect/index.d.ts +3 -0
  109. package/dist/story/effect/index.d.ts.map +1 -0
  110. package/dist/story/effect/index.js +1 -0
  111. package/dist/story/runner/index.d.ts +3 -0
  112. package/dist/story/runner/index.d.ts.map +1 -0
  113. package/dist/story/runner/index.js +1 -0
  114. package/dist/story/runner/runner.d.ts +44 -0
  115. package/dist/story/runner/runner.d.ts.map +1 -0
  116. package/dist/story/runner/runner.js +476 -0
  117. package/dist/story/story-runtime/index.d.ts +2 -0
  118. package/dist/story/story-runtime/index.d.ts.map +1 -0
  119. package/dist/story/story-runtime/index.js +1 -0
  120. package/dist/story/story-runtime/story-runtime.d.ts +52 -0
  121. package/dist/story/story-runtime/story-runtime.d.ts.map +1 -0
  122. package/dist/story/story-runtime/story-runtime.js +194 -0
  123. package/dist/story/vanilla/index.d.ts +29 -0
  124. package/dist/story/vanilla/index.d.ts.map +1 -0
  125. package/dist/story/vanilla/index.js +78 -0
  126. package/package.json +68 -0
@@ -0,0 +1,29 @@
1
+ import { normalizeConfigPath } from './path.js';
2
+ export function module(path, options) {
3
+ if (path.length === 0) {
4
+ throw new Error('Module path must not be empty');
5
+ }
6
+ return {
7
+ kind: 'module',
8
+ path: normalizeConfigPath(path),
9
+ ...(options?.description !== undefined
10
+ ? { description: options.description }
11
+ : {}),
12
+ };
13
+ }
14
+ export function rules(module, constraints) {
15
+ if (constraints.canImport === undefined &&
16
+ constraints.canImportedBy === undefined) {
17
+ throw new Error(`Rules for module "${module.path}" must declare canImport or canImportedBy`);
18
+ }
19
+ return {
20
+ kind: 'module-rules',
21
+ module,
22
+ ...(constraints.canImport !== undefined
23
+ ? { canImport: constraints.canImport }
24
+ : {}),
25
+ ...(constraints.canImportedBy !== undefined
26
+ ? { canImportedBy: constraints.canImportedBy }
27
+ : {}),
28
+ };
29
+ }
@@ -0,0 +1,4 @@
1
+ /** Converts a configured path to project-root-relative POSIX form. */
2
+ export declare function normalizeConfigPath(path: string): string;
3
+ export declare function pathContains(parent: string, child: string): boolean;
4
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/config/path.ts"],"names":[],"mappings":"AAGA,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA0BxD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnE"}
@@ -0,0 +1,32 @@
1
+ const globSyntax = /[*?{}[\]]/;
2
+ const windowsAbsolutePath = /^[a-zA-Z]:\//;
3
+ /** Converts a configured path to project-root-relative POSIX form. */
4
+ export function normalizeConfigPath(path) {
5
+ if (path.length === 0) {
6
+ throw new Error('Path must not be empty');
7
+ }
8
+ const unixPath = path.replaceAll('\\', '/');
9
+ if (unixPath.startsWith('/') || windowsAbsolutePath.test(unixPath)) {
10
+ throw new Error(`Path "${path}" must be relative to the project root`);
11
+ }
12
+ if (globSyntax.test(unixPath)) {
13
+ throw new Error(`Path "${path}" must be a plain path, not a glob`);
14
+ }
15
+ const segments = [];
16
+ for (const segment of unixPath.split('/')) {
17
+ if (segment.length === 0 || segment === '.')
18
+ continue;
19
+ if (segment === '..') {
20
+ if (segments.length === 0) {
21
+ throw new Error(`Path "${path}" escapes the project root`);
22
+ }
23
+ segments.pop();
24
+ continue;
25
+ }
26
+ segments.push(segment);
27
+ }
28
+ return segments.join('/') || '.';
29
+ }
30
+ export function pathContains(parent, child) {
31
+ return parent === '.' || child === parent || child.startsWith(`${parent}/`);
32
+ }
@@ -0,0 +1,36 @@
1
+ export interface Layer {
2
+ readonly kind: 'layer';
3
+ readonly name: string;
4
+ readonly paths: readonly string[];
5
+ readonly description?: string;
6
+ }
7
+ export interface LayerEdge {
8
+ readonly from: Layer;
9
+ readonly to: Layer;
10
+ }
11
+ export interface LayerGraph {
12
+ readonly kind: 'layer-graph';
13
+ readonly name: string;
14
+ readonly layers: readonly Layer[];
15
+ readonly edges: readonly LayerEdge[];
16
+ readonly description?: string;
17
+ }
18
+ export interface ModuleDef {
19
+ readonly kind: 'module';
20
+ readonly path: string;
21
+ readonly description?: string;
22
+ }
23
+ export interface ModuleRules {
24
+ readonly kind: 'module-rules';
25
+ readonly module: ModuleDef;
26
+ readonly canImport?: readonly ModuleDef[];
27
+ readonly canImportedBy?: readonly ModuleDef[];
28
+ }
29
+ export interface LaymosConfig {
30
+ readonly sourceRoots: readonly string[];
31
+ readonly graphs: readonly LayerGraph[];
32
+ readonly modules?: readonly ModuleDef[];
33
+ readonly moduleRules?: readonly ModuleRules[];
34
+ readonly ignore?: readonly string[];
35
+ }
36
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { Effect } from 'effect';
2
+ import { ExtractError } from '../errors.js';
3
+ export interface FileNode {
4
+ readonly path: string;
5
+ readonly imports: readonly string[];
6
+ }
7
+ export interface FileGraph {
8
+ readonly files: Readonly<Record<string, FileNode>>;
9
+ }
10
+ /** Extracts configured source roots with skott and type-only tracking enabled. */
11
+ export declare function extractFileGraph(baseDir: string, sourceRoots: readonly string[], ignoredPaths?: readonly string[]): Effect.Effect<FileGraph, ExtractError>;
12
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../../src/engine/1-extract/extract.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;CACpD;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,YAAY,GAAE,SAAS,MAAM,EAAO,GACnC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAgExC"}
@@ -0,0 +1,160 @@
1
+ import { readdir, stat } from 'node:fs/promises';
2
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
3
+ import { Effect } from 'effect';
4
+ import skott from 'skott';
5
+ import { pathContains } from '../../config/path.js';
6
+ import { ExtractError } from '../errors.js';
7
+ /** Extracts configured source roots with skott and type-only tracking enabled. */
8
+ export function extractFileGraph(baseDir, sourceRoots, ignoredPaths = []) {
9
+ return Effect.tryPromise({
10
+ try: async () => {
11
+ const inventoryFiles = await listSourceFiles(baseDir, sourceRoots);
12
+ const eligibleFiles = inventoryFiles.filter((path) => !ignoredPaths.some((ignored) => pathContains(ignored, path)));
13
+ const eligible = new Set(eligibleFiles);
14
+ const imports = new Map(inventoryFiles.map((path) => [path, new Set()]));
15
+ const extracted = new Set();
16
+ for (const sourceRoot of sourceRoots) {
17
+ const absolute = resolve(baseDir, sourceRoot);
18
+ const sourceRootStat = await statOrUndefined(absolute);
19
+ if (sourceRootStat === undefined)
20
+ continue;
21
+ if (sourceRootStat.isFile() && !eligible.has(sourceRoot))
22
+ continue;
23
+ if (!sourceRootStat.isFile() && !sourceRootStat.isDirectory())
24
+ continue;
25
+ const structure = sourceRootStat.isFile()
26
+ ? await runSkott({ baseDir, ignoredPaths, entrypoint: absolute })
27
+ : await runSkott({ baseDir, ignoredPaths, cwd: absolute });
28
+ const resolutionBase = sourceRootStat.isFile()
29
+ ? dirname(absolute)
30
+ : absolute;
31
+ for (const path of collectExtractedGraph({
32
+ baseDir,
33
+ eligible,
34
+ graph: structure.graph,
35
+ imports,
36
+ resolutionBase,
37
+ })) {
38
+ extracted.add(path);
39
+ }
40
+ }
41
+ for (const path of eligibleFiles) {
42
+ if (extracted.has(path))
43
+ continue;
44
+ const entrypoint = resolve(baseDir, path);
45
+ const structure = await runSkott({
46
+ baseDir,
47
+ ignoredPaths,
48
+ entrypoint,
49
+ });
50
+ collectExtractedGraph({
51
+ baseDir,
52
+ eligible,
53
+ graph: structure.graph,
54
+ imports,
55
+ resolutionBase: dirname(entrypoint),
56
+ });
57
+ }
58
+ return {
59
+ files: Object.fromEntries(inventoryFiles.map((path) => [
60
+ path,
61
+ { path, imports: [...imports.get(path)].sort() },
62
+ ])),
63
+ };
64
+ },
65
+ catch: (cause) => new ExtractError({ baseDir, cause }),
66
+ });
67
+ }
68
+ async function runSkott({ baseDir, ignoredPaths, entrypoint, cwd, }) {
69
+ const { getStructure } = await skott({
70
+ ...(entrypoint === undefined ? { cwd: cwd ?? baseDir } : { entrypoint }),
71
+ ignorePatterns: ignoredPaths.flatMap((path) => {
72
+ const absolute = toPosixPath(resolve(baseDir, path));
73
+ return [absolute, `${absolute}/**`];
74
+ }),
75
+ tsConfigPath: resolve(baseDir, 'tsconfig.json'),
76
+ dependencyTracking: {
77
+ builtin: false,
78
+ thirdParty: false,
79
+ typeOnly: true,
80
+ },
81
+ fileExtensions: [...sourceExtensions],
82
+ incremental: false,
83
+ });
84
+ return getStructure();
85
+ }
86
+ function collectExtractedGraph({ baseDir, eligible, graph, imports, resolutionBase, }) {
87
+ const extracted = new Set();
88
+ for (const node of Object.values(graph)) {
89
+ const from = findEligiblePath(baseDir, node.id, eligible, resolutionBase);
90
+ if (from === undefined)
91
+ continue;
92
+ extracted.add(from);
93
+ for (const adjacent of node.adjacentTo) {
94
+ const to = findEligiblePath(baseDir, adjacent, eligible, resolutionBase);
95
+ if (to !== undefined)
96
+ imports.get(from).add(to);
97
+ }
98
+ }
99
+ return extracted;
100
+ }
101
+ const sourceExtensions = ['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx'];
102
+ async function listSourceFiles(baseDir, sourceRoots) {
103
+ const files = new Set();
104
+ for (const sourceRoot of sourceRoots) {
105
+ const absolute = resolve(baseDir, sourceRoot);
106
+ const sourceRootStat = await statOrUndefined(absolute);
107
+ if (sourceRootStat?.isFile()) {
108
+ if (isSupportedSourceFile(sourceRoot))
109
+ files.add(sourceRoot);
110
+ continue;
111
+ }
112
+ if (!sourceRootStat?.isDirectory())
113
+ continue;
114
+ const entries = await readdir(absolute, {
115
+ recursive: true,
116
+ withFileTypes: true,
117
+ });
118
+ for (const entry of entries) {
119
+ if (!entry.isFile() || !isSupportedSourceFile(entry.name))
120
+ continue;
121
+ files.add(toPosixPath(relative(baseDir, join(entry.parentPath, entry.name))));
122
+ }
123
+ }
124
+ return [...files].sort();
125
+ }
126
+ async function statOrUndefined(path) {
127
+ try {
128
+ return await stat(path);
129
+ }
130
+ catch (cause) {
131
+ if (typeof cause === 'object' &&
132
+ cause !== null &&
133
+ 'code' in cause &&
134
+ cause.code === 'ENOENT') {
135
+ return undefined;
136
+ }
137
+ throw cause;
138
+ }
139
+ }
140
+ function isSupportedSourceFile(path) {
141
+ return (sourceExtensions.some((extension) => path.endsWith(extension)) &&
142
+ !path.endsWith('.d.ts') &&
143
+ !/\.min\.(?:[cm]?js|jsx)$/.test(path));
144
+ }
145
+ function findEligiblePath(baseDir, path, eligible, resolutionBase) {
146
+ const absoluteCandidates = isAbsolute(path)
147
+ ? [path]
148
+ : [
149
+ resolve(process.cwd(), path),
150
+ ...(resolutionBase === undefined
151
+ ? []
152
+ : [resolve(resolutionBase, path)]),
153
+ ];
154
+ return absoluteCandidates
155
+ .map((candidate) => toPosixPath(relative(baseDir, candidate)))
156
+ .find((candidate) => eligible.has(candidate));
157
+ }
158
+ function toPosixPath(path) {
159
+ return sep === '/' ? path : path.split(sep).join('/');
160
+ }
@@ -0,0 +1,3 @@
1
+ export { extractFileGraph } from './extract.js';
2
+ export type { FileGraph, FileNode } from './extract.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/1-extract/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1 @@
1
+ export { extractFileGraph } from './extract.js';
@@ -0,0 +1,3 @@
1
+ export { resolveProject } from './resolve.js';
2
+ export type { ResolvedFile, ResolvedProject } from './resolve.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/2-resolve/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1 @@
1
+ export { resolveProject } from './resolve.js';
@@ -0,0 +1,24 @@
1
+ import { Effect } from 'effect';
2
+ import type { LaymosConfig } from '../../config/types.js';
3
+ import type { FileGraph } from '../1-extract/index.js';
4
+ export type ResolvedFile = {
5
+ readonly kind: 'ignored';
6
+ readonly path: string;
7
+ } | {
8
+ readonly kind: 'uncovered';
9
+ readonly path: string;
10
+ } | {
11
+ readonly kind: 'covered';
12
+ readonly path: string;
13
+ readonly layer: string;
14
+ readonly module?: string;
15
+ };
16
+ export interface ResolvedProject {
17
+ readonly config: LaymosConfig;
18
+ readonly fileGraph: FileGraph;
19
+ readonly files: Readonly<Record<string, ResolvedFile>>;
20
+ readonly reachability: Readonly<Record<string, readonly string[]>>;
21
+ }
22
+ /** Assigns files to layers and modules; validation happens in defineConfig. */
23
+ export declare function resolveProject(config: LaymosConfig, fileGraph: FileGraph): Effect.Effect<ResolvedProject>;
24
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/engine/2-resolve/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,uBAAuB,CAAC;AAEjE,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrD;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CACpE;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,SAAS,GACnB,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CA4DhC"}
@@ -0,0 +1,74 @@
1
+ import { Effect } from 'effect';
2
+ import { pathContains } from '../../config/path.js';
3
+ /** Assigns files to layers and modules; validation happens in defineConfig. */
4
+ export function resolveProject(config, fileGraph) {
5
+ const layers = [
6
+ ...new Set(config.graphs.flatMap((graph) => [...graph.layers])),
7
+ ];
8
+ const files = {};
9
+ for (const path of Object.keys(fileGraph.files).sort()) {
10
+ if ((config.ignore ?? []).some((ignored) => pathContains(ignored, path))) {
11
+ files[path] = { kind: 'ignored', path };
12
+ continue;
13
+ }
14
+ const layer = layers
15
+ .flatMap((candidate) => candidate.paths
16
+ .filter((prefix) => pathContains(prefix, path))
17
+ .map((prefix) => ({ candidate, prefix })))
18
+ .sort((a, b) => b.prefix.length - a.prefix.length)[0]?.candidate;
19
+ if (layer === undefined) {
20
+ files[path] = { kind: 'uncovered', path };
21
+ continue;
22
+ }
23
+ const module = (config.modules ?? []).find((candidate) => pathContains(candidate.path, path));
24
+ files[path] = {
25
+ kind: 'covered',
26
+ path,
27
+ layer: layer.name,
28
+ ...(module !== undefined ? { module: module.path } : {}),
29
+ };
30
+ }
31
+ const visibleFileGraph = {
32
+ files: Object.fromEntries(Object.entries(fileGraph.files).map(([path, node]) => [
33
+ path,
34
+ {
35
+ path,
36
+ imports: files[path]?.kind === 'ignored'
37
+ ? []
38
+ : node.imports.filter((target) => files[target]?.kind !== 'ignored'),
39
+ },
40
+ ])),
41
+ };
42
+ return Effect.succeed({
43
+ config,
44
+ fileGraph: visibleFileGraph,
45
+ files,
46
+ reachability: buildReachability(config),
47
+ });
48
+ }
49
+ function buildReachability(config) {
50
+ const adjacency = new Map();
51
+ for (const graph of config.graphs) {
52
+ for (const layer of graph.layers) {
53
+ if (!adjacency.has(layer.name))
54
+ adjacency.set(layer.name, new Set());
55
+ }
56
+ for (const edge of graph.edges) {
57
+ adjacency.get(edge.from.name).add(edge.to.name);
58
+ }
59
+ }
60
+ const reachability = {};
61
+ for (const layer of adjacency.keys()) {
62
+ const reached = new Set();
63
+ const pending = [...(adjacency.get(layer) ?? [])];
64
+ while (pending.length > 0) {
65
+ const next = pending.pop();
66
+ if (reached.has(next))
67
+ continue;
68
+ reached.add(next);
69
+ pending.push(...(adjacency.get(next) ?? []));
70
+ }
71
+ reachability[layer] = [...reached].sort();
72
+ }
73
+ return reachability;
74
+ }
@@ -0,0 +1,10 @@
1
+ import { Effect } from 'effect';
2
+ import type { Coverage, LayerCoverage, LayerViolation, ModuleCoverage, ModuleViolation, Violation } from '../../report/index.js';
3
+ import type { ResolvedProject } from '../2-resolve/index.js';
4
+ export type { Coverage, LayerCoverage, LayerViolation, ModuleCoverage, ModuleViolation, Violation, };
5
+ export interface Evaluation {
6
+ readonly violations: readonly Violation[];
7
+ readonly coverage: Coverage;
8
+ }
9
+ export declare function evaluateRules(resolved: ResolvedProject): Effect.Effect<Evaluation>;
10
+ //# sourceMappingURL=evaluate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../../src/engine/3-evaluate/evaluate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EACV,QAAQ,EACR,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,SAAS,EACV,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,YAAY,EACV,QAAQ,EACR,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,SAAS,GACV,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAoG3B"}
@@ -0,0 +1,80 @@
1
+ import { Effect } from 'effect';
2
+ export function evaluateRules(resolved) {
3
+ const violations = [];
4
+ const rules = new Map((resolved.config.moduleRules ?? []).map((rule) => [rule.module.path, rule]));
5
+ for (const fromPath of Object.keys(resolved.fileGraph.files).sort()) {
6
+ const from = resolved.files[fromPath];
7
+ if (from?.kind !== 'covered')
8
+ continue;
9
+ for (const toPath of resolved.fileGraph.files[fromPath].imports) {
10
+ const to = resolved.files[toPath];
11
+ if (to?.kind !== 'covered')
12
+ continue;
13
+ if (from.layer !== to.layer &&
14
+ !(resolved.reachability[from.layer] ?? []).includes(to.layer)) {
15
+ violations.push({
16
+ kind: 'layer',
17
+ from: { layer: from.layer, file: fromPath },
18
+ to: { layer: to.layer, file: toPath },
19
+ });
20
+ }
21
+ if (from.module === undefined ||
22
+ to.module === undefined ||
23
+ from.module === to.module) {
24
+ continue;
25
+ }
26
+ const fromRule = rules.get(from.module);
27
+ if (fromRule?.canImport !== undefined &&
28
+ !fromRule.canImport.some((module) => module.path === to.module)) {
29
+ violations.push({
30
+ kind: 'module',
31
+ rule: 'canImport',
32
+ from: { module: from.module, layer: from.layer, file: fromPath },
33
+ to: { module: to.module, layer: to.layer, file: toPath },
34
+ });
35
+ }
36
+ const toRule = rules.get(to.module);
37
+ if (toRule?.canImportedBy !== undefined &&
38
+ !toRule.canImportedBy.some((module) => module.path === from.module)) {
39
+ violations.push({
40
+ kind: 'module',
41
+ rule: 'canImportedBy',
42
+ from: { module: from.module, layer: from.layer, file: fromPath },
43
+ to: { module: to.module, layer: to.layer, file: toPath },
44
+ });
45
+ }
46
+ }
47
+ }
48
+ const visibleFiles = Object.values(resolved.files).filter((file) => file.kind !== 'ignored');
49
+ const coveredFiles = visibleFiles.filter((file) => file.kind === 'covered');
50
+ const uncoveredFiles = visibleFiles
51
+ .filter((file) => file.kind === 'uncovered')
52
+ .map((file) => file.path)
53
+ .sort();
54
+ const layerNames = [
55
+ ...new Set(resolved.config.graphs.flatMap((graph) => graph.layers.map((layer) => layer.name))),
56
+ ];
57
+ return Effect.succeed({
58
+ violations,
59
+ coverage: {
60
+ layers: {
61
+ totalFiles: visibleFiles.length,
62
+ coveredFiles: coveredFiles.length,
63
+ uncovered: uncoveredFiles,
64
+ },
65
+ modules: layerNames.map((layer) => {
66
+ const layerFiles = coveredFiles.filter((file) => file.layer === layer);
67
+ const uncovered = layerFiles
68
+ .filter((file) => file.module === undefined)
69
+ .map((file) => file.path)
70
+ .sort();
71
+ return {
72
+ layer,
73
+ totalFiles: layerFiles.length,
74
+ coveredFiles: layerFiles.length - uncovered.length,
75
+ uncovered,
76
+ };
77
+ }),
78
+ },
79
+ });
80
+ }
@@ -0,0 +1,3 @@
1
+ export { evaluateRules } from './evaluate.js';
2
+ export type { Evaluation } from './evaluate.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/3-evaluate/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1 @@
1
+ export { evaluateRules } from './evaluate.js';
@@ -0,0 +1,7 @@
1
+ import { Effect } from 'effect';
2
+ import type { AnalysisWarning, LaymosReport } from '../../report/index.js';
3
+ import type { ResolvedProject } from '../2-resolve/index.js';
4
+ import type { Evaluation } from '../3-evaluate/index.js';
5
+ /** Emits the canonical, serializable result of Laymos analysis. */
6
+ export declare function emitReport(resolved: ResolvedProject, evaluation: Evaluation, warnings?: readonly AnalysisWarning[]): Effect.Effect<LaymosReport>;
7
+ //# sourceMappingURL=emit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../../src/engine/4-emit/emit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAKb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAuEzD,mEAAmE;AACnE,wBAAgB,UAAU,CACxB,QAAQ,EAAE,eAAe,EACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,GAAE,SAAS,eAAe,EAAO,GACxC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAS7B"}
@@ -0,0 +1,77 @@
1
+ import { Effect } from 'effect';
2
+ function emitArchitecture(config) {
3
+ const layers = {};
4
+ for (const graph of config.graphs) {
5
+ for (const layer of graph.layers) {
6
+ layers[layer.name] = {
7
+ paths: layer.paths,
8
+ ...(layer.description !== undefined
9
+ ? { description: layer.description }
10
+ : {}),
11
+ };
12
+ }
13
+ }
14
+ const modules = {};
15
+ for (const module of config.modules ?? []) {
16
+ modules[module.path] =
17
+ module.description !== undefined
18
+ ? { description: module.description }
19
+ : {};
20
+ }
21
+ return {
22
+ sourceRoots: config.sourceRoots,
23
+ layers,
24
+ graphs: config.graphs.map((graph) => ({
25
+ name: graph.name,
26
+ layers: graph.layers.map((layer) => layer.name),
27
+ edges: graph.edges.map((edge) => ({
28
+ from: edge.from.name,
29
+ to: edge.to.name,
30
+ })),
31
+ ...(graph.description !== undefined
32
+ ? { description: graph.description }
33
+ : {}),
34
+ })),
35
+ modules,
36
+ moduleRules: (config.moduleRules ?? []).map((rules) => ({
37
+ module: rules.module.path,
38
+ ...(rules.canImport !== undefined
39
+ ? { canImport: rules.canImport.map((module) => module.path) }
40
+ : {}),
41
+ ...(rules.canImportedBy !== undefined
42
+ ? {
43
+ canImportedBy: rules.canImportedBy.map((module) => module.path),
44
+ }
45
+ : {}),
46
+ })),
47
+ ignoredPaths: config.ignore ?? [],
48
+ };
49
+ }
50
+ function emitFiles(resolved) {
51
+ const files = {};
52
+ for (const [path, file] of Object.entries(resolved.files)) {
53
+ const imports = resolved.fileGraph.files[path]?.imports ?? [];
54
+ if (file.kind === 'covered') {
55
+ files[path] = {
56
+ kind: 'covered',
57
+ layer: file.layer,
58
+ ...(file.module !== undefined ? { module: file.module } : {}),
59
+ imports,
60
+ };
61
+ continue;
62
+ }
63
+ files[path] = { kind: file.kind, imports };
64
+ }
65
+ return files;
66
+ }
67
+ /** Emits the canonical, serializable result of Laymos analysis. */
68
+ export function emitReport(resolved, evaluation, warnings = []) {
69
+ return Effect.succeed({
70
+ schemaVersion: 2,
71
+ architecture: emitArchitecture(resolved.config),
72
+ files: emitFiles(resolved),
73
+ violations: evaluation.violations,
74
+ coverage: evaluation.coverage,
75
+ warnings,
76
+ });
77
+ }
@@ -0,0 +1,2 @@
1
+ export { emitReport } from './emit.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/4-emit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1 @@
1
+ export { emitReport } from './emit.js';
@@ -0,0 +1,19 @@
1
+ declare const ConfigLoadError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
2
+ readonly _tag: "ConfigLoadError";
3
+ } & Readonly<A>;
4
+ export declare class ConfigLoadError extends ConfigLoadError_base<{
5
+ readonly path: string;
6
+ readonly cause: unknown;
7
+ }> {
8
+ }
9
+ declare const ExtractError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
10
+ readonly _tag: "ExtractError";
11
+ } & Readonly<A>;
12
+ export declare class ExtractError extends ExtractError_base<{
13
+ readonly baseDir: string;
14
+ readonly cause: unknown;
15
+ }> {
16
+ }
17
+ export type LaymosError = ConfigLoadError | ExtractError;
18
+ export {};
19
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/engine/errors.ts"],"names":[],"mappings":";;;AAEA,qBAAa,eAAgB,SAAQ,qBAAoC;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;CAAG;;;;AAEL,qBAAa,YAAa,SAAQ,kBAAiC;IACjE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;CAAG;AAEL,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,YAAY,CAAC"}