@viberails/config 0.3.3 → 0.5.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/dist/index.cjs +572 -330
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +317 -182
- package/dist/index.d.ts +317 -182
- package/dist/index.js +568 -330
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
// src/generate-config.ts
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
1
4
|
// src/defaults.ts
|
|
2
5
|
var DEFAULT_RULES = {
|
|
3
6
|
maxFileLines: 300,
|
|
4
7
|
maxTestFileLines: 0,
|
|
5
|
-
|
|
6
|
-
requireTests: true,
|
|
8
|
+
testCoverage: 80,
|
|
7
9
|
enforceNaming: true,
|
|
8
|
-
enforceBoundaries: false
|
|
10
|
+
enforceBoundaries: false,
|
|
11
|
+
enforceMissingTests: true
|
|
9
12
|
};
|
|
10
|
-
var DEFAULT_IGNORE = [
|
|
13
|
+
var DEFAULT_IGNORE = [];
|
|
14
|
+
var BUILTIN_IGNORE = [
|
|
11
15
|
"**/*.d.ts",
|
|
12
16
|
"**/*.min.js",
|
|
13
17
|
"**/*.min.cjs",
|
|
@@ -29,70 +33,68 @@ var DEFAULT_IGNORE = [
|
|
|
29
33
|
"**/__generated__/**"
|
|
30
34
|
];
|
|
31
35
|
|
|
32
|
-
// src/generate-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// src/generate-packages.ts
|
|
37
|
+
function buildPackageStack(pkg) {
|
|
38
|
+
const config = {
|
|
39
|
+
language: formatStackItem(pkg.stack.language),
|
|
40
|
+
packageManager: formatStackItem(pkg.stack.packageManager)
|
|
41
|
+
};
|
|
42
|
+
if (pkg.stack.framework) config.framework = formatStackItem(pkg.stack.framework);
|
|
43
|
+
if (pkg.stack.styling) config.styling = formatStackItem(pkg.stack.styling);
|
|
44
|
+
if (pkg.stack.backend) config.backend = formatStackItem(pkg.stack.backend);
|
|
45
|
+
if (pkg.stack.orm) config.orm = formatStackItem(pkg.stack.orm);
|
|
46
|
+
if (pkg.stack.linter) config.linter = formatStackItem(pkg.stack.linter);
|
|
47
|
+
if (pkg.stack.formatter) config.formatter = formatStackItem(pkg.stack.formatter);
|
|
48
|
+
if (pkg.stack.testRunner) config.testRunner = formatStackItem(pkg.stack.testRunner);
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
function buildPackageConventions(pkgConventions) {
|
|
52
|
+
const config = {};
|
|
39
53
|
for (const key of CONVENTION_KEYS) {
|
|
40
54
|
const detected = pkgConventions[key];
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
if (mapped === void 0) continue;
|
|
44
|
-
const globalValue = globalConventions[key];
|
|
45
|
-
const globalStr = typeof globalValue === "string" ? globalValue : globalValue?.value;
|
|
46
|
-
if (detected.value !== globalStr) {
|
|
47
|
-
overrides[key] = mapped;
|
|
48
|
-
hasDiff = true;
|
|
55
|
+
if (detected && detected.confidence !== "low") {
|
|
56
|
+
config[key] = detected.value;
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
|
-
return
|
|
59
|
+
return config;
|
|
52
60
|
}
|
|
53
|
-
function
|
|
61
|
+
function generatePackages(scanResult) {
|
|
54
62
|
if (!scanResult.packages || scanResult.packages.length <= 1) return void 0;
|
|
55
|
-
const
|
|
63
|
+
const packages = [];
|
|
56
64
|
for (const pkg of scanResult.packages) {
|
|
57
|
-
const
|
|
65
|
+
const pkgScanResult = {
|
|
66
|
+
root: pkg.root,
|
|
67
|
+
stack: pkg.stack,
|
|
68
|
+
structure: pkg.structure,
|
|
69
|
+
conventions: pkg.conventions,
|
|
70
|
+
statistics: pkg.statistics
|
|
71
|
+
};
|
|
72
|
+
const packageConfig = {
|
|
58
73
|
name: pkg.name,
|
|
59
|
-
path: pkg.relativePath
|
|
74
|
+
path: pkg.relativePath,
|
|
75
|
+
stack: buildPackageStack(pkg),
|
|
76
|
+
structure: mapStructure(pkgScanResult),
|
|
77
|
+
conventions: buildPackageConventions(pkg.conventions)
|
|
60
78
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
let hasStackDiff = false;
|
|
64
|
-
const optionalStackFields = [
|
|
65
|
-
"framework",
|
|
66
|
-
"styling",
|
|
67
|
-
"backend",
|
|
68
|
-
"orm",
|
|
69
|
-
"linter",
|
|
70
|
-
"formatter",
|
|
71
|
-
"testRunner"
|
|
72
|
-
];
|
|
73
|
-
for (const field of optionalStackFields) {
|
|
74
|
-
const pkgItem = pkg.stack[field];
|
|
75
|
-
if (!pkgItem) continue;
|
|
76
|
-
const pkgValue = formatStackItem(pkgItem);
|
|
77
|
-
if (pkgValue !== globalConfig.stack[field]) {
|
|
78
|
-
stackOverride[field] = pkgValue;
|
|
79
|
-
hasStackDiff = true;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (hasStackDiff) {
|
|
83
|
-
override.stack = stackOverride;
|
|
84
|
-
hasDiff = true;
|
|
85
|
-
}
|
|
86
|
-
const conventionOverrides = conventionsDiffer(pkg.conventions, globalConfig.conventions);
|
|
87
|
-
if (conventionOverrides) {
|
|
88
|
-
override.conventions = conventionOverrides;
|
|
89
|
-
hasDiff = true;
|
|
90
|
-
}
|
|
91
|
-
if (hasDiff) {
|
|
92
|
-
overrides.push(override);
|
|
79
|
+
if (pkg.typesOnly) {
|
|
80
|
+
packageConfig.rules = { testCoverage: 0 };
|
|
93
81
|
}
|
|
82
|
+
packages.push(packageConfig);
|
|
83
|
+
}
|
|
84
|
+
return packages.length > 0 ? packages : void 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/infer-coverage-command.ts
|
|
88
|
+
function inferCoverageCommand(testRunner) {
|
|
89
|
+
if (!testRunner) return void 0;
|
|
90
|
+
const runner = testRunner.split("@")[0];
|
|
91
|
+
if (runner === "vitest") {
|
|
92
|
+
return "npx vitest run --coverage --coverage.reporter=json-summary";
|
|
93
|
+
}
|
|
94
|
+
if (runner === "jest") {
|
|
95
|
+
return "npx jest --coverage --coverageReporters=json-summary";
|
|
94
96
|
}
|
|
95
|
-
return
|
|
97
|
+
return void 0;
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
// src/generate-config.ts
|
|
@@ -139,16 +141,6 @@ function mapStructure(scanResult) {
|
|
|
139
141
|
}
|
|
140
142
|
return config;
|
|
141
143
|
}
|
|
142
|
-
function mapConvention(convention) {
|
|
143
|
-
if (convention.confidence === "low") {
|
|
144
|
-
return void 0;
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
value: convention.value,
|
|
148
|
-
_confidence: convention.confidence,
|
|
149
|
-
_consistency: convention.consistency
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
144
|
var CONVENTION_KEYS = [
|
|
153
145
|
"fileNaming",
|
|
154
146
|
"componentNaming",
|
|
@@ -156,67 +148,308 @@ var CONVENTION_KEYS = [
|
|
|
156
148
|
"importAlias"
|
|
157
149
|
];
|
|
158
150
|
function mapConventions(scanResult) {
|
|
159
|
-
const
|
|
151
|
+
const conventions = {};
|
|
152
|
+
const meta = {};
|
|
160
153
|
for (const key of CONVENTION_KEYS) {
|
|
161
154
|
const detected = scanResult.conventions[key];
|
|
162
|
-
if (detected) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
155
|
+
if (detected && detected.confidence !== "low") {
|
|
156
|
+
conventions[key] = detected.value;
|
|
157
|
+
meta[key] = {
|
|
158
|
+
value: detected.value,
|
|
159
|
+
confidence: detected.confidence,
|
|
160
|
+
consistency: detected.consistency
|
|
161
|
+
};
|
|
167
162
|
}
|
|
168
163
|
}
|
|
169
|
-
return
|
|
164
|
+
return { conventions, meta };
|
|
165
|
+
}
|
|
166
|
+
function buildConventionMeta(conventions) {
|
|
167
|
+
const meta = {};
|
|
168
|
+
for (const key of CONVENTION_KEYS) {
|
|
169
|
+
const detected = conventions[key];
|
|
170
|
+
if (detected && detected.confidence !== "low") {
|
|
171
|
+
meta[key] = {
|
|
172
|
+
value: detected.value,
|
|
173
|
+
confidence: detected.confidence,
|
|
174
|
+
consistency: detected.consistency
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return meta;
|
|
170
179
|
}
|
|
171
180
|
function generateConfig(scanResult) {
|
|
181
|
+
const projectName = path.basename(scanResult.root);
|
|
182
|
+
const { conventions, meta } = mapConventions(scanResult);
|
|
183
|
+
const rootPackage = {
|
|
184
|
+
name: projectName,
|
|
185
|
+
path: ".",
|
|
186
|
+
stack: mapStack(scanResult),
|
|
187
|
+
structure: mapStructure(scanResult),
|
|
188
|
+
conventions
|
|
189
|
+
};
|
|
190
|
+
const _meta = {
|
|
191
|
+
lastSync: (/* @__PURE__ */ new Date()).toISOString(),
|
|
192
|
+
packages: {
|
|
193
|
+
".": { conventions: Object.keys(meta).length > 0 ? meta : void 0 }
|
|
194
|
+
}
|
|
195
|
+
};
|
|
172
196
|
const config = {
|
|
173
197
|
$schema: "https://viberails.sh/schema/v1.json",
|
|
174
198
|
version: 1,
|
|
175
|
-
name:
|
|
176
|
-
enforcement: "warn",
|
|
177
|
-
stack: mapStack(scanResult),
|
|
178
|
-
structure: mapStructure(scanResult),
|
|
179
|
-
conventions: mapConventions(scanResult),
|
|
199
|
+
name: projectName,
|
|
180
200
|
rules: { ...DEFAULT_RULES },
|
|
181
|
-
ignore: [...DEFAULT_IGNORE]
|
|
201
|
+
ignore: [...DEFAULT_IGNORE],
|
|
202
|
+
packages: [rootPackage],
|
|
203
|
+
_meta
|
|
182
204
|
};
|
|
205
|
+
const testRunner = scanResult.stack.testRunner;
|
|
206
|
+
const coverageCommand = inferCoverageCommand(
|
|
207
|
+
testRunner ? formatStackItem(testRunner) : void 0
|
|
208
|
+
);
|
|
209
|
+
if (coverageCommand) {
|
|
210
|
+
config.defaults = { coverage: { command: coverageCommand } };
|
|
211
|
+
}
|
|
183
212
|
if (scanResult.workspace) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
213
|
+
const packages = generatePackages(scanResult);
|
|
214
|
+
if (packages) {
|
|
215
|
+
config.packages = packages;
|
|
216
|
+
const pkgMeta = {};
|
|
217
|
+
for (const pkg of scanResult.packages) {
|
|
218
|
+
const convMeta = buildConventionMeta(pkg.conventions);
|
|
219
|
+
if (Object.keys(convMeta).length > 0) {
|
|
220
|
+
pkgMeta[pkg.relativePath] = { conventions: convMeta };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (Object.keys(pkgMeta).length > 0) {
|
|
224
|
+
_meta.packages = pkgMeta;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
188
227
|
config.boundaries = { deny: {} };
|
|
189
228
|
}
|
|
190
|
-
const packageOverrides = generatePackageOverrides(scanResult, config);
|
|
191
|
-
if (packageOverrides) {
|
|
192
|
-
config.packages = packageOverrides;
|
|
193
|
-
}
|
|
194
229
|
return config;
|
|
195
230
|
}
|
|
196
231
|
|
|
232
|
+
// src/compact-config.ts
|
|
233
|
+
var STACK_KEYS = [
|
|
234
|
+
"language",
|
|
235
|
+
"packageManager",
|
|
236
|
+
"framework",
|
|
237
|
+
"styling",
|
|
238
|
+
"backend",
|
|
239
|
+
"orm",
|
|
240
|
+
"linter",
|
|
241
|
+
"formatter",
|
|
242
|
+
"testRunner"
|
|
243
|
+
];
|
|
244
|
+
var STRUCTURE_KEYS = [
|
|
245
|
+
"srcDir",
|
|
246
|
+
"pages",
|
|
247
|
+
"components",
|
|
248
|
+
"hooks",
|
|
249
|
+
"utils",
|
|
250
|
+
"types",
|
|
251
|
+
"tests",
|
|
252
|
+
"testPattern"
|
|
253
|
+
];
|
|
254
|
+
function compactConfig(config) {
|
|
255
|
+
const pkgs = config.packages ?? [];
|
|
256
|
+
const defaults = {};
|
|
257
|
+
const packages = pkgs.map((p) => {
|
|
258
|
+
const copy = { ...p };
|
|
259
|
+
if (config.defaults?.coverage) {
|
|
260
|
+
copy.coverage = { ...config.defaults.coverage, ...copy.coverage ?? {} };
|
|
261
|
+
}
|
|
262
|
+
if (config.defaults?.stack) {
|
|
263
|
+
copy.stack = { ...config.defaults.stack, ...copy.stack ?? {} };
|
|
264
|
+
}
|
|
265
|
+
if (config.defaults?.structure) {
|
|
266
|
+
copy.structure = { ...config.defaults.structure, ...copy.structure ?? {} };
|
|
267
|
+
}
|
|
268
|
+
if (config.defaults?.conventions) {
|
|
269
|
+
copy.conventions = { ...config.defaults.conventions, ...copy.conventions ?? {} };
|
|
270
|
+
}
|
|
271
|
+
return copy;
|
|
272
|
+
});
|
|
273
|
+
if (packages.length <= 1) {
|
|
274
|
+
for (const pkg of packages) {
|
|
275
|
+
if (pkg.stack && Object.keys(pkg.stack).length === 0) delete pkg.stack;
|
|
276
|
+
if (pkg.structure && Object.keys(pkg.structure).length === 0) delete pkg.structure;
|
|
277
|
+
if (pkg.conventions && Object.keys(pkg.conventions).length === 0) delete pkg.conventions;
|
|
278
|
+
if (pkg.coverage && Object.keys(pkg.coverage).length === 0) delete pkg.coverage;
|
|
279
|
+
}
|
|
280
|
+
const { defaults: _d, ...rest } = config;
|
|
281
|
+
return { ...rest, packages };
|
|
282
|
+
}
|
|
283
|
+
const sharedStack = {};
|
|
284
|
+
for (const key of STACK_KEYS) {
|
|
285
|
+
const values = packages.map((p) => (p.stack ?? {})[key]);
|
|
286
|
+
if (values[0] !== void 0 && values.every((v) => v === values[0])) {
|
|
287
|
+
sharedStack[key] = values[0];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (Object.keys(sharedStack).length > 0) {
|
|
291
|
+
defaults.stack = sharedStack;
|
|
292
|
+
for (const pkg of packages) {
|
|
293
|
+
const pkgStack = pkg.stack ?? {};
|
|
294
|
+
const sparse = {};
|
|
295
|
+
for (const key of STACK_KEYS) {
|
|
296
|
+
if (pkgStack[key] !== void 0 && pkgStack[key] !== sharedStack[key]) {
|
|
297
|
+
sparse[key] = pkgStack[key];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
pkg.stack = sparse;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const sharedStructure = {};
|
|
304
|
+
for (const key of STRUCTURE_KEYS) {
|
|
305
|
+
const values = packages.map((p) => p.structure?.[key]);
|
|
306
|
+
const first = values[0];
|
|
307
|
+
if (first !== void 0 && values.every((v) => JSON.stringify(v) === JSON.stringify(first))) {
|
|
308
|
+
sharedStructure[key] = first;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (Object.keys(sharedStructure).length > 0) {
|
|
312
|
+
defaults.structure = sharedStructure;
|
|
313
|
+
for (const pkg of packages) {
|
|
314
|
+
const pkgStructure = pkg.structure ?? {};
|
|
315
|
+
const sparse = {};
|
|
316
|
+
for (const key of STRUCTURE_KEYS) {
|
|
317
|
+
const val = pkgStructure[key];
|
|
318
|
+
if (val !== void 0 && JSON.stringify(val) !== JSON.stringify(sharedStructure[key])) {
|
|
319
|
+
sparse[key] = val;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
pkg.structure = sparse;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const sharedConventions = {};
|
|
326
|
+
for (const key of CONVENTION_KEYS) {
|
|
327
|
+
const values = packages.map((p) => p.conventions?.[key]);
|
|
328
|
+
if (values[0] !== void 0 && values.every((v) => v === values[0])) {
|
|
329
|
+
sharedConventions[key] = values[0];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const sharedCoverage = {};
|
|
333
|
+
const coverageKeys = ["command", "summaryPath"];
|
|
334
|
+
for (const key of coverageKeys) {
|
|
335
|
+
const values = packages.map((p) => p.coverage?.[key]);
|
|
336
|
+
if (values[0] !== void 0 && values.every((v) => v === values[0])) {
|
|
337
|
+
sharedCoverage[key] = values[0];
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (Object.keys(sharedCoverage).length > 0) {
|
|
341
|
+
defaults.coverage = sharedCoverage;
|
|
342
|
+
for (const pkg of packages) {
|
|
343
|
+
const pkgCoverage = pkg.coverage ?? {};
|
|
344
|
+
const sparse = {};
|
|
345
|
+
for (const key of coverageKeys) {
|
|
346
|
+
if (pkgCoverage[key] !== void 0 && pkgCoverage[key] !== sharedCoverage[key]) {
|
|
347
|
+
sparse[key] = pkgCoverage[key];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
pkg.coverage = sparse;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (Object.keys(sharedConventions).length > 0) {
|
|
354
|
+
defaults.conventions = sharedConventions;
|
|
355
|
+
for (const pkg of packages) {
|
|
356
|
+
const pkgConventions = pkg.conventions ?? {};
|
|
357
|
+
const sparse = {};
|
|
358
|
+
for (const key of CONVENTION_KEYS) {
|
|
359
|
+
if (pkgConventions[key] !== void 0 && pkgConventions[key] !== sharedConventions[key]) {
|
|
360
|
+
sparse[key] = pkgConventions[key];
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
pkg.conventions = sparse;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const pkg of packages) {
|
|
367
|
+
if (pkg.stack && Object.keys(pkg.stack).length === 0) {
|
|
368
|
+
delete pkg.stack;
|
|
369
|
+
}
|
|
370
|
+
if (pkg.structure && Object.keys(pkg.structure).length === 0) {
|
|
371
|
+
delete pkg.structure;
|
|
372
|
+
}
|
|
373
|
+
if (pkg.conventions && Object.keys(pkg.conventions).length === 0) {
|
|
374
|
+
delete pkg.conventions;
|
|
375
|
+
}
|
|
376
|
+
if (pkg.coverage && Object.keys(pkg.coverage).length === 0) {
|
|
377
|
+
delete pkg.coverage;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const result = {
|
|
381
|
+
...config.$schema ? { $schema: config.$schema } : {},
|
|
382
|
+
version: config.version,
|
|
383
|
+
name: config.name,
|
|
384
|
+
rules: config.rules,
|
|
385
|
+
...config.ignore && config.ignore.length > 0 ? { ignore: config.ignore } : {},
|
|
386
|
+
...config.boundaries ? { boundaries: config.boundaries } : {},
|
|
387
|
+
...Object.keys(defaults).length > 0 ? { defaults } : {},
|
|
388
|
+
packages,
|
|
389
|
+
...config._meta ? { _meta: config._meta } : {}
|
|
390
|
+
};
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
function expandDefaults(config) {
|
|
394
|
+
const defaults = config.defaults;
|
|
395
|
+
const packages = config.packages.map((pkg) => {
|
|
396
|
+
const expanded = { ...pkg };
|
|
397
|
+
expanded.stack = { ...defaults?.stack ?? {}, ...pkg.stack ?? {} };
|
|
398
|
+
expanded.structure = { ...defaults?.structure ?? {}, ...pkg.structure ?? {} };
|
|
399
|
+
expanded.conventions = { ...defaults?.conventions ?? {}, ...pkg.conventions ?? {} };
|
|
400
|
+
const mergedCoverage = { ...defaults?.coverage ?? {}, ...pkg.coverage ?? {} };
|
|
401
|
+
if (Object.keys(mergedCoverage).length > 0) {
|
|
402
|
+
expanded.coverage = mergedCoverage;
|
|
403
|
+
} else {
|
|
404
|
+
delete expanded.coverage;
|
|
405
|
+
}
|
|
406
|
+
return expanded;
|
|
407
|
+
});
|
|
408
|
+
const ignore = config.ignore ?? [];
|
|
409
|
+
const { defaults: _d, ...rest } = config;
|
|
410
|
+
return { ...rest, ignore, packages };
|
|
411
|
+
}
|
|
412
|
+
|
|
197
413
|
// src/load-config.ts
|
|
198
414
|
import * as fs from "fs/promises";
|
|
199
415
|
function validateConfig(parsed, configPath) {
|
|
200
416
|
const errors = [];
|
|
201
|
-
const required = ["version", "name", "
|
|
417
|
+
const required = ["version", "name", "packages", "rules"];
|
|
202
418
|
const missing = required.filter((field) => parsed[field] === void 0);
|
|
203
419
|
if (missing.length > 0) {
|
|
204
420
|
throw new Error(
|
|
205
421
|
`Invalid viberails config at ${configPath}: missing required field(s): ${missing.join(", ")}`
|
|
206
422
|
);
|
|
207
423
|
}
|
|
208
|
-
if (typeof parsed.version !== "number")
|
|
209
|
-
|
|
210
|
-
if (parsed.
|
|
211
|
-
errors.push('"
|
|
424
|
+
if (typeof parsed.version !== "number") {
|
|
425
|
+
errors.push('"version" must be a number');
|
|
426
|
+
} else if (parsed.version !== 1) {
|
|
427
|
+
errors.push('"version" must be 1');
|
|
212
428
|
}
|
|
213
|
-
if (typeof parsed.
|
|
214
|
-
|
|
429
|
+
if (typeof parsed.name !== "string") errors.push('"name" must be a string');
|
|
430
|
+
if (!Array.isArray(parsed.packages)) {
|
|
431
|
+
errors.push('"packages" must be an array');
|
|
432
|
+
} else if (parsed.packages.length === 0) {
|
|
433
|
+
errors.push('"packages" must contain at least one package');
|
|
215
434
|
} else {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
errors.push(
|
|
435
|
+
for (let i = 0; i < parsed.packages.length; i++) {
|
|
436
|
+
const pkg = parsed.packages[i];
|
|
437
|
+
if (typeof pkg.name !== "string") errors.push(`"packages[${i}].name" must be a string`);
|
|
438
|
+
if (typeof pkg.path !== "string") errors.push(`"packages[${i}].path" must be a string`);
|
|
439
|
+
if (pkg.coverage !== void 0) {
|
|
440
|
+
if (typeof pkg.coverage !== "object" || pkg.coverage === null) {
|
|
441
|
+
errors.push(`"packages[${i}].coverage" must be an object`);
|
|
442
|
+
} else {
|
|
443
|
+
const coverage = pkg.coverage;
|
|
444
|
+
if (coverage.command !== void 0 && typeof coverage.command !== "string") {
|
|
445
|
+
errors.push(`"packages[${i}].coverage.command" must be a string`);
|
|
446
|
+
}
|
|
447
|
+
if (coverage.summaryPath !== void 0 && typeof coverage.summaryPath !== "string") {
|
|
448
|
+
errors.push(`"packages[${i}].coverage.summaryPath" must be a string`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
220
453
|
}
|
|
221
454
|
if (typeof parsed.rules !== "object" || parsed.rules === null) {
|
|
222
455
|
errors.push('"rules" must be an object');
|
|
@@ -224,10 +457,18 @@ function validateConfig(parsed, configPath) {
|
|
|
224
457
|
const rules = parsed.rules;
|
|
225
458
|
if (typeof rules.maxFileLines !== "number")
|
|
226
459
|
errors.push('"rules.maxFileLines" must be a number');
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
460
|
+
else if (rules.maxFileLines < 0) errors.push('"rules.maxFileLines" must be >= 0');
|
|
461
|
+
if (rules.maxTestFileLines !== void 0) {
|
|
462
|
+
if (typeof rules.maxTestFileLines !== "number") {
|
|
463
|
+
errors.push('"rules.maxTestFileLines" must be a number');
|
|
464
|
+
} else if (rules.maxTestFileLines < 0) {
|
|
465
|
+
errors.push('"rules.maxTestFileLines" must be >= 0');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (typeof rules.testCoverage !== "number")
|
|
469
|
+
errors.push('"rules.testCoverage" must be a number');
|
|
470
|
+
else if (rules.testCoverage < 0 || rules.testCoverage > 100)
|
|
471
|
+
errors.push('"rules.testCoverage" must be between 0 and 100');
|
|
231
472
|
if (typeof rules.enforceNaming !== "boolean")
|
|
232
473
|
errors.push('"rules.enforceNaming" must be a boolean');
|
|
233
474
|
if (typeof rules.enforceBoundaries !== "boolean")
|
|
@@ -236,6 +477,26 @@ function validateConfig(parsed, configPath) {
|
|
|
236
477
|
if (parsed.ignore !== void 0 && !Array.isArray(parsed.ignore)) {
|
|
237
478
|
errors.push('"ignore" must be an array');
|
|
238
479
|
}
|
|
480
|
+
if (parsed.defaults !== void 0) {
|
|
481
|
+
if (typeof parsed.defaults !== "object" || parsed.defaults === null) {
|
|
482
|
+
errors.push('"defaults" must be an object');
|
|
483
|
+
} else {
|
|
484
|
+
const defaults = parsed.defaults;
|
|
485
|
+
if (defaults.coverage !== void 0) {
|
|
486
|
+
if (typeof defaults.coverage !== "object" || defaults.coverage === null) {
|
|
487
|
+
errors.push('"defaults.coverage" must be an object');
|
|
488
|
+
} else {
|
|
489
|
+
const coverage = defaults.coverage;
|
|
490
|
+
if (coverage.command !== void 0 && typeof coverage.command !== "string") {
|
|
491
|
+
errors.push('"defaults.coverage.command" must be a string');
|
|
492
|
+
}
|
|
493
|
+
if (coverage.summaryPath !== void 0 && typeof coverage.summaryPath !== "string") {
|
|
494
|
+
errors.push('"defaults.coverage.summaryPath" must be a string');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
239
500
|
if (errors.length > 0) {
|
|
240
501
|
throw new Error(`Invalid viberails config at ${configPath}: ${errors.join("; ")}`);
|
|
241
502
|
}
|
|
@@ -262,7 +523,15 @@ async function loadConfig(configPath) {
|
|
|
262
523
|
if (rules.maxTestFileLines === void 0) {
|
|
263
524
|
rules.maxTestFileLines = 0;
|
|
264
525
|
}
|
|
265
|
-
|
|
526
|
+
if (rules.enforceMissingTests === void 0) {
|
|
527
|
+
rules.enforceMissingTests = true;
|
|
528
|
+
}
|
|
529
|
+
if (parsed.ignore === void 0) {
|
|
530
|
+
parsed.ignore = [];
|
|
531
|
+
}
|
|
532
|
+
let config = parsed;
|
|
533
|
+
config = expandDefaults(config);
|
|
534
|
+
return config;
|
|
266
535
|
}
|
|
267
536
|
async function loadConfigSafe(configPath) {
|
|
268
537
|
try {
|
|
@@ -298,43 +567,79 @@ function mergeStructure(existing, fresh) {
|
|
|
298
567
|
testPattern: existing.testPattern ?? fresh.testPattern
|
|
299
568
|
};
|
|
300
569
|
}
|
|
301
|
-
function
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
function markAsDetected(value) {
|
|
305
|
-
if (typeof value === "string") {
|
|
306
|
-
return { value, _confidence: "high", _consistency: 100, _detected: true };
|
|
307
|
-
}
|
|
308
|
-
return { ...value, _detected: true };
|
|
309
|
-
}
|
|
310
|
-
function mergeConventions(existing, fresh) {
|
|
311
|
-
const merged = { ...existing };
|
|
570
|
+
function mergeConventions(existing, fresh, existingMeta, freshMeta) {
|
|
571
|
+
const conventions = { ...existing };
|
|
572
|
+
const meta = { ...existingMeta ?? {} };
|
|
312
573
|
for (const key of CONVENTION_KEYS) {
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
574
|
+
if (existing[key] === void 0 && fresh[key] !== void 0) {
|
|
575
|
+
conventions[key] = fresh[key];
|
|
576
|
+
if (freshMeta?.[key]) {
|
|
577
|
+
meta[key] = { ...freshMeta[key], detected: true };
|
|
317
578
|
}
|
|
579
|
+
} else if (existing[key] !== void 0 && freshMeta?.[key]) {
|
|
580
|
+
meta[key] = { ...freshMeta[key], value: freshMeta[key].value };
|
|
318
581
|
}
|
|
319
582
|
}
|
|
320
|
-
return
|
|
583
|
+
return { conventions, meta };
|
|
584
|
+
}
|
|
585
|
+
function mergePackage(existing, fresh, existingMeta, freshMeta) {
|
|
586
|
+
const { conventions, meta } = mergeConventions(
|
|
587
|
+
existing.conventions ?? {},
|
|
588
|
+
fresh.conventions ?? {},
|
|
589
|
+
existingMeta,
|
|
590
|
+
freshMeta
|
|
591
|
+
);
|
|
592
|
+
return {
|
|
593
|
+
pkg: {
|
|
594
|
+
...existing,
|
|
595
|
+
stack: mergeStack(existing.stack ?? {}, fresh.stack ?? {}),
|
|
596
|
+
structure: mergeStructure(existing.structure ?? {}, fresh.structure ?? {}),
|
|
597
|
+
conventions
|
|
598
|
+
},
|
|
599
|
+
meta
|
|
600
|
+
};
|
|
321
601
|
}
|
|
322
602
|
function mergeConfig(existing, scanResult) {
|
|
323
603
|
const fresh = generateConfig(scanResult);
|
|
604
|
+
const existingByPath = new Map(existing.packages.map((p) => [p.path, p]));
|
|
605
|
+
const freshByPath = new Map(fresh.packages.map((p) => [p.path, p]));
|
|
606
|
+
const mergedPackages = [];
|
|
607
|
+
const mergedPkgMeta = {};
|
|
608
|
+
for (const existingPkg of existing.packages) {
|
|
609
|
+
const freshPkg = freshByPath.get(existingPkg.path);
|
|
610
|
+
if (freshPkg) {
|
|
611
|
+
const existingConvMeta = existing._meta?.packages?.[existingPkg.path]?.conventions;
|
|
612
|
+
const freshConvMeta = fresh._meta?.packages?.[existingPkg.path]?.conventions;
|
|
613
|
+
const { pkg, meta } = mergePackage(existingPkg, freshPkg, existingConvMeta, freshConvMeta);
|
|
614
|
+
mergedPackages.push(pkg);
|
|
615
|
+
if (Object.keys(meta).length > 0) {
|
|
616
|
+
mergedPkgMeta[pkg.path] = { conventions: meta };
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
mergedPackages.push(existingPkg);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
for (const freshPkg of fresh.packages) {
|
|
623
|
+
if (!existingByPath.has(freshPkg.path)) {
|
|
624
|
+
mergedPackages.push(freshPkg);
|
|
625
|
+
const freshConvMeta = fresh._meta?.packages?.[freshPkg.path]?.conventions;
|
|
626
|
+
if (freshConvMeta && Object.keys(freshConvMeta).length > 0) {
|
|
627
|
+
mergedPkgMeta[freshPkg.path] = { conventions: freshConvMeta };
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
324
631
|
const merged = {
|
|
325
632
|
$schema: existing.$schema ?? fresh.$schema,
|
|
326
633
|
version: existing.version,
|
|
327
634
|
name: existing.name,
|
|
328
|
-
enforcement: existing.enforcement,
|
|
329
|
-
stack: mergeStack(existing.stack, fresh.stack),
|
|
330
|
-
structure: mergeStructure(existing.structure, fresh.structure),
|
|
331
|
-
conventions: mergeConventions(existing.conventions, fresh.conventions),
|
|
332
635
|
rules: { ...existing.rules },
|
|
333
|
-
ignore: [...existing.ignore]
|
|
636
|
+
ignore: [...existing.ignore ?? []],
|
|
637
|
+
packages: mergedPackages,
|
|
638
|
+
_meta: {
|
|
639
|
+
lastSync: (/* @__PURE__ */ new Date()).toISOString(),
|
|
640
|
+
...Object.keys(mergedPkgMeta).length > 0 ? { packages: mergedPkgMeta } : {}
|
|
641
|
+
}
|
|
334
642
|
};
|
|
335
|
-
if (fresh.workspace) {
|
|
336
|
-
merged.workspace = fresh.workspace;
|
|
337
|
-
}
|
|
338
643
|
if (existing.boundaries) {
|
|
339
644
|
merged.boundaries = {
|
|
340
645
|
deny: { ...existing.boundaries.deny },
|
|
@@ -346,57 +651,10 @@ function mergeConfig(existing, scanResult) {
|
|
|
346
651
|
...fresh.boundaries.ignore ? { ignore: [...fresh.boundaries.ignore] } : {}
|
|
347
652
|
};
|
|
348
653
|
}
|
|
349
|
-
if (existing.packages || fresh.packages) {
|
|
350
|
-
merged.packages = mergePackageOverrides(existing.packages, fresh.packages);
|
|
351
|
-
}
|
|
352
654
|
return merged;
|
|
353
655
|
}
|
|
354
|
-
function mergePackageOverrides(existing, fresh) {
|
|
355
|
-
if (!fresh || fresh.length === 0) return existing;
|
|
356
|
-
if (!existing || existing.length === 0) return fresh;
|
|
357
|
-
const existingByPath = new Map(existing.map((p) => [p.path, p]));
|
|
358
|
-
const merged = [...existing];
|
|
359
|
-
for (const freshPkg of fresh) {
|
|
360
|
-
if (!existingByPath.has(freshPkg.path)) {
|
|
361
|
-
merged.push(freshPkg);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return merged.length > 0 ? merged : void 0;
|
|
365
|
-
}
|
|
366
656
|
|
|
367
657
|
// src/schema-parts.ts
|
|
368
|
-
var conventionValueDef = {
|
|
369
|
-
description: "A convention value \u2014 either a plain string (user-confirmed) or an object with scanner metadata.",
|
|
370
|
-
oneOf: [
|
|
371
|
-
{ type: "string" },
|
|
372
|
-
{
|
|
373
|
-
type: "object",
|
|
374
|
-
required: ["value", "_confidence", "_consistency"],
|
|
375
|
-
properties: {
|
|
376
|
-
value: {
|
|
377
|
-
type: "string",
|
|
378
|
-
description: "The convention value."
|
|
379
|
-
},
|
|
380
|
-
_confidence: {
|
|
381
|
-
type: "string",
|
|
382
|
-
enum: ["high", "medium", "low"],
|
|
383
|
-
description: "Scanner confidence level."
|
|
384
|
-
},
|
|
385
|
-
_consistency: {
|
|
386
|
-
type: "number",
|
|
387
|
-
minimum: 0,
|
|
388
|
-
maximum: 100,
|
|
389
|
-
description: "Scanner consistency percentage."
|
|
390
|
-
},
|
|
391
|
-
_detected: {
|
|
392
|
-
type: "boolean",
|
|
393
|
-
description: "Set by mergeConfig when a convention is newly detected during sync."
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
additionalProperties: false
|
|
397
|
-
}
|
|
398
|
-
]
|
|
399
|
-
};
|
|
400
658
|
var boundarySchema = {
|
|
401
659
|
type: "object",
|
|
402
660
|
required: ["deny"],
|
|
@@ -417,44 +675,112 @@ var boundarySchema = {
|
|
|
417
675
|
},
|
|
418
676
|
additionalProperties: false
|
|
419
677
|
};
|
|
678
|
+
var stackSchema = {
|
|
679
|
+
type: "object",
|
|
680
|
+
properties: {
|
|
681
|
+
framework: { type: "string", description: 'Primary framework (e.g. "nextjs@15").' },
|
|
682
|
+
language: { type: "string", description: 'Primary language (e.g. "typescript").' },
|
|
683
|
+
styling: { type: "string", description: 'Styling solution (e.g. "tailwindcss@4").' },
|
|
684
|
+
backend: { type: "string", description: 'Backend framework (e.g. "express@5").' },
|
|
685
|
+
orm: { type: "string", description: 'ORM or database client (e.g. "prisma").' },
|
|
686
|
+
packageManager: { type: "string", description: 'Package manager (e.g. "pnpm").' },
|
|
687
|
+
linter: { type: "string", description: 'Linter (e.g. "eslint@9").' },
|
|
688
|
+
formatter: { type: "string", description: 'Formatter (e.g. "prettier").' },
|
|
689
|
+
testRunner: { type: "string", description: 'Test runner (e.g. "vitest").' }
|
|
690
|
+
},
|
|
691
|
+
additionalProperties: false
|
|
692
|
+
};
|
|
693
|
+
var structureSchema = {
|
|
694
|
+
type: "object",
|
|
695
|
+
properties: {
|
|
696
|
+
srcDir: { type: "string", description: 'Source directory (e.g. "src").' },
|
|
697
|
+
pages: { type: "string", description: "Pages or routes directory." },
|
|
698
|
+
components: { type: "string", description: "Components directory." },
|
|
699
|
+
hooks: { type: "string", description: "Hooks directory." },
|
|
700
|
+
utils: { type: "string", description: "Utilities directory." },
|
|
701
|
+
types: { type: "string", description: "Type definitions directory." },
|
|
702
|
+
tests: { type: "string", description: "Tests directory." },
|
|
703
|
+
testPattern: { type: "string", description: 'Test file naming pattern (e.g. "*.test.ts").' }
|
|
704
|
+
},
|
|
705
|
+
additionalProperties: false
|
|
706
|
+
};
|
|
707
|
+
var conventionsSchema = {
|
|
708
|
+
type: "object",
|
|
709
|
+
properties: {
|
|
710
|
+
fileNaming: { type: "string", description: 'File naming convention (e.g. "kebab-case").' },
|
|
711
|
+
componentNaming: { type: "string", description: "Component naming convention." },
|
|
712
|
+
hookNaming: { type: "string", description: "Hook naming convention." },
|
|
713
|
+
importAlias: { type: "string", description: 'Import alias pattern (e.g. "@/*").' }
|
|
714
|
+
},
|
|
715
|
+
additionalProperties: false
|
|
716
|
+
};
|
|
717
|
+
var coverageSchema = {
|
|
718
|
+
type: "object",
|
|
719
|
+
properties: {
|
|
720
|
+
command: {
|
|
721
|
+
type: "string",
|
|
722
|
+
description: "Command to generate coverage summary data for this package."
|
|
723
|
+
},
|
|
724
|
+
summaryPath: {
|
|
725
|
+
type: "string",
|
|
726
|
+
description: "Path to coverage summary JSON relative to package root."
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
additionalProperties: false
|
|
730
|
+
};
|
|
420
731
|
var packageItemSchema = {
|
|
421
732
|
type: "object",
|
|
422
733
|
required: ["name", "path"],
|
|
423
734
|
properties: {
|
|
424
735
|
name: { type: "string", description: "Package name from package.json." },
|
|
425
|
-
path: { type: "string", description:
|
|
426
|
-
stack: {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
backend: { type: "string" },
|
|
433
|
-
orm: { type: "string" },
|
|
434
|
-
packageManager: { type: "string" },
|
|
435
|
-
linter: { type: "string" },
|
|
436
|
-
formatter: { type: "string" },
|
|
437
|
-
testRunner: { type: "string" }
|
|
438
|
-
},
|
|
439
|
-
additionalProperties: false
|
|
736
|
+
path: { type: "string", description: 'Relative path to the package ("." for root).' },
|
|
737
|
+
stack: { ...stackSchema, description: "Technology stack for this package." },
|
|
738
|
+
structure: { ...structureSchema, description: "Directory structure for this package." },
|
|
739
|
+
conventions: { ...conventionsSchema, description: "Coding conventions for this package." },
|
|
740
|
+
coverage: {
|
|
741
|
+
...coverageSchema,
|
|
742
|
+
description: "Coverage generation and summary settings for this package."
|
|
440
743
|
},
|
|
441
|
-
conventions: { $ref: "#/properties/conventions" },
|
|
442
744
|
rules: {
|
|
443
745
|
type: "object",
|
|
444
746
|
properties: {
|
|
445
747
|
maxFileLines: { type: "number" },
|
|
446
748
|
maxTestFileLines: { type: "number" },
|
|
447
|
-
|
|
448
|
-
requireTests: { type: "boolean" },
|
|
749
|
+
testCoverage: { type: "number" },
|
|
449
750
|
enforceNaming: { type: "boolean" },
|
|
450
751
|
enforceBoundaries: { type: "boolean" }
|
|
451
752
|
},
|
|
452
753
|
additionalProperties: false
|
|
453
754
|
},
|
|
454
|
-
ignore: { type: "array", items: { type: "string" } }
|
|
755
|
+
ignore: { type: "array", items: { type: "string" } },
|
|
756
|
+
boundaries: {
|
|
757
|
+
type: "object",
|
|
758
|
+
properties: {
|
|
759
|
+
deny: { type: "array", items: { type: "string" } },
|
|
760
|
+
ignore: { type: "array", items: { type: "string" } }
|
|
761
|
+
},
|
|
762
|
+
additionalProperties: false
|
|
763
|
+
}
|
|
455
764
|
},
|
|
456
765
|
additionalProperties: false
|
|
457
766
|
};
|
|
767
|
+
var defaultsSchema = {
|
|
768
|
+
type: "object",
|
|
769
|
+
properties: {
|
|
770
|
+
stack: { ...stackSchema, description: "Default stack inherited by all packages." },
|
|
771
|
+
structure: { ...structureSchema, description: "Default structure inherited by all packages." },
|
|
772
|
+
conventions: {
|
|
773
|
+
...conventionsSchema,
|
|
774
|
+
description: "Default conventions inherited by all packages."
|
|
775
|
+
},
|
|
776
|
+
coverage: {
|
|
777
|
+
...coverageSchema,
|
|
778
|
+
description: "Default coverage settings inherited by all packages."
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
additionalProperties: false,
|
|
782
|
+
description: "Shared defaults for all packages. Packages inherit and can override."
|
|
783
|
+
};
|
|
458
784
|
|
|
459
785
|
// src/schema.ts
|
|
460
786
|
var configSchema = {
|
|
@@ -463,7 +789,7 @@ var configSchema = {
|
|
|
463
789
|
title: "viberails configuration",
|
|
464
790
|
description: "Configuration file for viberails \u2014 guardrails for vibe coding.",
|
|
465
791
|
type: "object",
|
|
466
|
-
required: ["version", "name", "
|
|
792
|
+
required: ["version", "name", "packages", "rules"],
|
|
467
793
|
properties: {
|
|
468
794
|
$schema: {
|
|
469
795
|
type: "string",
|
|
@@ -472,120 +798,20 @@ var configSchema = {
|
|
|
472
798
|
version: {
|
|
473
799
|
type: "number",
|
|
474
800
|
const: 1,
|
|
475
|
-
description: "Config format version. Always 1
|
|
801
|
+
description: "Config format version. Always 1."
|
|
476
802
|
},
|
|
477
803
|
name: {
|
|
478
804
|
type: "string",
|
|
479
805
|
description: "Project name, typically from package.json."
|
|
480
806
|
},
|
|
481
|
-
enforcement: {
|
|
482
|
-
type: "string",
|
|
483
|
-
enum: ["warn", "enforce"],
|
|
484
|
-
default: "warn",
|
|
485
|
-
description: "Whether conventions are warned about or enforced as errors."
|
|
486
|
-
},
|
|
487
|
-
stack: {
|
|
488
|
-
type: "object",
|
|
489
|
-
required: ["language", "packageManager"],
|
|
490
|
-
properties: {
|
|
491
|
-
framework: {
|
|
492
|
-
type: "string",
|
|
493
|
-
description: 'Primary framework identifier (e.g. "nextjs@15", "remix@2").'
|
|
494
|
-
},
|
|
495
|
-
language: {
|
|
496
|
-
type: "string",
|
|
497
|
-
description: 'Primary language (e.g. "typescript", "javascript").'
|
|
498
|
-
},
|
|
499
|
-
styling: {
|
|
500
|
-
type: "string",
|
|
501
|
-
description: 'Styling solution (e.g. "tailwindcss@4", "css-modules").'
|
|
502
|
-
},
|
|
503
|
-
backend: {
|
|
504
|
-
type: "string",
|
|
505
|
-
description: 'Backend framework (e.g. "express@5", "fastify").'
|
|
506
|
-
},
|
|
507
|
-
orm: {
|
|
508
|
-
type: "string",
|
|
509
|
-
description: 'ORM or database client (e.g. "prisma", "drizzle", "typeorm").'
|
|
510
|
-
},
|
|
511
|
-
packageManager: {
|
|
512
|
-
type: "string",
|
|
513
|
-
description: 'Package manager (e.g. "pnpm", "npm", "yarn").'
|
|
514
|
-
},
|
|
515
|
-
linter: {
|
|
516
|
-
type: "string",
|
|
517
|
-
description: 'Linter (e.g. "eslint@9", "biome").'
|
|
518
|
-
},
|
|
519
|
-
formatter: {
|
|
520
|
-
type: "string",
|
|
521
|
-
description: 'Formatter (e.g. "prettier", "biome").'
|
|
522
|
-
},
|
|
523
|
-
testRunner: {
|
|
524
|
-
type: "string",
|
|
525
|
-
description: 'Test runner (e.g. "vitest", "jest").'
|
|
526
|
-
}
|
|
527
|
-
},
|
|
528
|
-
additionalProperties: false,
|
|
529
|
-
description: "Detected or configured technology stack."
|
|
530
|
-
},
|
|
531
|
-
structure: {
|
|
532
|
-
type: "object",
|
|
533
|
-
properties: {
|
|
534
|
-
srcDir: {
|
|
535
|
-
type: "string",
|
|
536
|
-
description: 'Source directory (e.g. "src"), or omit for flat structure.'
|
|
537
|
-
},
|
|
538
|
-
pages: {
|
|
539
|
-
type: "string",
|
|
540
|
-
description: 'Pages or routes directory (e.g. "src/app").'
|
|
541
|
-
},
|
|
542
|
-
components: {
|
|
543
|
-
type: "string",
|
|
544
|
-
description: 'Components directory (e.g. "src/components").'
|
|
545
|
-
},
|
|
546
|
-
hooks: {
|
|
547
|
-
type: "string",
|
|
548
|
-
description: 'Hooks directory (e.g. "src/hooks").'
|
|
549
|
-
},
|
|
550
|
-
utils: {
|
|
551
|
-
type: "string",
|
|
552
|
-
description: 'Utilities directory (e.g. "src/utils", "src/lib").'
|
|
553
|
-
},
|
|
554
|
-
types: {
|
|
555
|
-
type: "string",
|
|
556
|
-
description: 'Type definitions directory (e.g. "src/types").'
|
|
557
|
-
},
|
|
558
|
-
tests: {
|
|
559
|
-
type: "string",
|
|
560
|
-
description: 'Tests directory (e.g. "tests", "__tests__").'
|
|
561
|
-
},
|
|
562
|
-
testPattern: {
|
|
563
|
-
type: "string",
|
|
564
|
-
description: 'Test file naming pattern (e.g. "*.test.ts", "*.spec.ts").'
|
|
565
|
-
}
|
|
566
|
-
},
|
|
567
|
-
additionalProperties: false,
|
|
568
|
-
description: "Detected or configured directory structure."
|
|
569
|
-
},
|
|
570
|
-
conventions: {
|
|
571
|
-
type: "object",
|
|
572
|
-
properties: {
|
|
573
|
-
fileNaming: { $ref: "#/definitions/conventionValue" },
|
|
574
|
-
componentNaming: { $ref: "#/definitions/conventionValue" },
|
|
575
|
-
hookNaming: { $ref: "#/definitions/conventionValue" },
|
|
576
|
-
importAlias: { $ref: "#/definitions/conventionValue" }
|
|
577
|
-
},
|
|
578
|
-
additionalProperties: false,
|
|
579
|
-
description: "Detected or configured coding conventions."
|
|
580
|
-
},
|
|
581
807
|
rules: {
|
|
582
808
|
type: "object",
|
|
583
809
|
required: [
|
|
584
810
|
"maxFileLines",
|
|
585
|
-
"
|
|
586
|
-
"requireTests",
|
|
811
|
+
"testCoverage",
|
|
587
812
|
"enforceNaming",
|
|
588
|
-
"enforceBoundaries"
|
|
813
|
+
"enforceBoundaries",
|
|
814
|
+
"enforceMissingTests"
|
|
589
815
|
],
|
|
590
816
|
properties: {
|
|
591
817
|
maxFileLines: {
|
|
@@ -596,17 +822,12 @@ var configSchema = {
|
|
|
596
822
|
maxTestFileLines: {
|
|
597
823
|
type: "number",
|
|
598
824
|
default: 0,
|
|
599
|
-
description: "Maximum number of lines allowed per test file. Set to 0 to exempt test files
|
|
825
|
+
description: "Maximum number of lines allowed per test file. Set to 0 to exempt test files."
|
|
600
826
|
},
|
|
601
|
-
|
|
827
|
+
testCoverage: {
|
|
602
828
|
type: "number",
|
|
603
|
-
default:
|
|
604
|
-
description: "
|
|
605
|
-
},
|
|
606
|
-
requireTests: {
|
|
607
|
-
type: "boolean",
|
|
608
|
-
default: true,
|
|
609
|
-
description: "Whether to require test files for source modules."
|
|
829
|
+
default: 80,
|
|
830
|
+
description: "Minimum line coverage target percentage. 0 disables coverage threshold checks."
|
|
610
831
|
},
|
|
611
832
|
enforceNaming: {
|
|
612
833
|
type: "boolean",
|
|
@@ -617,6 +838,11 @@ var configSchema = {
|
|
|
617
838
|
type: "boolean",
|
|
618
839
|
default: false,
|
|
619
840
|
description: "Whether to enforce module boundary rules."
|
|
841
|
+
},
|
|
842
|
+
enforceMissingTests: {
|
|
843
|
+
type: "boolean",
|
|
844
|
+
default: true,
|
|
845
|
+
description: "Whether to enforce that every source file has a corresponding test file."
|
|
620
846
|
}
|
|
621
847
|
},
|
|
622
848
|
additionalProperties: false,
|
|
@@ -625,49 +851,61 @@ var configSchema = {
|
|
|
625
851
|
ignore: {
|
|
626
852
|
type: "array",
|
|
627
853
|
items: { type: "string" },
|
|
628
|
-
description: "
|
|
854
|
+
description: "Project-specific glob patterns to ignore (universal patterns are built-in)."
|
|
629
855
|
},
|
|
630
856
|
boundaries: {
|
|
631
857
|
...boundarySchema,
|
|
632
858
|
description: "Module boundary rules for import enforcement."
|
|
633
859
|
},
|
|
634
|
-
|
|
860
|
+
defaults: defaultsSchema,
|
|
861
|
+
packages: {
|
|
862
|
+
type: "array",
|
|
863
|
+
items: packageItemSchema,
|
|
864
|
+
description: 'Per-package configs. Single projects use path ".".'
|
|
865
|
+
},
|
|
866
|
+
_meta: {
|
|
635
867
|
type: "object",
|
|
636
|
-
required: ["packages", "isMonorepo"],
|
|
637
868
|
properties: {
|
|
869
|
+
lastSync: { type: "string", description: "ISO timestamp of last sync." },
|
|
638
870
|
packages: {
|
|
639
|
-
type: "
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
871
|
+
type: "object",
|
|
872
|
+
additionalProperties: {
|
|
873
|
+
type: "object",
|
|
874
|
+
properties: {
|
|
875
|
+
conventions: {
|
|
876
|
+
type: "object",
|
|
877
|
+
additionalProperties: {
|
|
878
|
+
type: "object",
|
|
879
|
+
properties: {
|
|
880
|
+
value: { type: "string" },
|
|
881
|
+
confidence: { type: "string", enum: ["high", "medium", "low"] },
|
|
882
|
+
consistency: { type: "number" },
|
|
883
|
+
detected: { type: "boolean" }
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
646
889
|
}
|
|
647
890
|
},
|
|
648
|
-
|
|
649
|
-
description: "Workspace configuration for monorepo projects."
|
|
650
|
-
},
|
|
651
|
-
packages: {
|
|
652
|
-
type: "array",
|
|
653
|
-
items: packageItemSchema,
|
|
654
|
-
description: "Per-package overrides for monorepo projects."
|
|
891
|
+
description: "Scanner metadata. Regenerated on every sync \u2014 not user-editable."
|
|
655
892
|
}
|
|
656
893
|
},
|
|
657
|
-
additionalProperties: false
|
|
658
|
-
definitions: {
|
|
659
|
-
conventionValue: conventionValueDef
|
|
660
|
-
}
|
|
894
|
+
additionalProperties: false
|
|
661
895
|
};
|
|
662
896
|
|
|
663
897
|
// src/index.ts
|
|
664
|
-
var VERSION = "0.
|
|
898
|
+
var VERSION = "0.5.0";
|
|
665
899
|
export {
|
|
900
|
+
BUILTIN_IGNORE,
|
|
666
901
|
DEFAULT_IGNORE,
|
|
667
902
|
DEFAULT_RULES,
|
|
668
903
|
VERSION,
|
|
904
|
+
compactConfig,
|
|
669
905
|
configSchema,
|
|
906
|
+
expandDefaults,
|
|
670
907
|
generateConfig,
|
|
908
|
+
inferCoverageCommand,
|
|
671
909
|
loadConfig,
|
|
672
910
|
loadConfigSafe,
|
|
673
911
|
mergeConfig
|