@azure-net/cli 5.0.0-1 → 5.0.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/bin/azure-net.js +12 -45
- package/bin/azure-net.ts +13 -5
- package/package.json +1 -1
- package/src/checks/checkDomain.js +8 -38
- package/src/checks/checkFoldersStructure.js +0 -14
- package/src/checks/checkHooks.js +0 -5
- package/src/checks/checkImports.js +0 -1
- package/src/checks/checkInternal.js +2 -1
- package/src/checks/checkInternal.ts +2 -0
- package/src/checks/checkLayerBoundaries.js +50 -54
- package/src/checks/checkLayerBoundaries.ts +55 -58
- package/src/checks/checkLayerImports.js +136 -0
- package/src/checks/checkLayerImports.ts +185 -0
- package/src/checks/checkPresenterNames.js +1 -37
- package/src/checks/checkPresenterNames.ts +1 -6
- package/src/checks/checkProject.js +0 -6
- package/src/checks/checkProviderGraph.js +1 -38
- package/src/checks/checkProviderNames.js +0 -28
- package/src/checks/index.js +1 -1
- package/src/checks/index.ts +1 -0
- package/src/generate/layers/generateRepository.js +4 -19
- package/src/generate/layers/generateRepositoryMethod.js +0 -12
- package/src/generate/layers/generateUseCases.js +0 -6
- package/src/generate/layers/repositoryShared.js +41 -104
- package/src/generate/layers/repositoryShared.ts +17 -12
- package/src/generate/module/generateDomain.js +0 -25
- package/src/generate/module/generateModule.js +0 -10
- package/src/generate/module/generateModulePreset.js +10 -51
- package/src/generate/module/generateModulePreset.ts +9 -8
- package/src/generate/module/generatePresenter.js +29 -76
- package/src/generate/module/generatePresenter.ts +15 -10
- package/src/generate/module/generateRepository.js +0 -17
- package/src/generate/module/generateSchemaFromType.js +9 -58
- package/src/generate/module/generateSchemaFromType.ts +1 -2
- package/src/generate/module/generateTypesJson.js +4 -45
- package/src/generate/module/generateUseCases.js +0 -9
- package/src/generate/module/repositoryModuleShared.js +21 -98
- package/src/generate/module/repositoryModuleShared.ts +14 -10
- package/src/generate/ui/generateComponent.js +0 -16
- package/src/generate/ui/generateDesignComponent.js +0 -12
- package/src/generate/ui/generateWidget.js +0 -12
- package/src/init/initAliases.js +42 -64
- package/src/init/initAliases.ts +47 -42
- package/src/init/initEdges.js +0 -15
- package/src/init/initStructure.js +48 -21
- package/src/init/initStructure.ts +51 -3
- package/src/install/installFresh.js +0 -16
- package/src/plugins/addLint.js +11 -46
- package/src/scaffolds/createCoreSchema.js +6 -40
- package/src/scaffolds/createCoreSchema.ts +6 -18
- package/src/scaffolds/createDatasource.js +26 -61
- package/src/scaffolds/createDatasource.ts +26 -40
- package/src/scaffolds/createDatasourceProvider.js +19 -56
- package/src/scaffolds/createDatasourceProvider.ts +20 -34
- package/src/scaffolds/createPresenterFactory.js +5 -34
- package/src/scaffolds/createPresenterFactory.ts +5 -17
- package/src/scaffolds/createProgram.js +1 -5
- package/src/scaffolds/createResponse.js +3 -11
- package/src/scaffolds/createResponse.ts +3 -1
- package/src/scaffolds/createSchemaRule.js +3 -8
- package/src/scaffolds/createSchemaRule.ts +3 -1
- package/src/scaffolds/createTranslationManager.js +3 -14
- package/src/scaffolds/createTranslationManager.ts +3 -1
- package/src/utils/contextUtils.js +5 -20
- package/src/utils/contextUtils.ts +2 -3
- package/src/utils/fileUtils.js +3 -38
- package/src/utils/fileUtils.ts +0 -26
- package/src/utils/loadConfig.js +1 -14
- package/src/utils/loadConfig.ts +1 -3
- package/src/utils/sharedFoundation.js +48 -0
- package/src/utils/sharedFoundation.ts +70 -0
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
|
|
4
3
|
const TARGET_EXTENSIONS = new Set(['.ts', '.js', '.svelte']);
|
|
5
4
|
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.svelte-kit', '.git']);
|
|
6
|
-
|
|
7
5
|
const CREATE_FACTORY_PATTERN = '(?:export\\s+)?(?:const|let|var)\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s*=\\s*createPresenterFactory\\s*\\(';
|
|
8
6
|
const IMPORT_FROM_PRESENTER_PATTERN = 'import\\s*\\{([^}]+)\\}\\s*from\\s*[\'"]([^\'"]+)[\'"]';
|
|
9
7
|
const PRESENTER_CALL_PATTERN = '\\b([A-Za-z_$][A-Za-z0-9_$]*)\\s*\\(\\s*([\'"`])([^\'"`]*)\\2';
|
|
10
|
-
|
|
11
8
|
const toPosixPath = (value) => value.split(path.sep).join('/');
|
|
12
|
-
|
|
13
9
|
const isPresenterImportPath = (importPath) => {
|
|
14
10
|
const normalized = importPath.replace(/\\/g, '/');
|
|
15
11
|
return /(^|\/)presenter(\/|$)/.test(normalized);
|
|
16
12
|
};
|
|
17
|
-
|
|
18
13
|
const parseImportLocals = (rawImports) => {
|
|
19
14
|
const locals = [];
|
|
20
15
|
for (const segment of rawImports.split(',')) {
|
|
@@ -25,7 +20,6 @@ const parseImportLocals = (rawImports) => {
|
|
|
25
20
|
locals.push(aliasMatch[2]);
|
|
26
21
|
continue;
|
|
27
22
|
}
|
|
28
|
-
|
|
29
23
|
const directMatch = item.match(/^([A-Za-z_$][A-Za-z0-9_$]*)$/);
|
|
30
24
|
if (directMatch?.[1]) {
|
|
31
25
|
locals.push(directMatch[1]);
|
|
@@ -33,22 +27,18 @@ const parseImportLocals = (rawImports) => {
|
|
|
33
27
|
}
|
|
34
28
|
return locals;
|
|
35
29
|
};
|
|
36
|
-
|
|
37
30
|
const listSourceFiles = async (rootDir) => {
|
|
38
31
|
const result = [];
|
|
39
32
|
const stack = [rootDir];
|
|
40
|
-
|
|
41
33
|
while (stack.length) {
|
|
42
34
|
const current = stack.pop();
|
|
43
35
|
if (!current) continue;
|
|
44
|
-
|
|
45
36
|
let entries = [];
|
|
46
37
|
try {
|
|
47
38
|
entries = await fs.readdir(current, { withFileTypes: true });
|
|
48
39
|
} catch {
|
|
49
40
|
continue;
|
|
50
41
|
}
|
|
51
|
-
|
|
52
42
|
for (const entry of entries) {
|
|
53
43
|
if (entry.isDirectory()) {
|
|
54
44
|
if (!IGNORED_DIRS.has(entry.name)) {
|
|
@@ -56,7 +46,6 @@ const listSourceFiles = async (rootDir) => {
|
|
|
56
46
|
}
|
|
57
47
|
continue;
|
|
58
48
|
}
|
|
59
|
-
|
|
60
49
|
if (!entry.isFile()) continue;
|
|
61
50
|
const fullPath = path.join(current, entry.name);
|
|
62
51
|
const extension = path.extname(fullPath);
|
|
@@ -64,10 +53,8 @@ const listSourceFiles = async (rootDir) => {
|
|
|
64
53
|
result.push(fullPath);
|
|
65
54
|
}
|
|
66
55
|
}
|
|
67
|
-
|
|
68
56
|
return result;
|
|
69
57
|
};
|
|
70
|
-
|
|
71
58
|
const getLineByIndex = (content, index) => {
|
|
72
59
|
let line = 1;
|
|
73
60
|
for (let i = 0; i < index; i += 1) {
|
|
@@ -75,7 +62,6 @@ const getLineByIndex = (content, index) => {
|
|
|
75
62
|
}
|
|
76
63
|
return line;
|
|
77
64
|
};
|
|
78
|
-
|
|
79
65
|
const extractFactoryNamesFromContent = (content) => {
|
|
80
66
|
const createFactoryRegex = new RegExp(CREATE_FACTORY_PATTERN, 'g');
|
|
81
67
|
const names = [];
|
|
@@ -86,7 +72,6 @@ const extractFactoryNamesFromContent = (content) => {
|
|
|
86
72
|
}
|
|
87
73
|
return names;
|
|
88
74
|
};
|
|
89
|
-
|
|
90
75
|
const extractImportedPresenterFactories = (content) => {
|
|
91
76
|
const importFromPresenterRegex = new RegExp(IMPORT_FROM_PRESENTER_PATTERN, 'g');
|
|
92
77
|
const names = [];
|
|
@@ -100,12 +85,10 @@ const extractImportedPresenterFactories = (content) => {
|
|
|
100
85
|
}
|
|
101
86
|
return names;
|
|
102
87
|
};
|
|
103
|
-
|
|
104
88
|
const collectPresenterOccurrences = (content, filePath, knownFactories) => {
|
|
105
89
|
const presenterCallRegex = new RegExp(PRESENTER_CALL_PATTERN, 'g');
|
|
106
90
|
const occurrences = [];
|
|
107
91
|
const availableCallees = new Set(['createPresenter']);
|
|
108
|
-
|
|
109
92
|
for (const name of extractFactoryNamesFromContent(content)) {
|
|
110
93
|
availableCallees.add(name);
|
|
111
94
|
}
|
|
@@ -115,7 +98,6 @@ const collectPresenterOccurrences = (content, filePath, knownFactories) => {
|
|
|
115
98
|
for (const name of knownFactories) {
|
|
116
99
|
availableCallees.add(name);
|
|
117
100
|
}
|
|
118
|
-
|
|
119
101
|
let match;
|
|
120
102
|
while ((match = presenterCallRegex.exec(content)) !== null) {
|
|
121
103
|
const callee = match[1];
|
|
@@ -124,7 +106,6 @@ const collectPresenterOccurrences = (content, filePath, knownFactories) => {
|
|
|
124
106
|
if (!callee || !presenterId) continue;
|
|
125
107
|
if (!availableCallees.has(callee)) continue;
|
|
126
108
|
if (quote === '`' && presenterId.includes('${')) continue;
|
|
127
|
-
|
|
128
109
|
occurrences.push({
|
|
129
110
|
id: presenterId,
|
|
130
111
|
file: filePath,
|
|
@@ -132,10 +113,8 @@ const collectPresenterOccurrences = (content, filePath, knownFactories) => {
|
|
|
132
113
|
callee
|
|
133
114
|
});
|
|
134
115
|
}
|
|
135
|
-
|
|
136
116
|
return occurrences;
|
|
137
117
|
};
|
|
138
|
-
|
|
139
118
|
export default async function checkPresenterNames() {
|
|
140
119
|
const srcPath = path.join(process.cwd(), 'src');
|
|
141
120
|
try {
|
|
@@ -145,51 +124,37 @@ export default async function checkPresenterNames() {
|
|
|
145
124
|
process.exitCode = 1;
|
|
146
125
|
return;
|
|
147
126
|
}
|
|
148
|
-
|
|
149
127
|
const files = await listSourceFiles(srcPath);
|
|
150
128
|
const fileContents = new Map();
|
|
151
|
-
|
|
152
129
|
for (const filePath of files) {
|
|
153
|
-
// eslint-disable-next-line no-await-in-loop
|
|
154
130
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
155
131
|
fileContents.set(filePath, content);
|
|
156
132
|
}
|
|
157
|
-
|
|
158
133
|
const globalFactoryNames = new Set();
|
|
159
|
-
for (const
|
|
134
|
+
for (const content of fileContents.values()) {
|
|
160
135
|
for (const name of extractFactoryNamesFromContent(content)) {
|
|
161
136
|
globalFactoryNames.add(name);
|
|
162
137
|
}
|
|
163
|
-
if (filePath.endsWith(path.join('core', 'presenter', 'index.ts')) || filePath.endsWith(path.join('core', 'presenter', 'index.js'))) {
|
|
164
|
-
for (const name of extractImportedPresenterFactories(content)) {
|
|
165
|
-
globalFactoryNames.add(name);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
138
|
}
|
|
169
|
-
|
|
170
139
|
const occurrences = [];
|
|
171
140
|
for (const [filePath, content] of fileContents.entries()) {
|
|
172
141
|
occurrences.push(...collectPresenterOccurrences(content, filePath, globalFactoryNames));
|
|
173
142
|
}
|
|
174
|
-
|
|
175
143
|
if (!occurrences.length) {
|
|
176
144
|
console.log('✅ No presenter declarations found.');
|
|
177
145
|
return;
|
|
178
146
|
}
|
|
179
|
-
|
|
180
147
|
const grouped = new Map();
|
|
181
148
|
for (const occurrence of occurrences) {
|
|
182
149
|
const list = grouped.get(occurrence.id) ?? [];
|
|
183
150
|
list.push(occurrence);
|
|
184
151
|
grouped.set(occurrence.id, list);
|
|
185
152
|
}
|
|
186
|
-
|
|
187
153
|
const duplicated = Array.from(grouped.entries()).filter(([, list]) => list.length > 1);
|
|
188
154
|
if (!duplicated.length) {
|
|
189
155
|
console.log(`✅ Presenter names are unique (${occurrences.length} found).`);
|
|
190
156
|
return;
|
|
191
157
|
}
|
|
192
|
-
|
|
193
158
|
console.error('❌ Duplicate presenter names found:');
|
|
194
159
|
for (const [presenterId, list] of duplicated) {
|
|
195
160
|
console.error(`\n- "${presenterId}"`);
|
|
@@ -197,6 +162,5 @@ export default async function checkPresenterNames() {
|
|
|
197
162
|
console.error(` • ${toPosixPath(path.relative(process.cwd(), item.file))}:${item.line} (${item.callee})`);
|
|
198
163
|
}
|
|
199
164
|
}
|
|
200
|
-
|
|
201
165
|
process.exitCode = 1;
|
|
202
166
|
}
|
|
@@ -162,15 +162,10 @@ export default async function checkPresenterNames(): Promise<void> {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
const globalFactoryNames = new Set<string>();
|
|
165
|
-
for (const
|
|
165
|
+
for (const content of fileContents.values()) {
|
|
166
166
|
for (const name of extractFactoryNamesFromContent(content)) {
|
|
167
167
|
globalFactoryNames.add(name);
|
|
168
168
|
}
|
|
169
|
-
if (filePath.endsWith(path.join('core', 'presenter', 'index.ts')) || filePath.endsWith(path.join('core', 'presenter', 'index.js'))) {
|
|
170
|
-
for (const name of extractImportedPresenterFactories(content)) {
|
|
171
|
-
globalFactoryNames.add(name);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
169
|
}
|
|
175
170
|
|
|
176
171
|
const occurrences: PresenterOccurrence[] = [];
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
2
|
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
3
3
|
import checkInternal from './checkInternal.js';
|
|
4
|
-
|
|
5
4
|
const isPackageManager = (value) => typeof value === 'string' && ['pnpm', 'npm', 'yarn', 'bun'].includes(value);
|
|
6
|
-
|
|
7
5
|
const runScript = (manager, script) => {
|
|
8
6
|
const command = `${manager} run ${script}`;
|
|
9
7
|
console.log(`\n▶ Running: ${command}`);
|
|
10
8
|
execSync(command, { stdio: 'inherit' });
|
|
11
9
|
};
|
|
12
|
-
|
|
13
10
|
const runInternalChecks = async () => {
|
|
14
11
|
console.log('\n▶ Running: azure-net check internal');
|
|
15
12
|
process.exitCode = 0;
|
|
@@ -18,17 +15,14 @@ const runInternalChecks = async () => {
|
|
|
18
15
|
throw new Error('azure-net check internal failed');
|
|
19
16
|
}
|
|
20
17
|
};
|
|
21
|
-
|
|
22
18
|
export default async function checkProject() {
|
|
23
19
|
const config = await loadUserConfig();
|
|
24
20
|
const manager = config.packageManager;
|
|
25
|
-
|
|
26
21
|
if (!isPackageManager(manager)) {
|
|
27
22
|
console.error('❌ packageManager is missing or invalid in azure-net.config.ts/js. Use one of: bun, pnpm, npm, yarn');
|
|
28
23
|
process.exitCode = 1;
|
|
29
24
|
return;
|
|
30
25
|
}
|
|
31
|
-
|
|
32
26
|
try {
|
|
33
27
|
runScript(manager, 'format');
|
|
34
28
|
await runInternalChecks();
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
|
|
4
3
|
const TARGET_EXTENSIONS = new Set(['.ts', '.js']);
|
|
5
4
|
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.svelte-kit', '.git']);
|
|
6
|
-
|
|
7
5
|
const toPosixPath = (value) => value.split(path.sep).join('/');
|
|
8
|
-
|
|
9
6
|
const getLineByIndex = (content, index) => {
|
|
10
7
|
let line = 1;
|
|
11
8
|
for (let i = 0; i < index; i += 1) {
|
|
@@ -13,38 +10,31 @@ const getLineByIndex = (content, index) => {
|
|
|
13
10
|
}
|
|
14
11
|
return line;
|
|
15
12
|
};
|
|
16
|
-
|
|
17
13
|
const listSourceFiles = async (rootDir) => {
|
|
18
14
|
const files = [];
|
|
19
15
|
const stack = [rootDir];
|
|
20
|
-
|
|
21
16
|
while (stack.length) {
|
|
22
17
|
const current = stack.pop();
|
|
23
18
|
if (!current) continue;
|
|
24
|
-
|
|
25
19
|
let entries = [];
|
|
26
20
|
try {
|
|
27
21
|
entries = await fs.readdir(current, { withFileTypes: true });
|
|
28
22
|
} catch {
|
|
29
23
|
continue;
|
|
30
24
|
}
|
|
31
|
-
|
|
32
25
|
for (const entry of entries) {
|
|
33
26
|
if (entry.isDirectory()) {
|
|
34
27
|
if (!IGNORED_DIRS.has(entry.name)) stack.push(path.join(current, entry.name));
|
|
35
28
|
continue;
|
|
36
29
|
}
|
|
37
|
-
|
|
38
30
|
if (!entry.isFile()) continue;
|
|
39
31
|
const filePath = path.join(current, entry.name);
|
|
40
32
|
if (!TARGET_EXTENSIONS.has(path.extname(filePath))) continue;
|
|
41
33
|
files.push(filePath);
|
|
42
34
|
}
|
|
43
35
|
}
|
|
44
|
-
|
|
45
36
|
return files;
|
|
46
37
|
};
|
|
47
|
-
|
|
48
38
|
const findMatchingBracket = (content, startIndex, openChar, closeChar) => {
|
|
49
39
|
let depth = 0;
|
|
50
40
|
for (let i = startIndex; i < content.length; i += 1) {
|
|
@@ -57,11 +47,9 @@ const findMatchingBracket = (content, startIndex, openChar, closeChar) => {
|
|
|
57
47
|
}
|
|
58
48
|
return -1;
|
|
59
49
|
};
|
|
60
|
-
|
|
61
50
|
const extractDependsOn = (configBlock) => {
|
|
62
51
|
const dependsOnMatch = configBlock.match(/dependsOn\s*:\s*\{([\s\S]*?)\}/m);
|
|
63
52
|
if (!dependsOnMatch?.[1]) return [];
|
|
64
|
-
|
|
65
53
|
const block = dependsOnMatch[1];
|
|
66
54
|
const deps = new Set();
|
|
67
55
|
const keyRegex = /([A-Za-z_$][A-Za-z0-9_$]*)\s*:/g;
|
|
@@ -72,20 +60,16 @@ const extractDependsOn = (configBlock) => {
|
|
|
72
60
|
}
|
|
73
61
|
return [...deps];
|
|
74
62
|
};
|
|
75
|
-
|
|
76
63
|
const parseProviderGraphFromFile = (content, filePath) => {
|
|
77
64
|
const nodes = [];
|
|
78
65
|
const token = 'createBoundaryProvider(';
|
|
79
66
|
let fromIndex = 0;
|
|
80
|
-
|
|
81
67
|
while (fromIndex < content.length) {
|
|
82
68
|
const start = content.indexOf(token, fromIndex);
|
|
83
69
|
if (start === -1) break;
|
|
84
|
-
|
|
85
70
|
const openParenIndex = start + token.length - 1;
|
|
86
71
|
const closeParenIndex = findMatchingBracket(content, openParenIndex, '(', ')');
|
|
87
72
|
if (closeParenIndex === -1) break;
|
|
88
|
-
|
|
89
73
|
const fullCall = content.slice(openParenIndex + 1, closeParenIndex);
|
|
90
74
|
const idMatch = fullCall.match(/^\s*(['"`])([^'"`]+)\1\s*,/m);
|
|
91
75
|
const providerName = idMatch?.[2];
|
|
@@ -96,7 +80,6 @@ const parseProviderGraphFromFile = (content, filePath) => {
|
|
|
96
80
|
const absoluteObjectEnd = findMatchingBracket(content, absoluteObjectStart, '{', '}');
|
|
97
81
|
const configBlock =
|
|
98
82
|
absoluteObjectEnd !== -1 ? content.slice(absoluteObjectStart, absoluteObjectEnd + 1) : fullCall.slice(objectStartInCall);
|
|
99
|
-
|
|
100
83
|
nodes.push({
|
|
101
84
|
name: providerName,
|
|
102
85
|
file: filePath,
|
|
@@ -112,24 +95,19 @@ const parseProviderGraphFromFile = (content, filePath) => {
|
|
|
112
95
|
});
|
|
113
96
|
}
|
|
114
97
|
}
|
|
115
|
-
|
|
116
98
|
fromIndex = closeParenIndex + 1;
|
|
117
99
|
}
|
|
118
|
-
|
|
119
100
|
return nodes;
|
|
120
101
|
};
|
|
121
|
-
|
|
122
102
|
const findCycles = (graph) => {
|
|
123
103
|
const cycles = [];
|
|
124
104
|
const visited = new Set();
|
|
125
105
|
const inStack = new Set();
|
|
126
106
|
const stack = [];
|
|
127
|
-
|
|
128
107
|
const dfs = (node) => {
|
|
129
108
|
visited.add(node);
|
|
130
109
|
inStack.add(node);
|
|
131
110
|
stack.push(node);
|
|
132
|
-
|
|
133
111
|
const neighbors = graph.get(node) ?? [];
|
|
134
112
|
for (const next of neighbors) {
|
|
135
113
|
if (!graph.has(next)) continue;
|
|
@@ -142,18 +120,14 @@ const findCycles = (graph) => {
|
|
|
142
120
|
if (idx !== -1) cycles.push([...stack.slice(idx), next]);
|
|
143
121
|
}
|
|
144
122
|
}
|
|
145
|
-
|
|
146
123
|
stack.pop();
|
|
147
124
|
inStack.delete(node);
|
|
148
125
|
};
|
|
149
|
-
|
|
150
126
|
for (const node of graph.keys()) {
|
|
151
127
|
if (!visited.has(node)) dfs(node);
|
|
152
128
|
}
|
|
153
|
-
|
|
154
129
|
return cycles;
|
|
155
130
|
};
|
|
156
|
-
|
|
157
131
|
export default async function checkProviderGraph() {
|
|
158
132
|
const srcPath = path.join(process.cwd(), 'src');
|
|
159
133
|
try {
|
|
@@ -163,40 +137,32 @@ export default async function checkProviderGraph() {
|
|
|
163
137
|
process.exitCode = 1;
|
|
164
138
|
return;
|
|
165
139
|
}
|
|
166
|
-
|
|
167
140
|
const files = await listSourceFiles(srcPath);
|
|
168
141
|
const providers = [];
|
|
169
|
-
|
|
170
142
|
for (const filePath of files) {
|
|
171
143
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
172
144
|
providers.push(...parseProviderGraphFromFile(content, filePath));
|
|
173
145
|
}
|
|
174
|
-
|
|
175
146
|
if (!providers.length) {
|
|
176
147
|
console.log('✅ No createBoundaryProvider declarations found.');
|
|
177
148
|
return;
|
|
178
149
|
}
|
|
179
|
-
|
|
180
150
|
const graph = new Map();
|
|
181
151
|
const issues = [];
|
|
182
152
|
const byName = new Map();
|
|
183
|
-
|
|
184
153
|
for (const provider of providers) {
|
|
185
154
|
if (byName.has(provider.name)) {
|
|
186
155
|
const existing = byName.get(provider.name);
|
|
187
156
|
issues.push({
|
|
188
157
|
file: provider.file,
|
|
189
158
|
line: provider.line,
|
|
190
|
-
message: `Provider graph ambiguity: duplicate provider id "${provider.name}" (first declared at ${toPosixPath(
|
|
191
|
-
path.relative(process.cwd(), existing.file)
|
|
192
|
-
)}:${existing.line})`
|
|
159
|
+
message: `Provider graph ambiguity: duplicate provider id "${provider.name}" (first declared at ${toPosixPath(path.relative(process.cwd(), existing.file))}:${existing.line})`
|
|
193
160
|
});
|
|
194
161
|
continue;
|
|
195
162
|
}
|
|
196
163
|
byName.set(provider.name, provider);
|
|
197
164
|
graph.set(provider.name, provider.dependsOn);
|
|
198
165
|
}
|
|
199
|
-
|
|
200
166
|
const cycles = findCycles(graph);
|
|
201
167
|
for (const cycle of cycles) {
|
|
202
168
|
const firstNode = byName.get(cycle[0] ?? '');
|
|
@@ -206,16 +172,13 @@ export default async function checkProviderGraph() {
|
|
|
206
172
|
message: `Circular provider dependency detected: ${cycle.join(' -> ')}`
|
|
207
173
|
});
|
|
208
174
|
}
|
|
209
|
-
|
|
210
175
|
if (!issues.length) {
|
|
211
176
|
console.log(`✅ Provider graph check passed (${providers.length} providers).`);
|
|
212
177
|
return;
|
|
213
178
|
}
|
|
214
|
-
|
|
215
179
|
console.error('❌ Provider graph check failed:');
|
|
216
180
|
for (const issue of issues) {
|
|
217
181
|
console.error(`• ${toPosixPath(path.relative(process.cwd(), issue.file))}:${issue.line} — ${issue.message}`);
|
|
218
182
|
}
|
|
219
|
-
|
|
220
183
|
process.exitCode = 1;
|
|
221
184
|
}
|
|
@@ -1,47 +1,38 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
|
|
4
3
|
const TARGET_EXTENSIONS = new Set(['.ts', '.js', '.svelte']);
|
|
5
4
|
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.svelte-kit', '.git']);
|
|
6
5
|
const IMPORT_PATTERN = 'import\\s*\\{([^}]+)\\}\\s*from\\s*[\'"]([^\'"]+)[\'"]';
|
|
7
6
|
const CALL_PATTERN = '\\b([A-Za-z_$][A-Za-z0-9_$]*)\\s*\\(\\s*([\'"`])([^\'"`]*)\\2';
|
|
8
|
-
|
|
9
7
|
const toPosixPath = (value) => value.split(path.sep).join('/');
|
|
10
|
-
|
|
11
8
|
const parseImportAliases = (rawImports, importName) => {
|
|
12
9
|
const aliases = [];
|
|
13
10
|
for (const segment of rawImports.split(',')) {
|
|
14
11
|
const item = segment.trim();
|
|
15
12
|
if (!item.length) continue;
|
|
16
|
-
|
|
17
13
|
const aliasMatch = item.match(/^([A-Za-z_$][A-Za-z0-9_$]*)\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)$/);
|
|
18
14
|
if (aliasMatch?.[1] === importName && aliasMatch[2]) {
|
|
19
15
|
aliases.push(aliasMatch[2]);
|
|
20
16
|
continue;
|
|
21
17
|
}
|
|
22
|
-
|
|
23
18
|
if (item === importName) {
|
|
24
19
|
aliases.push(importName);
|
|
25
20
|
}
|
|
26
21
|
}
|
|
27
22
|
return aliases;
|
|
28
23
|
};
|
|
29
|
-
|
|
30
24
|
const listSourceFiles = async (rootDir) => {
|
|
31
25
|
const files = [];
|
|
32
26
|
const stack = [rootDir];
|
|
33
|
-
|
|
34
27
|
while (stack.length) {
|
|
35
28
|
const current = stack.pop();
|
|
36
29
|
if (!current) continue;
|
|
37
|
-
|
|
38
30
|
let entries = [];
|
|
39
31
|
try {
|
|
40
32
|
entries = await fs.readdir(current, { withFileTypes: true });
|
|
41
33
|
} catch {
|
|
42
34
|
continue;
|
|
43
35
|
}
|
|
44
|
-
|
|
45
36
|
for (const entry of entries) {
|
|
46
37
|
if (entry.isDirectory()) {
|
|
47
38
|
if (!IGNORED_DIRS.has(entry.name)) {
|
|
@@ -49,17 +40,14 @@ const listSourceFiles = async (rootDir) => {
|
|
|
49
40
|
}
|
|
50
41
|
continue;
|
|
51
42
|
}
|
|
52
|
-
|
|
53
43
|
if (!entry.isFile()) continue;
|
|
54
44
|
const filepath = path.join(current, entry.name);
|
|
55
45
|
if (!TARGET_EXTENSIONS.has(path.extname(filepath))) continue;
|
|
56
46
|
files.push(filepath);
|
|
57
47
|
}
|
|
58
48
|
}
|
|
59
|
-
|
|
60
49
|
return files;
|
|
61
50
|
};
|
|
62
|
-
|
|
63
51
|
const getLineByIndex = (content, index) => {
|
|
64
52
|
let line = 1;
|
|
65
53
|
for (let i = 0; i < index; i += 1) {
|
|
@@ -67,12 +55,10 @@ const getLineByIndex = (content, index) => {
|
|
|
67
55
|
}
|
|
68
56
|
return line;
|
|
69
57
|
};
|
|
70
|
-
|
|
71
58
|
const extractBoundaryFactoryAliases = (content) => {
|
|
72
59
|
const importRegex = new RegExp(IMPORT_PATTERN, 'g');
|
|
73
60
|
const aliases = new Set(['createBoundaryProvider']);
|
|
74
61
|
let match;
|
|
75
|
-
|
|
76
62
|
while ((match = importRegex.exec(content)) !== null) {
|
|
77
63
|
const rawImports = match[1];
|
|
78
64
|
if (!rawImports) continue;
|
|
@@ -80,25 +66,20 @@ const extractBoundaryFactoryAliases = (content) => {
|
|
|
80
66
|
aliases.add(alias);
|
|
81
67
|
}
|
|
82
68
|
}
|
|
83
|
-
|
|
84
69
|
return aliases;
|
|
85
70
|
};
|
|
86
|
-
|
|
87
71
|
const collectProviderOccurrences = (content, filepath) => {
|
|
88
72
|
const aliases = extractBoundaryFactoryAliases(content);
|
|
89
73
|
const callRegex = new RegExp(CALL_PATTERN, 'g');
|
|
90
74
|
const occurrences = [];
|
|
91
75
|
let match;
|
|
92
|
-
|
|
93
76
|
while ((match = callRegex.exec(content)) !== null) {
|
|
94
77
|
const callee = match[1];
|
|
95
78
|
const quote = match[2];
|
|
96
79
|
const providerId = match[3];
|
|
97
|
-
|
|
98
80
|
if (!callee || !providerId) continue;
|
|
99
81
|
if (!aliases.has(callee)) continue;
|
|
100
82
|
if (quote === '`' && providerId.includes('${')) continue;
|
|
101
|
-
|
|
102
83
|
occurrences.push({
|
|
103
84
|
id: providerId,
|
|
104
85
|
file: filepath,
|
|
@@ -106,10 +87,8 @@ const collectProviderOccurrences = (content, filepath) => {
|
|
|
106
87
|
callee
|
|
107
88
|
});
|
|
108
89
|
}
|
|
109
|
-
|
|
110
90
|
return occurrences;
|
|
111
91
|
};
|
|
112
|
-
|
|
113
92
|
export default async function checkProviderNames() {
|
|
114
93
|
const srcPath = path.join(process.cwd(), 'src');
|
|
115
94
|
try {
|
|
@@ -119,33 +98,27 @@ export default async function checkProviderNames() {
|
|
|
119
98
|
process.exitCode = 1;
|
|
120
99
|
return;
|
|
121
100
|
}
|
|
122
|
-
|
|
123
101
|
const files = await listSourceFiles(srcPath);
|
|
124
102
|
const occurrences = [];
|
|
125
|
-
|
|
126
103
|
for (const filepath of files) {
|
|
127
104
|
const content = await fs.readFile(filepath, 'utf-8');
|
|
128
105
|
occurrences.push(...collectProviderOccurrences(content, filepath));
|
|
129
106
|
}
|
|
130
|
-
|
|
131
107
|
if (!occurrences.length) {
|
|
132
108
|
console.log('✅ No providers declarations found.');
|
|
133
109
|
return;
|
|
134
110
|
}
|
|
135
|
-
|
|
136
111
|
const grouped = new Map();
|
|
137
112
|
for (const occurrence of occurrences) {
|
|
138
113
|
const list = grouped.get(occurrence.id) ?? [];
|
|
139
114
|
list.push(occurrence);
|
|
140
115
|
grouped.set(occurrence.id, list);
|
|
141
116
|
}
|
|
142
|
-
|
|
143
117
|
const duplicated = Array.from(grouped.entries()).filter(([, list]) => list.length > 1);
|
|
144
118
|
if (!duplicated.length) {
|
|
145
119
|
console.log(`✅ Provider names are unique (${occurrences.length} found).`);
|
|
146
120
|
return;
|
|
147
121
|
}
|
|
148
|
-
|
|
149
122
|
console.error('❌ Duplicate provider names found:');
|
|
150
123
|
for (const [providerId, list] of duplicated) {
|
|
151
124
|
console.error(`\n- "${providerId}"`);
|
|
@@ -153,6 +126,5 @@ export default async function checkProviderNames() {
|
|
|
153
126
|
console.error(` • ${toPosixPath(path.relative(process.cwd(), item.file))}:${item.line} (${item.callee})`);
|
|
154
127
|
}
|
|
155
128
|
}
|
|
156
|
-
|
|
157
129
|
process.exitCode = 1;
|
|
158
130
|
}
|
package/src/checks/index.js
CHANGED
|
@@ -4,8 +4,8 @@ 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';
|
|
10
11
|
export { default as checkProject } from './checkProject.js';
|
|
11
|
-
//# sourceMappingURL=index.js.map
|
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';
|