@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
- async function analyzeSingleFile(filePath, limits, duplicates, fileSnapshots) {
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
+ }
@@ -1,2 +1,3 @@
1
1
  export { collectSourceFiles } from './collect.js';
2
2
  export { analyzeFiles } from './analysis.js';
3
+ export { analyzeFileContent } from './analysis.js';
@@ -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 lines.length;
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.4.14",
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.3",
26
+ "semver": "^7.7.4",
27
27
  "ts-morph": "^27.0.2",
28
- "ts-node": "^10.9.1",
29
- "typescript": "^5.3.0"
28
+ "ts-node": "^10.9.2",
29
+ "typescript": "^5.9.3"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^20.17.0",
32
+ "@types/node": "^20.19.33",
33
33
  "@types/semver": "^7.7.1",
34
34
  "cross-env": "^7.0.3"
35
35
  },