@emeryld/manager 1.4.14 → 1.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.
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { DEFAULT_LIMITS } from '../format-checker/config.js';
|
|
4
|
+
import { analyzeFileContent } from '../format-checker/scan/index.js';
|
|
5
|
+
import { collectDuplicateViolations } from '../format-checker/scan/duplicates.js';
|
|
6
|
+
function asPositiveInt(value, fallback) {
|
|
7
|
+
const num = Number(value);
|
|
8
|
+
if (!Number.isFinite(num))
|
|
9
|
+
return fallback;
|
|
10
|
+
const floored = Math.floor(num);
|
|
11
|
+
return floored <= 0 ? fallback : floored;
|
|
12
|
+
}
|
|
13
|
+
function asNonNegativeInt(value, fallback) {
|
|
14
|
+
const num = Number(value);
|
|
15
|
+
if (!Number.isFinite(num))
|
|
16
|
+
return fallback;
|
|
17
|
+
const floored = Math.floor(num);
|
|
18
|
+
return floored < 0 ? fallback : floored;
|
|
19
|
+
}
|
|
20
|
+
function buildLimits(rawOptions) {
|
|
21
|
+
const options = rawOptions && typeof rawOptions === 'object' ? rawOptions : {};
|
|
22
|
+
return {
|
|
23
|
+
...BASE_LIMITS,
|
|
24
|
+
maxFunctionLength: asPositiveInt(options.maxFunctionLength, BASE_LIMITS.maxFunctionLength),
|
|
25
|
+
maxMethodLength: asPositiveInt(options.maxMethodLength, BASE_LIMITS.maxMethodLength),
|
|
26
|
+
maxIndentationDepth: asPositiveInt(options.maxIndentationDepth, BASE_LIMITS.maxIndentationDepth),
|
|
27
|
+
maxFunctionsPerFile: asPositiveInt(options.maxFunctionsPerFile, BASE_LIMITS.maxFunctionsPerFile),
|
|
28
|
+
maxComponentsPerFile: asPositiveInt(options.maxComponentsPerFile, BASE_LIMITS.maxComponentsPerFile),
|
|
29
|
+
maxFileLength: asPositiveInt(options.maxFileLength, BASE_LIMITS.maxFileLength),
|
|
30
|
+
maxDuplicateLineOccurrences: asPositiveInt(options.maxDuplicateLineOccurrences, BASE_LIMITS.maxDuplicateLineOccurrences),
|
|
31
|
+
minDuplicateLines: asNonNegativeInt(options.minDuplicateLines, BASE_LIMITS.minDuplicateLines),
|
|
32
|
+
exportOnly: typeof options.exportOnly === 'boolean'
|
|
33
|
+
? options.exportOnly
|
|
34
|
+
: BASE_LIMITS.exportOnly,
|
|
35
|
+
indentationWidth: asPositiveInt(options.indentationWidth, BASE_LIMITS.indentationWidth),
|
|
36
|
+
reportingMode: BASE_LIMITS.reportingMode,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function loadWorkspaceDefaults() {
|
|
40
|
+
const settingsPath = path.join(process.cwd(), '.vscode', 'settings.json');
|
|
41
|
+
if (!existsSync(settingsPath))
|
|
42
|
+
return DEFAULT_LIMITS;
|
|
43
|
+
try {
|
|
44
|
+
const raw = readFileSync(settingsPath, 'utf-8');
|
|
45
|
+
const json = JSON.parse(raw);
|
|
46
|
+
const settings = json?.['manager.formatChecker'];
|
|
47
|
+
if (!settings || typeof settings !== 'object')
|
|
48
|
+
return DEFAULT_LIMITS;
|
|
49
|
+
return {
|
|
50
|
+
...DEFAULT_LIMITS,
|
|
51
|
+
maxFunctionLength: asPositiveInt(settings.maxFunctionLength, DEFAULT_LIMITS.maxFunctionLength),
|
|
52
|
+
maxMethodLength: asPositiveInt(settings.maxMethodLength, DEFAULT_LIMITS.maxMethodLength),
|
|
53
|
+
maxIndentationDepth: asPositiveInt(settings.maxIndentationDepth, DEFAULT_LIMITS.maxIndentationDepth),
|
|
54
|
+
maxFunctionsPerFile: asPositiveInt(settings.maxFunctionsPerFile, DEFAULT_LIMITS.maxFunctionsPerFile),
|
|
55
|
+
maxComponentsPerFile: asPositiveInt(settings.maxComponentsPerFile, DEFAULT_LIMITS.maxComponentsPerFile),
|
|
56
|
+
maxFileLength: asPositiveInt(settings.maxFileLength, DEFAULT_LIMITS.maxFileLength),
|
|
57
|
+
maxDuplicateLineOccurrences: asPositiveInt(settings.maxDuplicateLineOccurrences, DEFAULT_LIMITS.maxDuplicateLineOccurrences),
|
|
58
|
+
minDuplicateLines: asNonNegativeInt(settings.minDuplicateLines, DEFAULT_LIMITS.minDuplicateLines),
|
|
59
|
+
exportOnly: typeof settings.exportOnly === 'boolean'
|
|
60
|
+
? settings.exportOnly
|
|
61
|
+
: DEFAULT_LIMITS.exportOnly,
|
|
62
|
+
indentationWidth: asPositiveInt(settings.indentationWidth, DEFAULT_LIMITS.indentationWidth),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return DEFAULT_LIMITS;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const BASE_LIMITS = loadWorkspaceDefaults();
|
|
70
|
+
function parseDetailRange(detail) {
|
|
71
|
+
if (typeof detail !== 'string')
|
|
72
|
+
return undefined;
|
|
73
|
+
const match = /^(\d+):(\d+)\/(\d+):(\d+)$/.exec(detail);
|
|
74
|
+
if (!match)
|
|
75
|
+
return undefined;
|
|
76
|
+
return {
|
|
77
|
+
startLine: Number(match[1]),
|
|
78
|
+
startColumn: Number(match[2]),
|
|
79
|
+
endLine: Number(match[3]),
|
|
80
|
+
endColumn: Number(match[4]),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function locFromViolation(violation) {
|
|
84
|
+
const parsed = parseDetailRange(violation.detail);
|
|
85
|
+
if (parsed) {
|
|
86
|
+
return {
|
|
87
|
+
start: { line: parsed.startLine, column: Math.max(0, parsed.startColumn - 1) },
|
|
88
|
+
end: { line: parsed.endLine, column: Math.max(0, parsed.endColumn - 1) },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const line = typeof violation.line === 'number' && violation.line > 0 ? violation.line : 1;
|
|
92
|
+
return {
|
|
93
|
+
start: { line, column: 0 },
|
|
94
|
+
end: { line, column: 0 },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const rule = {
|
|
98
|
+
meta: {
|
|
99
|
+
type: 'suggestion',
|
|
100
|
+
docs: {
|
|
101
|
+
description: 'Runs @emeryld/manager format-checker heuristics as ESLint diagnostics.',
|
|
102
|
+
},
|
|
103
|
+
schema: [
|
|
104
|
+
{
|
|
105
|
+
type: 'object',
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
properties: {
|
|
108
|
+
maxFunctionLength: { type: 'integer', minimum: 1 },
|
|
109
|
+
maxMethodLength: { type: 'integer', minimum: 1 },
|
|
110
|
+
maxIndentationDepth: { type: 'integer', minimum: 1 },
|
|
111
|
+
maxFunctionsPerFile: { type: 'integer', minimum: 1 },
|
|
112
|
+
maxComponentsPerFile: { type: 'integer', minimum: 1 },
|
|
113
|
+
maxFileLength: { type: 'integer', minimum: 1 },
|
|
114
|
+
maxDuplicateLineOccurrences: { type: 'integer', minimum: 1 },
|
|
115
|
+
minDuplicateLines: { type: 'integer', minimum: 0 },
|
|
116
|
+
exportOnly: { type: 'boolean' },
|
|
117
|
+
indentationWidth: { type: 'integer', minimum: 1 },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
create(context) {
|
|
123
|
+
const filename = context.getFilename?.() ?? '<text>';
|
|
124
|
+
if (!filename || filename === '<input>' || filename === '<text>')
|
|
125
|
+
return {};
|
|
126
|
+
if (filename.endsWith('.d.ts'))
|
|
127
|
+
return {};
|
|
128
|
+
const limits = buildLimits(context.options?.[0]);
|
|
129
|
+
return {
|
|
130
|
+
Program() {
|
|
131
|
+
const sourceCode = context.getSourceCode();
|
|
132
|
+
const duplicates = new Map();
|
|
133
|
+
const fileSnapshots = new Map();
|
|
134
|
+
const violations = analyzeFileContent(filename, sourceCode.text, limits, duplicates, fileSnapshots);
|
|
135
|
+
const duplicateViolations = collectDuplicateViolations(duplicates, limits, fileSnapshots);
|
|
136
|
+
for (const violation of [...violations, ...duplicateViolations]) {
|
|
137
|
+
context.report({
|
|
138
|
+
loc: locFromViolation(violation),
|
|
139
|
+
message: `format-checker (${violation.type}): ${violation.message}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
export default {
|
|
147
|
+
rules: {
|
|
148
|
+
'format-checker': rule,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
@@ -16,14 +16,7 @@ export async function analyzeFiles(files, limits) {
|
|
|
16
16
|
violations.push(...collectDuplicateViolations(duplicateMap, limits, fileSnapshots));
|
|
17
17
|
return violations;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
let content;
|
|
21
|
-
try {
|
|
22
|
-
content = await readFile(filePath, 'utf-8');
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
19
|
+
export function analyzeFileContent(filePath, content, limits, duplicates, fileSnapshots) {
|
|
27
20
|
const lines = content.split(/\r?\n/);
|
|
28
21
|
const importFlags = markImportLines(lines);
|
|
29
22
|
const normalizedLines = lines.map((line, index) => ({
|
|
@@ -114,3 +107,13 @@ async function analyzeSingleFile(filePath, limits, duplicates, fileSnapshots) {
|
|
|
114
107
|
});
|
|
115
108
|
return violations;
|
|
116
109
|
}
|
|
110
|
+
async function analyzeSingleFile(filePath, limits, duplicates, fileSnapshots) {
|
|
111
|
+
let content;
|
|
112
|
+
try {
|
|
113
|
+
content = await readFile(filePath, 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
return analyzeFileContent(filePath, content, limits, duplicates, fileSnapshots);
|
|
119
|
+
}
|
|
@@ -110,12 +110,28 @@ function formatInteractiveLines(state, selectedIndex, title, searchState) {
|
|
|
110
110
|
return lines;
|
|
111
111
|
}
|
|
112
112
|
function renderInteractiveList(lines, previousLineCount) {
|
|
113
|
+
const columns = process.stdout.columns ?? 80;
|
|
114
|
+
const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
|
115
|
+
const visibleLength = (text) => {
|
|
116
|
+
const clean = text.replace(ansiRegex, '');
|
|
117
|
+
let length = 0;
|
|
118
|
+
for (const ch of clean) {
|
|
119
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
120
|
+
length += code > 0xffff ? 2 : 1;
|
|
121
|
+
}
|
|
122
|
+
return length;
|
|
123
|
+
};
|
|
124
|
+
const nextLineCount = lines.reduce((total, line) => {
|
|
125
|
+
const len = visibleLength(line);
|
|
126
|
+
const wrapped = Math.max(1, Math.ceil(len / columns));
|
|
127
|
+
return total + wrapped;
|
|
128
|
+
}, 0);
|
|
113
129
|
if (previousLineCount > 0) {
|
|
114
130
|
process.stdout.write(`\x1b[${previousLineCount}A`);
|
|
115
131
|
process.stdout.write('\x1b[0J');
|
|
116
132
|
}
|
|
117
133
|
lines.forEach((line) => console.log(line));
|
|
118
|
-
return
|
|
134
|
+
return nextLineCount;
|
|
119
135
|
}
|
|
120
136
|
export async function promptForScript(entries, title) {
|
|
121
137
|
const supportsRawMode = typeof input.setRawMode === 'function' && input.isTTY;
|
|
@@ -122,11 +122,6 @@ export function mergeManifestEntries(rootDir, inferred, overrides) {
|
|
|
122
122
|
merged.push({ ...baseEntry, path: normalized });
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
normalizedOverrides.forEach((entry) => {
|
|
126
|
-
const name = entry.name || path.basename(entry.path) || 'package';
|
|
127
|
-
const color = entry.color ?? colorFromSeed(name);
|
|
128
|
-
merged.push({ name, path: entry.path, color });
|
|
129
|
-
});
|
|
130
125
|
return merged;
|
|
131
126
|
}
|
|
132
127
|
export function colorFromSeed(seed) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emeryld/manager",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"node": ">=18"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"semver": "^7.7.
|
|
26
|
+
"semver": "^7.7.4",
|
|
27
27
|
"ts-morph": "^27.0.2",
|
|
28
|
-
"ts-node": "^10.9.
|
|
29
|
-
"typescript": "^5.3
|
|
28
|
+
"ts-node": "^10.9.2",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@types/node": "^20.
|
|
32
|
+
"@types/node": "^20.19.33",
|
|
33
33
|
"@types/semver": "^7.7.1",
|
|
34
34
|
"cross-env": "^7.0.3"
|
|
35
35
|
},
|