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