@azure-net/cli 5.0.0-3 → 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 +5 -46
- package/bin/azure-net.ts +5 -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 +0 -1
- package/src/checks/checkLayerBoundaries.js +50 -54
- package/src/checks/checkLayerBoundaries.ts +55 -58
- 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 +0 -1
- 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
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
4
4
|
import { normalizeContexts } from '../init/initAliases.js';
|
|
5
|
+
import { SHARED_ESSENTIALS_DIR_NAME } from '../utils/sharedFoundation.js';
|
|
5
6
|
const TARGET_EXTENSIONS = new Set(['.ts', '.js', '.svelte']);
|
|
6
7
|
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.svelte-kit', '.git']);
|
|
7
8
|
const IMPORT_FROM_PATTERN = /\bimport\s+(?:type\s+)?[\s\S]*?\sfrom\s*['"]([^'"]+)['"]/g;
|
|
@@ -83,10 +84,10 @@ const resolveContextFromRelativeImport = (filePath, importPath, contexts) => {
|
|
|
83
84
|
}
|
|
84
85
|
return null;
|
|
85
86
|
};
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return
|
|
87
|
+
const isInside = (targetPath, rootPath) => targetPath === rootPath || targetPath.startsWith(`${rootPath}${path.sep}`);
|
|
88
|
+
const resolveAbsoluteRelativeImport = (filePath, importPath) => {
|
|
89
|
+
if (!importPath.startsWith('.')) return null;
|
|
90
|
+
return path.resolve(path.dirname(filePath), importPath);
|
|
90
91
|
};
|
|
91
92
|
export default async function checkLayerBoundaries() {
|
|
92
93
|
const config = await loadUserConfig();
|
|
@@ -96,96 +97,92 @@ export default async function checkLayerBoundaries() {
|
|
|
96
97
|
process.exitCode = 1;
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
99
|
-
const coreAlias = normalizeAlias(config.coreAlias, '$core');
|
|
100
100
|
const sharedAliasRaw = normalizeAlias(config.sharedAlias, '');
|
|
101
101
|
const sharedAlias = sharedAliasRaw.length ? sharedAliasRaw : null;
|
|
102
|
+
if (!sharedAlias) {
|
|
103
|
+
console.error('❌ sharedAlias is missing in azure-net.config.ts/js. Set it explicitly to the shared context alias.');
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
102
107
|
const aliasToContext = new Map();
|
|
103
108
|
for (const context of contexts) {
|
|
104
109
|
aliasToContext.set(context.alias, context);
|
|
105
110
|
}
|
|
106
|
-
const knownAliases = [
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
const knownAliases = [...contexts.map((context) => context.alias)];
|
|
112
|
+
const sharedContext = aliasToContext.get(sharedAlias);
|
|
113
|
+
if (!sharedContext) {
|
|
114
|
+
console.error(`❌ sharedAlias "${sharedAlias}" does not point to any configured context.`);
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return;
|
|
109
117
|
}
|
|
110
118
|
const issues = [];
|
|
111
|
-
const coreRoot = path.join(process.cwd(), 'src', 'core');
|
|
112
|
-
try {
|
|
113
|
-
await fs.access(coreRoot);
|
|
114
|
-
const coreFiles = await listFiles(coreRoot);
|
|
115
|
-
for (const filePath of coreFiles) {
|
|
116
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
117
|
-
const imports = extractImports(content);
|
|
118
|
-
for (const entry of imports) {
|
|
119
|
-
const targetAlias = resolveAliasFromImport(entry.value, knownAliases);
|
|
120
|
-
if (targetAlias && targetAlias !== coreAlias) {
|
|
121
|
-
const targetContext = aliasToContext.get(targetAlias);
|
|
122
|
-
issues.push({
|
|
123
|
-
file: filePath,
|
|
124
|
-
line: entry.line,
|
|
125
|
-
message: targetContext
|
|
126
|
-
? `Layer boundary violation: core cannot import from context "${targetContext.name}" (${targetAlias})`
|
|
127
|
-
: `Layer boundary violation: core cannot import from "${targetAlias}"`
|
|
128
|
-
});
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
const relativeTargetContext = resolveContextFromRelativeImport(filePath, entry.value, contexts);
|
|
132
|
-
if (!relativeTargetContext) continue;
|
|
133
|
-
issues.push({
|
|
134
|
-
file: filePath,
|
|
135
|
-
line: entry.line,
|
|
136
|
-
message: `Layer boundary violation: core cannot import from context "${relativeTargetContext.name}" via relative path`
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
} catch {
|
|
141
|
-
// Core folder is optional for the check.
|
|
142
|
-
}
|
|
143
119
|
for (const context of contexts) {
|
|
144
120
|
const contextRoot = path.join(process.cwd(), 'src', 'app', context.name);
|
|
121
|
+
const isSharedContext = context.alias === sharedAlias;
|
|
122
|
+
const sharedEssentialsRoot = isSharedContext ? path.join(contextRoot, SHARED_ESSENTIALS_DIR_NAME) : null;
|
|
145
123
|
try {
|
|
146
124
|
await fs.access(contextRoot);
|
|
147
125
|
} catch {
|
|
148
126
|
continue;
|
|
149
127
|
}
|
|
150
128
|
const files = await listFiles(contextRoot);
|
|
151
|
-
const allowedAliases = new Set([context.alias
|
|
152
|
-
if (
|
|
129
|
+
const allowedAliases = new Set([context.alias]);
|
|
130
|
+
if (context.alias !== sharedAlias) {
|
|
153
131
|
allowedAliases.add(sharedAlias);
|
|
154
132
|
}
|
|
155
133
|
for (const filePath of files) {
|
|
156
134
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
157
135
|
const imports = extractImports(content);
|
|
136
|
+
const isSharedEssentialsFile = Boolean(sharedEssentialsRoot && isInside(filePath, sharedEssentialsRoot));
|
|
158
137
|
for (const entry of imports) {
|
|
159
138
|
const targetAlias = resolveAliasFromImport(entry.value, knownAliases);
|
|
160
139
|
if (!targetAlias) continue;
|
|
161
|
-
if (targetAlias ===
|
|
140
|
+
if (isSharedEssentialsFile && targetAlias === sharedAlias) {
|
|
141
|
+
const importRest = entry.value.slice(sharedAlias.length).replace(/^\/+/, '');
|
|
142
|
+
if (importRest.length && !importRest.startsWith(`${SHARED_ESSENTIALS_DIR_NAME}/`)) {
|
|
143
|
+
issues.push({
|
|
144
|
+
file: filePath,
|
|
145
|
+
line: entry.line,
|
|
146
|
+
message: `Layer boundary violation: shared essentials cannot import from shared "${importRest}". Essentials may depend only on shared essentials code.`
|
|
147
|
+
});
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
162
151
|
if (allowedAliases.has(targetAlias)) continue;
|
|
163
152
|
const targetContext = aliasToContext.get(targetAlias);
|
|
164
153
|
const baseMessage = targetContext
|
|
165
154
|
? `Layer boundary violation: context "${context.name}" cannot import from context "${targetContext.name}" (${targetAlias})`
|
|
166
155
|
: `Layer boundary violation: context "${context.name}" cannot import from "${targetAlias}"`;
|
|
167
|
-
const withSharedHint =
|
|
168
|
-
!sharedAlias && targetContext && isSharedLikeContext(targetContext.name, targetAlias)
|
|
169
|
-
? `${baseMessage}. sharedAlias is not configured in azure-net.config.ts/js; if this context is shared, set sharedAlias explicitly`
|
|
170
|
-
: baseMessage;
|
|
171
156
|
issues.push({
|
|
172
157
|
file: filePath,
|
|
173
158
|
line: entry.line,
|
|
174
|
-
message:
|
|
159
|
+
message: baseMessage
|
|
175
160
|
});
|
|
176
161
|
}
|
|
177
162
|
for (const entry of imports) {
|
|
163
|
+
const absoluteRelativeImport = resolveAbsoluteRelativeImport(filePath, entry.value);
|
|
164
|
+
if (
|
|
165
|
+
isSharedEssentialsFile &&
|
|
166
|
+
absoluteRelativeImport &&
|
|
167
|
+
sharedEssentialsRoot &&
|
|
168
|
+
isInside(absoluteRelativeImport, contextRoot) &&
|
|
169
|
+
!isInside(absoluteRelativeImport, sharedEssentialsRoot)
|
|
170
|
+
) {
|
|
171
|
+
issues.push({
|
|
172
|
+
file: filePath,
|
|
173
|
+
line: entry.line,
|
|
174
|
+
message: 'Layer boundary violation: shared essentials cannot import from shared layers/ui via relative path.'
|
|
175
|
+
});
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
178
|
const relativeTargetContext = resolveContextFromRelativeImport(filePath, entry.value, contexts);
|
|
179
179
|
if (!relativeTargetContext || relativeTargetContext.name === context.name) continue;
|
|
180
|
+
if (context.alias !== sharedAlias && relativeTargetContext.alias === sharedAlias) continue;
|
|
180
181
|
const baseMessage = `Layer boundary violation: context "${context.name}" cannot import from context "${relativeTargetContext.name}" via relative path`;
|
|
181
|
-
const withSharedHint =
|
|
182
|
-
!sharedAlias && isSharedLikeContext(relativeTargetContext.name, relativeTargetContext.alias)
|
|
183
|
-
? `${baseMessage}. sharedAlias is not configured in azure-net.config.ts/js; if this context is shared, set sharedAlias explicitly`
|
|
184
|
-
: baseMessage;
|
|
185
182
|
issues.push({
|
|
186
183
|
file: filePath,
|
|
187
184
|
line: entry.line,
|
|
188
|
-
message:
|
|
185
|
+
message: baseMessage
|
|
189
186
|
});
|
|
190
187
|
}
|
|
191
188
|
}
|
|
@@ -200,4 +197,3 @@ export default async function checkLayerBoundaries() {
|
|
|
200
197
|
}
|
|
201
198
|
process.exitCode = 1;
|
|
202
199
|
}
|
|
203
|
-
//# sourceMappingURL=checkLayerBoundaries.js.map
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { loadUserConfig } from '../utils/loadConfig.js';
|
|
4
4
|
import { normalizeContexts } from '../init/initAliases.js';
|
|
5
|
+
import { SHARED_ESSENTIALS_DIR_NAME } from '../utils/sharedFoundation.js';
|
|
5
6
|
|
|
6
7
|
type BoundaryIssue = {
|
|
7
8
|
file: string;
|
|
@@ -115,10 +116,12 @@ const resolveContextFromRelativeImport = (filePath: string, importPath: string,
|
|
|
115
116
|
return null;
|
|
116
117
|
};
|
|
117
118
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const isInside = (targetPath: string, rootPath: string): boolean =>
|
|
120
|
+
targetPath === rootPath || targetPath.startsWith(`${rootPath}${path.sep}`);
|
|
121
|
+
|
|
122
|
+
const resolveAbsoluteRelativeImport = (filePath: string, importPath: string): string | null => {
|
|
123
|
+
if (!importPath.startsWith('.')) return null;
|
|
124
|
+
return path.resolve(path.dirname(filePath), importPath);
|
|
122
125
|
};
|
|
123
126
|
|
|
124
127
|
export default async function checkLayerBoundaries(): Promise<void> {
|
|
@@ -131,60 +134,33 @@ export default async function checkLayerBoundaries(): Promise<void> {
|
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
const coreAlias = normalizeAlias(config.coreAlias, '$core');
|
|
135
137
|
const sharedAliasRaw = normalizeAlias(config.sharedAlias, '');
|
|
136
138
|
const sharedAlias = sharedAliasRaw.length ? sharedAliasRaw : null;
|
|
139
|
+
if (!sharedAlias) {
|
|
140
|
+
console.error('❌ sharedAlias is missing in azure-net.config.ts/js. Set it explicitly to the shared context alias.');
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
137
144
|
|
|
138
145
|
const aliasToContext = new Map<string, ContextConfig>();
|
|
139
146
|
for (const context of contexts) {
|
|
140
147
|
aliasToContext.set(context.alias, context);
|
|
141
148
|
}
|
|
142
149
|
|
|
143
|
-
const knownAliases = [
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
const knownAliases = [...contexts.map((context) => context.alias)];
|
|
151
|
+
const sharedContext = aliasToContext.get(sharedAlias);
|
|
152
|
+
if (!sharedContext) {
|
|
153
|
+
console.error(`❌ sharedAlias "${sharedAlias}" does not point to any configured context.`);
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
return;
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
const issues: BoundaryIssue[] = [];
|
|
149
159
|
|
|
150
|
-
const coreRoot = path.join(process.cwd(), 'src', 'core');
|
|
151
|
-
try {
|
|
152
|
-
await fs.access(coreRoot);
|
|
153
|
-
const coreFiles = await listFiles(coreRoot);
|
|
154
|
-
for (const filePath of coreFiles) {
|
|
155
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
156
|
-
const imports = extractImports(content);
|
|
157
|
-
|
|
158
|
-
for (const entry of imports) {
|
|
159
|
-
const targetAlias = resolveAliasFromImport(entry.value, knownAliases);
|
|
160
|
-
if (targetAlias && targetAlias !== coreAlias) {
|
|
161
|
-
const targetContext = aliasToContext.get(targetAlias);
|
|
162
|
-
issues.push({
|
|
163
|
-
file: filePath,
|
|
164
|
-
line: entry.line,
|
|
165
|
-
message: targetContext
|
|
166
|
-
? `Layer boundary violation: core cannot import from context "${targetContext.name}" (${targetAlias})`
|
|
167
|
-
: `Layer boundary violation: core cannot import from "${targetAlias}"`
|
|
168
|
-
});
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const relativeTargetContext = resolveContextFromRelativeImport(filePath, entry.value, contexts);
|
|
173
|
-
if (!relativeTargetContext) continue;
|
|
174
|
-
|
|
175
|
-
issues.push({
|
|
176
|
-
file: filePath,
|
|
177
|
-
line: entry.line,
|
|
178
|
-
message: `Layer boundary violation: core cannot import from context "${relativeTargetContext.name}" via relative path`
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
} catch {
|
|
183
|
-
// Core folder is optional for the check.
|
|
184
|
-
}
|
|
185
|
-
|
|
186
160
|
for (const context of contexts) {
|
|
187
161
|
const contextRoot = path.join(process.cwd(), 'src', 'app', context.name);
|
|
162
|
+
const isSharedContext = context.alias === sharedAlias;
|
|
163
|
+
const sharedEssentialsRoot = isSharedContext ? path.join(contextRoot, SHARED_ESSENTIALS_DIR_NAME) : null;
|
|
188
164
|
|
|
189
165
|
try {
|
|
190
166
|
await fs.access(contextRoot);
|
|
@@ -193,19 +169,32 @@ export default async function checkLayerBoundaries(): Promise<void> {
|
|
|
193
169
|
}
|
|
194
170
|
|
|
195
171
|
const files = await listFiles(contextRoot);
|
|
196
|
-
const allowedAliases = new Set<string>([context.alias
|
|
197
|
-
if (
|
|
172
|
+
const allowedAliases = new Set<string>([context.alias]);
|
|
173
|
+
if (context.alias !== sharedAlias) {
|
|
198
174
|
allowedAliases.add(sharedAlias);
|
|
199
175
|
}
|
|
200
176
|
|
|
201
177
|
for (const filePath of files) {
|
|
202
178
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
203
179
|
const imports = extractImports(content);
|
|
180
|
+
const isSharedEssentialsFile = Boolean(sharedEssentialsRoot && isInside(filePath, sharedEssentialsRoot));
|
|
204
181
|
|
|
205
182
|
for (const entry of imports) {
|
|
206
183
|
const targetAlias = resolveAliasFromImport(entry.value, knownAliases);
|
|
207
184
|
if (!targetAlias) continue;
|
|
208
|
-
|
|
185
|
+
|
|
186
|
+
if (isSharedEssentialsFile && targetAlias === sharedAlias) {
|
|
187
|
+
const importRest = entry.value.slice(sharedAlias.length).replace(/^\/+/, '');
|
|
188
|
+
if (importRest.length && !importRest.startsWith(`${SHARED_ESSENTIALS_DIR_NAME}/`)) {
|
|
189
|
+
issues.push({
|
|
190
|
+
file: filePath,
|
|
191
|
+
line: entry.line,
|
|
192
|
+
message: `Layer boundary violation: shared essentials cannot import from shared "${importRest}". Essentials may depend only on shared essentials code.`
|
|
193
|
+
});
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
209
198
|
if (allowedAliases.has(targetAlias)) continue;
|
|
210
199
|
|
|
211
200
|
const targetContext = aliasToContext.get(targetAlias);
|
|
@@ -213,32 +202,40 @@ export default async function checkLayerBoundaries(): Promise<void> {
|
|
|
213
202
|
? `Layer boundary violation: context "${context.name}" cannot import from context "${targetContext.name}" (${targetAlias})`
|
|
214
203
|
: `Layer boundary violation: context "${context.name}" cannot import from "${targetAlias}"`;
|
|
215
204
|
|
|
216
|
-
const withSharedHint =
|
|
217
|
-
!sharedAlias && targetContext && isSharedLikeContext(targetContext.name, targetAlias)
|
|
218
|
-
? `${baseMessage}. sharedAlias is not configured in azure-net.config.ts/js; if this context is shared, set sharedAlias explicitly`
|
|
219
|
-
: baseMessage;
|
|
220
|
-
|
|
221
205
|
issues.push({
|
|
222
206
|
file: filePath,
|
|
223
207
|
line: entry.line,
|
|
224
|
-
message:
|
|
208
|
+
message: baseMessage
|
|
225
209
|
});
|
|
226
210
|
}
|
|
227
211
|
|
|
228
212
|
for (const entry of imports) {
|
|
213
|
+
const absoluteRelativeImport = resolveAbsoluteRelativeImport(filePath, entry.value);
|
|
214
|
+
if (
|
|
215
|
+
isSharedEssentialsFile &&
|
|
216
|
+
absoluteRelativeImport &&
|
|
217
|
+
sharedEssentialsRoot &&
|
|
218
|
+
isInside(absoluteRelativeImport, contextRoot) &&
|
|
219
|
+
!isInside(absoluteRelativeImport, sharedEssentialsRoot)
|
|
220
|
+
) {
|
|
221
|
+
issues.push({
|
|
222
|
+
file: filePath,
|
|
223
|
+
line: entry.line,
|
|
224
|
+
message: 'Layer boundary violation: shared essentials cannot import from shared layers/ui via relative path.'
|
|
225
|
+
});
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
229
|
const relativeTargetContext = resolveContextFromRelativeImport(filePath, entry.value, contexts);
|
|
230
230
|
if (!relativeTargetContext || relativeTargetContext.name === context.name) continue;
|
|
231
|
+
if (context.alias !== sharedAlias && relativeTargetContext.alias === sharedAlias) continue;
|
|
231
232
|
|
|
232
233
|
const baseMessage = `Layer boundary violation: context "${context.name}" cannot import from context "${relativeTargetContext.name}" via relative path`;
|
|
233
|
-
const withSharedHint =
|
|
234
|
-
!sharedAlias && isSharedLikeContext(relativeTargetContext.name, relativeTargetContext.alias)
|
|
235
|
-
? `${baseMessage}. sharedAlias is not configured in azure-net.config.ts/js; if this context is shared, set sharedAlias explicitly`
|
|
236
|
-
: baseMessage;
|
|
237
234
|
|
|
238
235
|
issues.push({
|
|
239
236
|
file: filePath,
|
|
240
237
|
line: entry.line,
|
|
241
|
-
message:
|
|
238
|
+
message: baseMessage
|
|
242
239
|
});
|
|
243
240
|
}
|
|
244
241
|
}
|
|
@@ -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();
|