@azure-net/cli 5.0.0-1 → 5.0.0-3
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/bin/azure-net.js +8 -0
- package/bin/azure-net.ts +8 -0
- package/package.json +1 -1
- package/src/checks/checkInternal.js +2 -0
- package/src/checks/checkInternal.ts +2 -0
- package/src/checks/checkLayerImports.js +136 -0
- package/src/checks/checkLayerImports.ts +185 -0
- package/src/checks/index.js +1 -0
- package/src/checks/index.ts +1 -0
package/bin/azure-net.js
CHANGED
|
@@ -257,6 +257,14 @@ checkCommand
|
|
|
257
257
|
await handler();
|
|
258
258
|
});
|
|
259
259
|
|
|
260
|
+
checkCommand
|
|
261
|
+
.command('layer-imports')
|
|
262
|
+
.description('Check forbidden imports between layers inside each context')
|
|
263
|
+
.action(async () => {
|
|
264
|
+
const handler = (await import('../src/checks/checkLayerImports.js')).default;
|
|
265
|
+
await handler();
|
|
266
|
+
});
|
|
267
|
+
|
|
260
268
|
checkCommand
|
|
261
269
|
.command('layer-boundaries')
|
|
262
270
|
.description('Check layer boundaries between contexts by aliases')
|
package/bin/azure-net.ts
CHANGED
|
@@ -257,6 +257,14 @@ checkCommand
|
|
|
257
257
|
await handler();
|
|
258
258
|
});
|
|
259
259
|
|
|
260
|
+
checkCommand
|
|
261
|
+
.command('layer-imports')
|
|
262
|
+
.description('Check forbidden imports between layers inside each context')
|
|
263
|
+
.action(async () => {
|
|
264
|
+
const handler = (await import('../src/checks/checkLayerImports.js')).default;
|
|
265
|
+
await handler();
|
|
266
|
+
});
|
|
267
|
+
|
|
260
268
|
checkCommand
|
|
261
269
|
.command('layer-boundaries')
|
|
262
270
|
.description('Check layer boundaries between contexts by aliases')
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import checkLayerBoundaries from './checkLayerBoundaries.js';
|
|
|
6
6
|
import checkFoldersStructure from './checkFoldersStructure.js';
|
|
7
7
|
import checkImports from './checkImports.js';
|
|
8
8
|
import checkHooks from './checkHooks.js';
|
|
9
|
+
import checkLayerImports from './checkLayerImports.js';
|
|
9
10
|
const tasks = [
|
|
10
11
|
{ name: 'presenter-names', run: checkPresenterNames },
|
|
11
12
|
{ name: 'provider-names', run: checkProviderNames },
|
|
@@ -13,6 +14,7 @@ const tasks = [
|
|
|
13
14
|
{ name: 'domain', run: checkDomain },
|
|
14
15
|
{ name: 'imports', run: checkImports },
|
|
15
16
|
{ name: 'hooks', run: checkHooks },
|
|
17
|
+
{ name: 'layer-imports', run: checkLayerImports },
|
|
16
18
|
{ name: 'layer-boundaries', run: checkLayerBoundaries },
|
|
17
19
|
{ name: 'folders-structure', run: checkFoldersStructure }
|
|
18
20
|
];
|
|
@@ -6,6 +6,7 @@ import checkLayerBoundaries from './checkLayerBoundaries.js';
|
|
|
6
6
|
import checkFoldersStructure from './checkFoldersStructure.js';
|
|
7
7
|
import checkImports from './checkImports.js';
|
|
8
8
|
import checkHooks from './checkHooks.js';
|
|
9
|
+
import checkLayerImports from './checkLayerImports.js';
|
|
9
10
|
|
|
10
11
|
type CheckTask = {
|
|
11
12
|
name: string;
|
|
@@ -19,6 +20,7 @@ const tasks: CheckTask[] = [
|
|
|
19
20
|
{ name: 'domain', run: checkDomain },
|
|
20
21
|
{ name: 'imports', run: checkImports },
|
|
21
22
|
{ name: 'hooks', run: checkHooks },
|
|
23
|
+
{ name: 'layer-imports', run: checkLayerImports },
|
|
22
24
|
{ name: 'layer-boundaries', run: checkLayerBoundaries },
|
|
23
25
|
{ name: 'folders-structure', run: checkFoldersStructure }
|
|
24
26
|
];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { normalizeContexts } from '../init/initAliases.js';
|
|
4
|
+
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
5
|
+
const TARGET_EXTENSIONS = new Set(['.ts', '.js', '.svelte']);
|
|
6
|
+
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.svelte-kit', '.git']);
|
|
7
|
+
const LAYERS = ['domain', 'application', 'infrastructure', 'presentation'];
|
|
8
|
+
const FORBIDDEN_LAYER_IMPORTS = {
|
|
9
|
+
infrastructure: ['application', 'presentation'],
|
|
10
|
+
application: ['presentation'],
|
|
11
|
+
domain: ['application', 'infrastructure', 'presentation'],
|
|
12
|
+
presentation: []
|
|
13
|
+
};
|
|
14
|
+
const IMPORT_FROM_PATTERN = /\bimport\s+(?:type\s+)?[\s\S]*?\sfrom\s*['"]([^'"]+)['"]/g;
|
|
15
|
+
const EXPORT_FROM_PATTERN = /\bexport\s+(?:type\s+)?\{[\s\S]*?\}\sfrom\s*['"]([^'"]+)['"]/g;
|
|
16
|
+
const DYNAMIC_IMPORT_PATTERN = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
17
|
+
const toPosixPath = (value) => value.split(path.sep).join('/');
|
|
18
|
+
const findLineByIndex = (content, index) => {
|
|
19
|
+
let line = 1;
|
|
20
|
+
for (let i = 0; i < index; i += 1) {
|
|
21
|
+
if (content[i] === '\n') line += 1;
|
|
22
|
+
}
|
|
23
|
+
return line;
|
|
24
|
+
};
|
|
25
|
+
const getRelativePath = (filePath) => toPosixPath(path.relative(process.cwd(), filePath));
|
|
26
|
+
const listFiles = async (rootDir) => {
|
|
27
|
+
const files = [];
|
|
28
|
+
const stack = [rootDir];
|
|
29
|
+
while (stack.length) {
|
|
30
|
+
const current = stack.pop();
|
|
31
|
+
if (!current) continue;
|
|
32
|
+
let entries = [];
|
|
33
|
+
try {
|
|
34
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
35
|
+
} catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
if (!IGNORED_DIRS.has(entry.name)) {
|
|
41
|
+
stack.push(path.join(current, entry.name));
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!entry.isFile()) continue;
|
|
46
|
+
const filePath = path.join(current, entry.name);
|
|
47
|
+
if (!TARGET_EXTENSIONS.has(path.extname(filePath))) continue;
|
|
48
|
+
files.push(filePath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return files;
|
|
52
|
+
};
|
|
53
|
+
const extractImports = (content) => {
|
|
54
|
+
const matches = [];
|
|
55
|
+
const patterns = [IMPORT_FROM_PATTERN, EXPORT_FROM_PATTERN, DYNAMIC_IMPORT_PATTERN];
|
|
56
|
+
for (const pattern of patterns) {
|
|
57
|
+
const regex = new RegExp(pattern.source, 'g');
|
|
58
|
+
let match;
|
|
59
|
+
while ((match = regex.exec(content)) !== null) {
|
|
60
|
+
const value = match[1];
|
|
61
|
+
if (!value) continue;
|
|
62
|
+
matches.push({ value, line: findLineByIndex(content, match.index) });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return matches;
|
|
66
|
+
};
|
|
67
|
+
const getLayerFromPath = (filePath, contextRoot) => {
|
|
68
|
+
for (const layer of LAYERS) {
|
|
69
|
+
const layerRoot = path.join(contextRoot, 'layers', layer);
|
|
70
|
+
if (filePath === layerRoot || filePath.startsWith(`${layerRoot}${path.sep}`)) {
|
|
71
|
+
return layer;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
const getLayerFromAliasImport = (importPath, context) => {
|
|
77
|
+
if (importPath !== context.alias && !importPath.startsWith(`${context.alias}/`)) return null;
|
|
78
|
+
const withoutAlias = importPath.slice(context.alias.length).replace(/^\/+/, '');
|
|
79
|
+
for (const layer of LAYERS) {
|
|
80
|
+
if (withoutAlias === `layers/${layer}` || withoutAlias.startsWith(`layers/${layer}/`)) return layer;
|
|
81
|
+
if (withoutAlias === layer || withoutAlias.startsWith(`${layer}/`)) return layer;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
const getLayerFromRelativeImport = (filePath, importPath, contextRoot) => {
|
|
86
|
+
if (!importPath.startsWith('.')) return null;
|
|
87
|
+
const absoluteImportPath = path.resolve(path.dirname(filePath), importPath);
|
|
88
|
+
return getLayerFromPath(absoluteImportPath, contextRoot);
|
|
89
|
+
};
|
|
90
|
+
const createMessage = (sourceLayer, targetLayer, context) =>
|
|
91
|
+
`Layer import violation: "${context.name}" ${sourceLayer} layer cannot import from ${targetLayer} layer`;
|
|
92
|
+
export default async function checkLayerImports() {
|
|
93
|
+
const config = await loadUserConfig();
|
|
94
|
+
const contexts = normalizeContexts(config.contexts);
|
|
95
|
+
if (!contexts.length) {
|
|
96
|
+
console.error('❌ No contexts found in azure-net.config.ts/js. Fill `contexts` first.');
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const issues = [];
|
|
101
|
+
for (const context of contexts) {
|
|
102
|
+
const contextRoot = path.join(process.cwd(), 'src', 'app', context.name);
|
|
103
|
+
try {
|
|
104
|
+
await fs.access(contextRoot);
|
|
105
|
+
} catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const files = await listFiles(path.join(contextRoot, 'layers'));
|
|
109
|
+
for (const filePath of files) {
|
|
110
|
+
const sourceLayer = getLayerFromPath(filePath, contextRoot);
|
|
111
|
+
if (!sourceLayer) continue;
|
|
112
|
+
const forbiddenLayers = FORBIDDEN_LAYER_IMPORTS[sourceLayer];
|
|
113
|
+
if (!forbiddenLayers.length) continue;
|
|
114
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
115
|
+
const imports = extractImports(content);
|
|
116
|
+
for (const entry of imports) {
|
|
117
|
+
const targetLayer = getLayerFromAliasImport(entry.value, context) ?? getLayerFromRelativeImport(filePath, entry.value, contextRoot);
|
|
118
|
+
if (!targetLayer || !forbiddenLayers.includes(targetLayer)) continue;
|
|
119
|
+
issues.push({
|
|
120
|
+
file: filePath,
|
|
121
|
+
line: entry.line,
|
|
122
|
+
message: createMessage(sourceLayer, targetLayer, context)
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!issues.length) {
|
|
128
|
+
console.log('✅ Layer imports check passed.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
console.error('❌ Layer imports check failed:');
|
|
132
|
+
for (const issue of issues) {
|
|
133
|
+
console.error(`• ${getRelativePath(issue.file)}:${issue.line} — ${issue.message}`);
|
|
134
|
+
}
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { normalizeContexts } from '../init/initAliases.js';
|
|
4
|
+
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
5
|
+
|
|
6
|
+
type LayerName = 'domain' | 'application' | 'infrastructure' | 'presentation';
|
|
7
|
+
|
|
8
|
+
type ContextConfig = {
|
|
9
|
+
name: string;
|
|
10
|
+
alias: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type LayerImportIssue = {
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const TARGET_EXTENSIONS = new Set(['.ts', '.js', '.svelte']);
|
|
20
|
+
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.svelte-kit', '.git']);
|
|
21
|
+
const LAYERS: LayerName[] = ['domain', 'application', 'infrastructure', 'presentation'];
|
|
22
|
+
const FORBIDDEN_LAYER_IMPORTS: Record<LayerName, LayerName[]> = {
|
|
23
|
+
infrastructure: ['application', 'presentation'],
|
|
24
|
+
application: ['presentation'],
|
|
25
|
+
domain: ['application', 'infrastructure', 'presentation'],
|
|
26
|
+
presentation: []
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const IMPORT_FROM_PATTERN = /\bimport\s+(?:type\s+)?[\s\S]*?\sfrom\s*['"]([^'"]+)['"]/g;
|
|
30
|
+
const EXPORT_FROM_PATTERN = /\bexport\s+(?:type\s+)?\{[\s\S]*?\}\sfrom\s*['"]([^'"]+)['"]/g;
|
|
31
|
+
const DYNAMIC_IMPORT_PATTERN = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
32
|
+
|
|
33
|
+
const toPosixPath = (value: string): string => value.split(path.sep).join('/');
|
|
34
|
+
|
|
35
|
+
const findLineByIndex = (content: string, index: number): number => {
|
|
36
|
+
let line = 1;
|
|
37
|
+
for (let i = 0; i < index; i += 1) {
|
|
38
|
+
if (content[i] === '\n') line += 1;
|
|
39
|
+
}
|
|
40
|
+
return line;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getRelativePath = (filePath: string): string => toPosixPath(path.relative(process.cwd(), filePath));
|
|
44
|
+
|
|
45
|
+
const listFiles = async (rootDir: string): Promise<string[]> => {
|
|
46
|
+
const files: string[] = [];
|
|
47
|
+
const stack: string[] = [rootDir];
|
|
48
|
+
|
|
49
|
+
while (stack.length) {
|
|
50
|
+
const current = stack.pop();
|
|
51
|
+
if (!current) continue;
|
|
52
|
+
|
|
53
|
+
let entries: import('node:fs').Dirent[] = [];
|
|
54
|
+
try {
|
|
55
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
56
|
+
} catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
if (!IGNORED_DIRS.has(entry.name)) {
|
|
63
|
+
stack.push(path.join(current, entry.name));
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!entry.isFile()) continue;
|
|
69
|
+
const filePath = path.join(current, entry.name);
|
|
70
|
+
if (!TARGET_EXTENSIONS.has(path.extname(filePath))) continue;
|
|
71
|
+
files.push(filePath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return files;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const extractImports = (content: string): Array<{ value: string; line: number }> => {
|
|
79
|
+
const matches: Array<{ value: string; line: number }> = [];
|
|
80
|
+
const patterns = [IMPORT_FROM_PATTERN, EXPORT_FROM_PATTERN, DYNAMIC_IMPORT_PATTERN];
|
|
81
|
+
|
|
82
|
+
for (const pattern of patterns) {
|
|
83
|
+
const regex = new RegExp(pattern.source, 'g');
|
|
84
|
+
let match: RegExpExecArray | null;
|
|
85
|
+
while ((match = regex.exec(content)) !== null) {
|
|
86
|
+
const value = match[1];
|
|
87
|
+
if (!value) continue;
|
|
88
|
+
matches.push({ value, line: findLineByIndex(content, match.index) });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return matches;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const getLayerFromPath = (filePath: string, contextRoot: string): LayerName | null => {
|
|
96
|
+
for (const layer of LAYERS) {
|
|
97
|
+
const layerRoot = path.join(contextRoot, 'layers', layer);
|
|
98
|
+
if (filePath === layerRoot || filePath.startsWith(`${layerRoot}${path.sep}`)) {
|
|
99
|
+
return layer;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const getLayerFromAliasImport = (importPath: string, context: ContextConfig): LayerName | null => {
|
|
107
|
+
if (importPath !== context.alias && !importPath.startsWith(`${context.alias}/`)) return null;
|
|
108
|
+
|
|
109
|
+
const withoutAlias = importPath.slice(context.alias.length).replace(/^\/+/, '');
|
|
110
|
+
for (const layer of LAYERS) {
|
|
111
|
+
if (withoutAlias === `layers/${layer}` || withoutAlias.startsWith(`layers/${layer}/`)) return layer;
|
|
112
|
+
if (withoutAlias === layer || withoutAlias.startsWith(`${layer}/`)) return layer;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const getLayerFromRelativeImport = (filePath: string, importPath: string, contextRoot: string): LayerName | null => {
|
|
119
|
+
if (!importPath.startsWith('.')) return null;
|
|
120
|
+
|
|
121
|
+
const absoluteImportPath = path.resolve(path.dirname(filePath), importPath);
|
|
122
|
+
return getLayerFromPath(absoluteImportPath, contextRoot);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const createMessage = (sourceLayer: LayerName, targetLayer: LayerName, context: ContextConfig): string =>
|
|
126
|
+
`Layer import violation: "${context.name}" ${sourceLayer} layer cannot import from ${targetLayer} layer`;
|
|
127
|
+
|
|
128
|
+
export default async function checkLayerImports(): Promise<void> {
|
|
129
|
+
const config = await loadUserConfig();
|
|
130
|
+
const contexts = normalizeContexts(config.contexts) as ContextConfig[];
|
|
131
|
+
|
|
132
|
+
if (!contexts.length) {
|
|
133
|
+
console.error('❌ No contexts found in azure-net.config.ts/js. Fill `contexts` first.');
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const issues: LayerImportIssue[] = [];
|
|
139
|
+
|
|
140
|
+
for (const context of contexts) {
|
|
141
|
+
const contextRoot = path.join(process.cwd(), 'src', 'app', context.name);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await fs.access(contextRoot);
|
|
145
|
+
} catch {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const files = await listFiles(path.join(contextRoot, 'layers'));
|
|
150
|
+
|
|
151
|
+
for (const filePath of files) {
|
|
152
|
+
const sourceLayer = getLayerFromPath(filePath, contextRoot);
|
|
153
|
+
if (!sourceLayer) continue;
|
|
154
|
+
|
|
155
|
+
const forbiddenLayers = FORBIDDEN_LAYER_IMPORTS[sourceLayer];
|
|
156
|
+
if (!forbiddenLayers.length) continue;
|
|
157
|
+
|
|
158
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
159
|
+
const imports = extractImports(content);
|
|
160
|
+
|
|
161
|
+
for (const entry of imports) {
|
|
162
|
+
const targetLayer = getLayerFromAliasImport(entry.value, context) ?? getLayerFromRelativeImport(filePath, entry.value, contextRoot);
|
|
163
|
+
if (!targetLayer || !forbiddenLayers.includes(targetLayer)) continue;
|
|
164
|
+
|
|
165
|
+
issues.push({
|
|
166
|
+
file: filePath,
|
|
167
|
+
line: entry.line,
|
|
168
|
+
message: createMessage(sourceLayer, targetLayer, context)
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!issues.length) {
|
|
175
|
+
console.log('✅ Layer imports check passed.');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.error('❌ Layer imports check failed:');
|
|
180
|
+
for (const issue of issues) {
|
|
181
|
+
console.error(`• ${getRelativePath(issue.file)}:${issue.line} — ${issue.message}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
}
|
package/src/checks/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { default as checkProviderGraph } from './checkProviderGraph.js';
|
|
|
4
4
|
export { default as checkDomain } from './checkDomain.js';
|
|
5
5
|
export { default as checkImports } from './checkImports.js';
|
|
6
6
|
export { default as checkHooks } from './checkHooks.js';
|
|
7
|
+
export { default as checkLayerImports } from './checkLayerImports.js';
|
|
7
8
|
export { default as checkLayerBoundaries } from './checkLayerBoundaries.js';
|
|
8
9
|
export { default as checkFoldersStructure } from './checkFoldersStructure.js';
|
|
9
10
|
export { default as checkInternal } from './checkInternal.js';
|
package/src/checks/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { default as checkProviderGraph } from './checkProviderGraph.js';
|
|
|
4
4
|
export { default as checkDomain } from './checkDomain.js';
|
|
5
5
|
export { default as checkImports } from './checkImports.js';
|
|
6
6
|
export { default as checkHooks } from './checkHooks.js';
|
|
7
|
+
export { default as checkLayerImports } from './checkLayerImports.js';
|
|
7
8
|
export { default as checkLayerBoundaries } from './checkLayerBoundaries.js';
|
|
8
9
|
export { default as checkFoldersStructure } from './checkFoldersStructure.js';
|
|
9
10
|
export { default as checkInternal } from './checkInternal.js';
|