@azure-net/cli 5.1.0 → 6.0.0-next.2
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/README.md +189 -0
- package/bin/azure-net.js +236 -221
- package/dist/bin/azure-net.d.ts +2 -0
- package/dist/src/checks/architecture.d.ts +20 -0
- package/dist/src/checks/checkDomain.d.ts +1 -0
- package/dist/src/checks/checkFoldersStructure.d.ts +1 -0
- package/dist/src/checks/checkHooks.d.ts +1 -0
- package/dist/src/checks/checkImports.d.ts +1 -0
- package/dist/src/checks/checkInternal.d.ts +1 -0
- package/dist/src/checks/checkLayerBoundaries.d.ts +1 -0
- package/dist/src/checks/checkLayerImports.d.ts +1 -0
- package/dist/src/checks/checkPresenterNames.d.ts +1 -0
- package/dist/src/checks/checkProject.d.ts +1 -0
- package/dist/src/checks/checkProviderGraph.d.ts +1 -0
- package/dist/src/checks/checkProviderNames.d.ts +1 -0
- package/dist/src/generate/layers/generateRepository.d.ts +1 -0
- package/dist/src/generate/layers/generateRepositoryMethod.d.ts +1 -0
- package/dist/src/generate/layers/generateUseCases.d.ts +1 -0
- package/dist/src/generate/module/generateDomain.d.ts +8 -0
- package/dist/src/generate/module/generateModule.d.ts +1 -0
- package/dist/src/generate/module/generateModulePreset.d.ts +1 -0
- package/dist/src/generate/module/generatePresenter.d.ts +29 -0
- package/dist/src/generate/module/generateRepository.d.ts +15 -0
- package/dist/src/generate/module/generateRepositoryMethod.d.ts +2 -0
- package/dist/src/generate/module/generateSchemaFromType.d.ts +9 -0
- package/dist/src/generate/module/generateStore.d.ts +1 -0
- package/dist/src/generate/module/generateTypesJson.d.ts +2 -0
- package/dist/src/generate/module/generateUseCases.d.ts +1 -0
- package/dist/src/generate/module/repositoryModuleShared.d.ts +89 -0
- package/dist/src/generate/routes/generateRoutes.d.ts +14 -0
- package/dist/src/generate/ui/generateComponent.d.ts +1 -0
- package/dist/src/generate/ui/generateDesignComponent.d.ts +1 -0
- package/dist/src/generate/ui/generateWidget.d.ts +1 -0
- package/dist/src/git/commit.d.ts +23 -0
- package/dist/src/git/push.d.ts +11 -0
- package/dist/src/git/runGit.d.ts +2 -0
- package/dist/src/init/initAliases.d.ts +12 -0
- package/dist/src/init/initEdges.d.ts +2 -0
- package/dist/src/init/initStructure.d.ts +1 -0
- package/dist/src/install/configureCommitTools.d.ts +1 -0
- package/dist/src/install/installFresh.d.ts +1 -0
- package/dist/src/migrate/migrate.d.ts +22 -0
- package/dist/src/migrate/presenters.d.ts +7 -0
- package/dist/src/plugins/addLint.d.ts +5 -0
- package/dist/src/scaffolds/createCoreSchema.d.ts +1 -0
- package/dist/src/scaffolds/createDatasource.d.ts +1 -0
- package/dist/src/scaffolds/createProgram.d.ts +1 -0
- package/dist/src/scaffolds/createResponse.d.ts +1 -0
- package/dist/src/scaffolds/createSchemaRule.d.ts +1 -0
- package/dist/src/scaffolds/createTranslationManager.d.ts +1 -0
- package/{src/scaffolds/index.ts → dist/src/scaffolds/index.d.ts} +0 -2
- package/dist/src/scaffolds/registerDatasource.d.ts +2 -0
- package/dist/src/scaffolds/updateDefaultFactoryConfig.d.ts +1 -0
- package/dist/src/utils/architecture.d.ts +31 -0
- package/dist/src/utils/contextUtils.d.ts +6 -0
- package/dist/src/utils/fileUtils.d.ts +5 -0
- package/dist/src/utils/flattenConfig.d.ts +2 -0
- package/dist/src/utils/legacyAliases.d.ts +1 -0
- package/dist/src/utils/loadConfig.d.ts +16 -0
- package/dist/src/utils/packageImports.d.ts +4 -0
- package/dist/src/utils/sharedFoundation.d.ts +20 -0
- package/dist/src/utils/viteConfig.d.ts +9 -0
- package/package.json +48 -36
- package/src/checks/architecture.js +154 -0
- package/src/checks/checkDomain.js +124 -154
- package/src/checks/checkFoldersStructure.js +64 -52
- package/src/checks/checkHooks.js +12 -12
- package/src/checks/checkImports.js +26 -142
- package/src/checks/checkInternal.js +27 -24
- package/src/checks/checkLayerBoundaries.js +22 -197
- package/src/checks/checkLayerImports.js +14 -134
- package/src/checks/checkPresenterNames.js +156 -143
- package/src/checks/checkProject.js +27 -26
- package/src/checks/checkProviderGraph.js +182 -166
- package/src/checks/checkProviderNames.js +123 -112
- package/src/generate/layers/generateRepository.js +1 -109
- package/src/generate/layers/generateRepositoryMethod.js +1 -90
- package/src/generate/layers/generateUseCases.js +1 -44
- package/src/generate/module/generateDomain.js +81 -105
- package/src/generate/module/generateModule.js +34 -68
- package/src/generate/module/generateModulePreset.js +188 -248
- package/src/generate/module/generatePresenter.js +441 -369
- package/src/generate/module/generateRepository.js +102 -121
- package/src/generate/module/generateRepositoryMethod.js +117 -0
- package/src/generate/module/generateSchemaFromType.js +283 -280
- package/src/generate/module/generateStore.js +41 -0
- package/src/generate/module/generateTypesJson.js +196 -195
- package/src/generate/module/generateUseCases.js +47 -55
- package/src/generate/module/repositoryModuleShared.js +480 -459
- package/src/generate/routes/generateRoutes.js +165 -174
- package/src/generate/ui/generateComponent.js +44 -64
- package/src/generate/ui/generateDesignComponent.js +21 -41
- package/src/generate/ui/generateWidget.js +21 -41
- package/src/git/commit.js +67 -0
- package/src/git/push.js +19 -0
- package/src/git/runGit.js +11 -0
- package/src/init/initAliases.js +19 -135
- package/src/init/initEdges.js +79 -54
- package/src/init/initStructure.js +31 -145
- package/src/install/configureCommitTools.js +1 -0
- package/src/install/installFresh.js +39 -68
- package/src/migrate/migrate.js +914 -0
- package/src/migrate/presenters.js +265 -0
- package/src/plugins/addLint.js +52 -219
- package/src/scaffolds/createCoreSchema.js +65 -91
- package/src/scaffolds/createDatasource.js +67 -101
- package/src/scaffolds/createProgram.js +8 -8
- package/src/scaffolds/createResponse.js +29 -29
- package/src/scaffolds/createSchemaRule.js +19 -19
- package/src/scaffolds/createTranslationManager.js +84 -37
- package/src/scaffolds/index.js +0 -2
- package/src/scaffolds/registerDatasource.js +59 -0
- package/src/scaffolds/updateDefaultFactoryConfig.js +31 -0
- package/src/utils/architecture.js +145 -0
- package/src/utils/contextUtils.js +32 -65
- package/src/utils/fileUtils.js +69 -50
- package/src/utils/flattenConfig.js +38 -0
- package/src/utils/legacyAliases.js +75 -0
- package/src/utils/loadConfig.js +76 -40
- package/src/utils/packageImports.js +63 -0
- package/src/utils/sharedFoundation.js +36 -42
- package/src/utils/viteConfig.js +295 -0
- package/.prettierrc +0 -7
- package/bin/azure-net.ts +0 -308
- package/eslint.config.js +0 -34
- package/src/checks/checkDomain.ts +0 -223
- package/src/checks/checkFoldersStructure.ts +0 -85
- package/src/checks/checkHooks.ts +0 -22
- package/src/checks/checkImports.ts +0 -193
- package/src/checks/checkInternal.ts +0 -48
- package/src/checks/checkLayerBoundaries.ts +0 -255
- package/src/checks/checkLayerImports.ts +0 -185
- package/src/checks/checkPresenterNames.ts +0 -203
- package/src/checks/checkProject.ts +0 -45
- package/src/checks/checkProviderGraph.ts +0 -234
- package/src/checks/checkProviderNames.ts +0 -165
- package/src/generate/layers/generateRepository.ts +0 -123
- package/src/generate/layers/generateRepositoryMethod.ts +0 -102
- package/src/generate/layers/generateUseCases.ts +0 -50
- package/src/generate/layers/repositoryShared.js +0 -506
- package/src/generate/layers/repositoryShared.ts +0 -662
- package/src/generate/module/generateDomain.ts +0 -147
- package/src/generate/module/generateModule.ts +0 -84
- package/src/generate/module/generateModulePreset.ts +0 -338
- package/src/generate/module/generatePresenter.ts +0 -498
- package/src/generate/module/generateRepository.ts +0 -158
- package/src/generate/module/generateSchemaFromType.ts +0 -368
- package/src/generate/module/generateTypesJson.ts +0 -259
- package/src/generate/module/generateUseCases.ts +0 -67
- package/src/generate/module/repositoryModuleShared.ts +0 -669
- package/src/generate/routes/generateRoutes.ts +0 -282
- package/src/generate/ui/generateComponent.ts +0 -100
- package/src/generate/ui/generateDesignComponent.ts +0 -71
- package/src/generate/ui/generateWidget.ts +0 -71
- package/src/init/initAliases.ts +0 -172
- package/src/init/initEdges.ts +0 -74
- package/src/init/initStructure.ts +0 -172
- package/src/install/installFresh.ts +0 -93
- package/src/plugins/addLint.ts +0 -274
- package/src/scaffolds/createCoreSchema.ts +0 -130
- package/src/scaffolds/createDatasource.ts +0 -161
- package/src/scaffolds/createDatasourceProvider.js +0 -127
- package/src/scaffolds/createDatasourceProvider.ts +0 -161
- package/src/scaffolds/createPresenterFactory.js +0 -83
- package/src/scaffolds/createPresenterFactory.ts +0 -102
- package/src/scaffolds/createProgram.ts +0 -19
- package/src/scaffolds/createResponse.ts +0 -66
- package/src/scaffolds/createSchemaRule.ts +0 -44
- package/src/scaffolds/createTranslationManager.ts +0 -80
- package/src/utils/contextUtils.ts +0 -88
- package/src/utils/fileUtils.ts +0 -74
- package/src/utils/loadConfig.ts +0 -68
- package/src/utils/sharedFoundation.ts +0 -70
- package/tsconfig.json +0 -19
- /package/{src/checks/index.ts → dist/src/checks/index.d.ts} +0 -0
- /package/{src/plugins/index.ts → dist/src/plugins/index.d.ts} +0 -0
|
@@ -1,171 +1,141 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { appRoot, directories, exportOnly, parseSource } from './architecture.js';
|
|
5
5
|
const DOMAIN_FILE_EXTENSIONS = new Set(['.ts']);
|
|
6
|
-
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.svelte-kit', '.git']);
|
|
7
|
-
const INTERFACE_DECLARATION_PATTERN = /(?:^|\n)\s*(?:export\s+)?interface\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/g;
|
|
8
|
-
const TYPE_DECLARATION_PATTERN = /(?:^|\n)\s*(?:export\s+)?type\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=/g;
|
|
9
|
-
const EXPORT_STAR_PATTERN = /export\s+\*\s+from\s*['"][^'"]+['"]\s*;?/g;
|
|
10
|
-
const EXPORT_FROM_PATTERN = /export\s+(?:type\s+)?\{[\s\S]*?\}\s+from\s*['"][^'"]+['"]\s*;?/g;
|
|
11
|
-
const EXPORT_LOCAL_PATTERN = /export\s+(?:type\s+)?\{[\s\S]*?\}\s*;?/g;
|
|
6
|
+
const IGNORED_DIRS = new Set(['.backup', 'node_modules', 'dist', '.svelte-kit', '.git']);
|
|
12
7
|
const toPosixPath = (value) => value.split(path.sep).join('/');
|
|
13
|
-
const stripComments = (content) => content.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^\\])\/\/.*$/gm, '$1');
|
|
14
|
-
const stripExportStatements = (content) =>
|
|
15
|
-
content.replace(EXPORT_STAR_PATTERN, '').replace(EXPORT_FROM_PATTERN, '').replace(EXPORT_LOCAL_PATTERN, '');
|
|
16
|
-
const findLineByIndex = (content, index) => {
|
|
17
|
-
let line = 1;
|
|
18
|
-
for (let i = 0; i < index; i += 1) {
|
|
19
|
-
if (content[i] === '\n') line += 1;
|
|
20
|
-
}
|
|
21
|
-
return line;
|
|
22
|
-
};
|
|
23
8
|
const getRelativeFilePath = (filePath) => toPosixPath(path.relative(process.cwd(), filePath));
|
|
24
|
-
const isEnumsPath = (filePath) => {
|
|
25
|
-
const normalized = toPosixPath(filePath);
|
|
26
|
-
return normalized.includes('/enums/');
|
|
27
|
-
};
|
|
28
9
|
const isIndexFile = (filePath) => path.basename(filePath) === 'index.ts';
|
|
29
10
|
const startsWithInterfacePrefix = (filePath) => /^I[A-Z]/.test(path.basename(filePath, '.ts'));
|
|
30
|
-
const isExportOnlyIndex =
|
|
31
|
-
const withoutComments = stripComments(content);
|
|
32
|
-
const withoutExports = stripExportStatements(withoutComments);
|
|
33
|
-
return withoutExports.trim().length === 0;
|
|
34
|
-
};
|
|
11
|
+
const isExportOnlyIndex = exportOnly;
|
|
35
12
|
const validateName = (declaration) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
13
|
+
if (declaration.kind === 'interface') {
|
|
14
|
+
return /^I[A-Z]/.test(declaration.name) ? null : `Interface "${declaration.name}" must start with "I" and have uppercase second letter`;
|
|
15
|
+
}
|
|
16
|
+
return /^[IT][A-Z]/.test(declaration.name)
|
|
17
|
+
? null
|
|
18
|
+
: `Type "${declaration.name}" must start with "I" or "T" and have uppercase second letter`;
|
|
42
19
|
};
|
|
43
20
|
const extractDeclarations = (content) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const typeRegex = new RegExp(TYPE_DECLARATION_PATTERN.source, 'g');
|
|
58
|
-
let typeMatch;
|
|
59
|
-
while ((typeMatch = typeRegex.exec(cleaned)) !== null) {
|
|
60
|
-
const name = typeMatch[1];
|
|
61
|
-
if (!name) continue;
|
|
62
|
-
declarations.push({
|
|
63
|
-
kind: 'type',
|
|
64
|
-
name,
|
|
65
|
-
line: findLineByIndex(cleaned, typeMatch.index)
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return declarations.sort((a, b) => a.line - b.line);
|
|
21
|
+
const source = parseSource(content);
|
|
22
|
+
const declarations = [];
|
|
23
|
+
const visit = (node) => {
|
|
24
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node))
|
|
25
|
+
declarations.push({
|
|
26
|
+
kind: ts.isInterfaceDeclaration(node) ? 'interface' : 'type',
|
|
27
|
+
name: node.name.text,
|
|
28
|
+
line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1
|
|
29
|
+
});
|
|
30
|
+
ts.forEachChild(node, visit);
|
|
31
|
+
};
|
|
32
|
+
visit(source);
|
|
33
|
+
return declarations;
|
|
69
34
|
};
|
|
70
35
|
const listDomainFiles = async (domainRoot) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
36
|
+
const files = [];
|
|
37
|
+
const stack = [domainRoot];
|
|
38
|
+
while (stack.length) {
|
|
39
|
+
const current = stack.pop();
|
|
40
|
+
if (!current)
|
|
41
|
+
continue;
|
|
42
|
+
let entries = [];
|
|
43
|
+
try {
|
|
44
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
if (!IGNORED_DIRS.has(entry.name)) {
|
|
52
|
+
stack.push(path.join(current, entry.name));
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!entry.isFile())
|
|
57
|
+
continue;
|
|
58
|
+
const filePath = path.join(current, entry.name);
|
|
59
|
+
if (!DOMAIN_FILE_EXTENSIONS.has(path.extname(filePath)))
|
|
60
|
+
continue;
|
|
61
|
+
files.push(filePath);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return files;
|
|
96
65
|
};
|
|
97
66
|
const collectIssuesForFile = (filePath, content) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
return issues;
|
|
67
|
+
const issues = [];
|
|
68
|
+
if (isIndexFile(filePath) && !isExportOnlyIndex(content)) {
|
|
69
|
+
issues.push({
|
|
70
|
+
file: filePath,
|
|
71
|
+
line: 1,
|
|
72
|
+
message: 'index.ts must contain only exports. Move declarations to separate files'
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (!isIndexFile(filePath) && startsWithInterfacePrefix(filePath)) {
|
|
76
|
+
issues.push({
|
|
77
|
+
file: filePath,
|
|
78
|
+
line: 1,
|
|
79
|
+
message: 'Domain file names must not start with "I". Keep "I" only in interface/type names'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const declarations = extractDeclarations(content);
|
|
83
|
+
if (declarations.length > 5) {
|
|
84
|
+
issues.push({
|
|
85
|
+
file: filePath,
|
|
86
|
+
line: declarations[5]?.line ?? 1,
|
|
87
|
+
message: `Too many declarations in one file (${declarations.length}). Maximum is 5`
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const declaration of declarations) {
|
|
91
|
+
const validationError = validateName(declaration);
|
|
92
|
+
if (!validationError)
|
|
93
|
+
continue;
|
|
94
|
+
issues.push({
|
|
95
|
+
file: filePath,
|
|
96
|
+
line: declaration.line,
|
|
97
|
+
message: validationError
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return issues;
|
|
134
101
|
};
|
|
135
102
|
export default async function checkDomain() {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
103
|
+
const issues = [];
|
|
104
|
+
let contexts;
|
|
105
|
+
try {
|
|
106
|
+
contexts = await directories(path.join(appRoot(), 'Contexts'));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
console.error('Contexts folder not found in src/app.');
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const context of contexts) {
|
|
114
|
+
const domainRoot = path.join(appRoot(), 'Contexts', context, 'Domain');
|
|
115
|
+
try {
|
|
116
|
+
await fs.access(domainRoot);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
issues.push({
|
|
120
|
+
file: domainRoot,
|
|
121
|
+
line: 1,
|
|
122
|
+
message: `Domain folder not found for context "${context}"`
|
|
123
|
+
});
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const files = await listDomainFiles(domainRoot);
|
|
127
|
+
for (const filePath of files) {
|
|
128
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
129
|
+
issues.push(...collectIssuesForFile(filePath, content));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!issues.length) {
|
|
133
|
+
console.log('✅ Domain check passed: naming, per-file limits and index exports are valid.');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.error('❌ Domain check failed:');
|
|
137
|
+
for (const issue of issues) {
|
|
138
|
+
console.error(`• ${getRelativeFilePath(issue.file)}:${issue.line} — ${issue.message}`);
|
|
139
|
+
}
|
|
140
|
+
process.exitCode = 1;
|
|
171
141
|
}
|
|
@@ -1,56 +1,68 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import { normalizeContexts } from '../init/initAliases.js';
|
|
5
|
-
const ALLOWED_LAYERS_FOLDERS = new Set(['domain', 'presentation', 'infrastructure', 'application']);
|
|
6
|
-
const ALLOWED_UI_FOLDERS = new Set(['components', 'widgets', 'design', 'style']);
|
|
7
|
-
const ALLOWED_COMPONENTS_FOLDERS = new Set(['page', 'layout', 'shared']);
|
|
8
|
-
const toPosixPath = (value) => value.split(path.sep).join('/');
|
|
9
|
-
const getRelativePath = (filePath) => toPosixPath(path.relative(process.cwd(), filePath));
|
|
10
|
-
const getDirectories = async (dirPath) => {
|
|
11
|
-
try {
|
|
12
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
13
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
14
|
-
} catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
const checkOnlyAllowedFolders = async (basePath, allowed, scopeLabel, issues) => {
|
|
19
|
-
const directories = await getDirectories(basePath);
|
|
20
|
-
if (!directories) return;
|
|
21
|
-
for (const dir of directories) {
|
|
22
|
-
if (allowed.has(dir)) continue;
|
|
23
|
-
issues.push({
|
|
24
|
-
file: basePath,
|
|
25
|
-
message: `${scopeLabel} can contain only: ${Array.from(allowed).join(', ')}. Found: ${dir}`
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
};
|
|
3
|
+
import { appRoot, directories, report } from './architecture.js';
|
|
29
4
|
export default async function checkFoldersStructure() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
5
|
+
const issues = [];
|
|
6
|
+
const issue = (file, message) => {
|
|
7
|
+
issues.push({ file, line: 1, message });
|
|
8
|
+
};
|
|
9
|
+
const allowed = async (root, names) => {
|
|
10
|
+
for (const name of await directories(root))
|
|
11
|
+
if (!names.includes(name))
|
|
12
|
+
issue(path.join(root, name), `Allowed folders: ${names.join(', ')}`);
|
|
13
|
+
};
|
|
14
|
+
const walk = async (root) => {
|
|
15
|
+
for (const entry of await fs.readdir(root, { withFileTypes: true })) {
|
|
16
|
+
if (entry.name === '.backup' && root === appRoot())
|
|
17
|
+
continue;
|
|
18
|
+
const file = path.join(root, entry.name);
|
|
19
|
+
const routes = file === path.join(appRoot(), 'Presentation', 'routes');
|
|
20
|
+
const name = entry.isDirectory() ? entry.name : entry.name.split('.')[0];
|
|
21
|
+
if (!routes && !(entry.isFile() && entry.name === 'index.ts') && !/^[A-Z][A-Za-z0-9]*$/.test(name))
|
|
22
|
+
issue(file, 'Folders and files under src/app must use PascalCase (except index.ts)');
|
|
23
|
+
if (entry.isDirectory())
|
|
24
|
+
await walk(file);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
await allowed(appRoot(), ['Contexts', 'Presentation', 'Platform']);
|
|
29
|
+
for (const root of ['Contexts', 'Presentation', 'Platform']) {
|
|
30
|
+
if (!(await fs.stat(path.join(appRoot(), root)).catch(() => null))?.isDirectory())
|
|
31
|
+
issue(path.join(appRoot(), root), 'Required application root is missing');
|
|
32
|
+
}
|
|
33
|
+
for (const context of await directories(path.join(appRoot(), 'Contexts'))) {
|
|
34
|
+
const root = path.join(appRoot(), 'Contexts', context);
|
|
35
|
+
await allowed(root, ['Domain', 'Application', 'Infrastructure']);
|
|
36
|
+
for (const [layer, children] of Object.entries({
|
|
37
|
+
Domain: ['Entities', 'Ports', 'Enums'],
|
|
38
|
+
Application: ['UseCases'],
|
|
39
|
+
Infrastructure: ['Repositories']
|
|
40
|
+
})) {
|
|
41
|
+
const layerRoot = path.join(root, layer);
|
|
42
|
+
if (!(await fs.stat(layerRoot).catch(() => null))?.isDirectory()) {
|
|
43
|
+
issue(layerRoot, 'Required context layer is missing');
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
await allowed(layerRoot, children);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const presentation = path.join(appRoot(), 'Presentation');
|
|
50
|
+
const folders = await directories(presentation);
|
|
51
|
+
const flat = ['Ui', 'Scopes', 'Shell'];
|
|
52
|
+
for (const folder of folders) {
|
|
53
|
+
const canonical = [...flat, 'Shared'].find((name) => name.toLowerCase() === folder.toLowerCase());
|
|
54
|
+
if (canonical && canonical !== folder)
|
|
55
|
+
issue(path.join(presentation, folder), `Use the canonical folder name "${canonical}"`);
|
|
56
|
+
}
|
|
57
|
+
const workspaces = folders.filter((name) => ![...flat, 'routes', 'Routes'].includes(name));
|
|
58
|
+
if (folders.some((name) => flat.includes(name)) && workspaces.length)
|
|
59
|
+
issue(presentation, 'Presentation cannot mix flat Ui/Scopes/Shell folders with workspaces');
|
|
60
|
+
for (const workspace of workspaces)
|
|
61
|
+
await allowed(path.join(presentation, workspace), flat);
|
|
62
|
+
await walk(appRoot());
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
issue(appRoot(), String(error));
|
|
66
|
+
}
|
|
67
|
+
report('Folders structure', issues);
|
|
56
68
|
}
|
package/src/checks/checkHooks.js
CHANGED
|
@@ -2,16 +2,16 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
const forbiddenHooks = ['hooks.server.ts', 'hooks.server.js', 'hooks.client.ts', 'hooks.client.js'];
|
|
4
4
|
export default async function checkHooks() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
const srcPath = path.join(process.cwd(), 'src');
|
|
6
|
+
const found = forbiddenHooks.map((filename) => path.join(srcPath, filename)).filter((filepath) => fs.existsSync(filepath));
|
|
7
|
+
if (!found.length) {
|
|
8
|
+
console.log('✅ No manual SvelteKit hooks found.');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
console.error('❌ Manual SvelteKit hooks are not allowed with @azure-net/kit AzureNetPlugin.');
|
|
12
|
+
console.error('Move lifecycle code to src/program.ts through createApp(). Found:');
|
|
13
|
+
for (const filepath of found) {
|
|
14
|
+
console.error(` - ${path.relative(process.cwd(), filepath)}`);
|
|
15
|
+
}
|
|
16
|
+
process.exitCode = 1;
|
|
17
17
|
}
|
|
@@ -1,146 +1,30 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
const IGNORED_DIRS = new Set(['node_modules', 'dist', '.svelte-kit', '.git']);
|
|
5
|
-
const IMPORT_DECLARATION_PATTERN = /(^|\n)\s*import\s+(?:[\s\S]*?\s+from\s*)?['"]([^'"]+)['"]\s*;?/g;
|
|
6
|
-
const EXPORT_DECLARATION_PATTERN = /(^|\n)\s*export\s+(?:type\s+)?(?:[\s\S]*?\s+from\s*)['"]([^'"]+)['"]\s*;?/g;
|
|
7
|
-
const FORBIDDEN_IMPORT_EXTENSION_PATTERN = /\.(?:ts|js)(?:$|[?#])/;
|
|
8
|
-
const toPosixPath = (value) => value.split(path.sep).join('/');
|
|
9
|
-
const getRelativePath = (filePath) => toPosixPath(path.relative(process.cwd(), filePath));
|
|
10
|
-
const findLineByIndex = (content, index) => {
|
|
11
|
-
let line = 1;
|
|
12
|
-
for (let i = 0; i < index; i += 1) {
|
|
13
|
-
if (content[i] === '\n') line += 1;
|
|
14
|
-
}
|
|
15
|
-
return line;
|
|
16
|
-
};
|
|
17
|
-
const listFiles = async (rootDir) => {
|
|
18
|
-
const files = [];
|
|
19
|
-
const stack = [rootDir];
|
|
20
|
-
while (stack.length) {
|
|
21
|
-
const current = stack.pop();
|
|
22
|
-
if (!current) continue;
|
|
23
|
-
let entries = [];
|
|
24
|
-
try {
|
|
25
|
-
entries = await fs.readdir(current, { withFileTypes: true });
|
|
26
|
-
} catch {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
for (const entry of entries) {
|
|
30
|
-
if (entry.isDirectory()) {
|
|
31
|
-
if (!IGNORED_DIRS.has(entry.name)) {
|
|
32
|
-
stack.push(path.join(current, entry.name));
|
|
33
|
-
}
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (!entry.isFile()) continue;
|
|
37
|
-
const filePath = path.join(current, entry.name);
|
|
38
|
-
if (!TARGET_EXTENSIONS.has(path.extname(filePath))) continue;
|
|
39
|
-
files.push(filePath);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return files;
|
|
43
|
-
};
|
|
44
|
-
const getImportKind = (raw) => {
|
|
45
|
-
const normalized = raw.trim();
|
|
46
|
-
if (/^import\s+type\b/.test(normalized)) return 'type';
|
|
47
|
-
if (/^import\s*['"]/.test(normalized)) return 'side-effect';
|
|
48
|
-
return 'value';
|
|
49
|
-
};
|
|
50
|
-
const getKindOrder = (kind) => {
|
|
51
|
-
if (kind === 'value') return 0;
|
|
52
|
-
if (kind === 'type') return 1;
|
|
53
|
-
return 2;
|
|
54
|
-
};
|
|
55
|
-
const extractImports = (content) => {
|
|
56
|
-
const imports = [];
|
|
57
|
-
const regex = new RegExp(IMPORT_DECLARATION_PATTERN.source, 'g');
|
|
58
|
-
let match;
|
|
59
|
-
while ((match = regex.exec(content)) !== null) {
|
|
60
|
-
const raw = match[0].trim();
|
|
61
|
-
const source = match[2];
|
|
62
|
-
if (!source) continue;
|
|
63
|
-
imports.push({
|
|
64
|
-
kind: getImportKind(raw),
|
|
65
|
-
line: findLineByIndex(content, match.index + (match[1] === '\n' ? 1 : 0)),
|
|
66
|
-
source,
|
|
67
|
-
raw
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
return imports.sort((a, b) => a.line - b.line);
|
|
71
|
-
};
|
|
72
|
-
const extractExportSources = (content) => {
|
|
73
|
-
const exports = [];
|
|
74
|
-
const regex = new RegExp(EXPORT_DECLARATION_PATTERN.source, 'g');
|
|
75
|
-
let match;
|
|
76
|
-
while ((match = regex.exec(content)) !== null) {
|
|
77
|
-
const source = match[2];
|
|
78
|
-
if (!source) continue;
|
|
79
|
-
exports.push({
|
|
80
|
-
source,
|
|
81
|
-
line: findLineByIndex(content, match.index + (match[1] === '\n' ? 1 : 0))
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
return exports;
|
|
85
|
-
};
|
|
86
|
-
const hasForbiddenExtension = (source) => {
|
|
87
|
-
const sourceWithoutQuery = source.split('?')[0] ?? source;
|
|
88
|
-
return FORBIDDEN_IMPORT_EXTENSION_PATTERN.test(sourceWithoutQuery);
|
|
89
|
-
};
|
|
90
|
-
const collectIssuesForFile = (filePath, content) => {
|
|
91
|
-
const issues = [];
|
|
92
|
-
const imports = extractImports(content);
|
|
93
|
-
let lastKindOrder = 0;
|
|
94
|
-
for (const importEntry of imports) {
|
|
95
|
-
if (hasForbiddenExtension(importEntry.source)) {
|
|
96
|
-
issues.push({
|
|
97
|
-
file: filePath,
|
|
98
|
-
line: importEntry.line,
|
|
99
|
-
message: `Import "${importEntry.source}" must not include .ts/.js extension`
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
const currentOrder = getKindOrder(importEntry.kind);
|
|
103
|
-
if (currentOrder < lastKindOrder) {
|
|
104
|
-
issues.push({
|
|
105
|
-
file: filePath,
|
|
106
|
-
line: importEntry.line,
|
|
107
|
-
message: 'Import order violation: value imports first, then type imports, then side-effect imports'
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
lastKindOrder = Math.max(lastKindOrder, currentOrder);
|
|
111
|
-
}
|
|
112
|
-
for (const exportEntry of extractExportSources(content)) {
|
|
113
|
-
if (!hasForbiddenExtension(exportEntry.source)) continue;
|
|
114
|
-
issues.push({
|
|
115
|
-
file: filePath,
|
|
116
|
-
line: exportEntry.line,
|
|
117
|
-
message: `Export source "${exportEntry.source}" must not include .ts/.js extension`
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
return issues;
|
|
121
|
-
};
|
|
3
|
+
import { dependencies, sourceFiles, report } from './architecture.js';
|
|
122
4
|
export default async function checkImports() {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
5
|
+
const issues = [];
|
|
6
|
+
const src = path.join(process.cwd(), 'src');
|
|
7
|
+
try {
|
|
8
|
+
for (const file of await sourceFiles(src)) {
|
|
9
|
+
let previous = 0;
|
|
10
|
+
for (const entry of dependencies(await fs.readFile(file, 'utf8'), file)) {
|
|
11
|
+
if (/\.(?:ts|js)(?:$|[?#])/.test(entry.source))
|
|
12
|
+
issues.push({ file, line: entry.line, message: `Import "${entry.source}" must not include .ts/.js extension` });
|
|
13
|
+
if (['export', 'dynamic'].includes(entry.kind))
|
|
14
|
+
continue;
|
|
15
|
+
const order = entry.kind === 'type' ? 1 : entry.kind === 'side-effect' ? 2 : 0;
|
|
16
|
+
if (order < previous)
|
|
17
|
+
issues.push({
|
|
18
|
+
file,
|
|
19
|
+
line: entry.line,
|
|
20
|
+
message: 'Import order violation: value imports first, then type imports, then side-effect imports'
|
|
21
|
+
});
|
|
22
|
+
previous = Math.max(previous, order);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
issues.push({ file: src, line: 1, message: String(error) });
|
|
28
|
+
}
|
|
29
|
+
report('Imports', issues);
|
|
146
30
|
}
|