orch-mini 0.1.3 → 0.1.4
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/package.json +1 -1
- package/src/info.ts +70 -40
package/package.json
CHANGED
package/src/info.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { resolve } from 'node:path';
|
|
|
3
3
|
import type { LoadedStack } from './parser.js';
|
|
4
4
|
import { hasRepo, type Service, type Stack } from './schema.js';
|
|
5
5
|
|
|
6
|
-
// Patrones que sugieren "el usuario debería completar esto antes de prod".
|
|
7
6
|
const PLACEHOLDER_PATTERNS = [
|
|
8
7
|
/^changeit-/i,
|
|
9
8
|
/^dev-default-/i,
|
|
@@ -13,6 +12,9 @@ const PLACEHOLDER_PATTERNS = [
|
|
|
13
12
|
/^your-/i,
|
|
14
13
|
];
|
|
15
14
|
|
|
15
|
+
const WRAP_WIDTH = 78;
|
|
16
|
+
const DESC_INDENT = ' '; // 7 espacios — alineado con texto post-marker
|
|
17
|
+
|
|
16
18
|
const C = supportsColor()
|
|
17
19
|
? {
|
|
18
20
|
reset: '\x1b[0m',
|
|
@@ -22,8 +24,9 @@ const C = supportsColor()
|
|
|
22
24
|
yellow: '\x1b[33m',
|
|
23
25
|
green: '\x1b[32m',
|
|
24
26
|
red: '\x1b[31m',
|
|
27
|
+
magenta: '\x1b[35m',
|
|
25
28
|
}
|
|
26
|
-
: { reset: '', dim: '', bold: '', cyan: '', yellow: '', green: '', red: '' };
|
|
29
|
+
: { reset: '', dim: '', bold: '', cyan: '', yellow: '', green: '', red: '', magenta: '' };
|
|
27
30
|
|
|
28
31
|
function supportsColor(): boolean {
|
|
29
32
|
return process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
|
|
@@ -37,8 +40,9 @@ export function renderInfo(loaded: LoadedStack): string {
|
|
|
37
40
|
const gw = stack.gateway;
|
|
38
41
|
|
|
39
42
|
out.push(
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
`${C.bold}${stack.name}${C.reset} ${C.dim}${services.length} services${
|
|
44
|
+
gw ? ` · gateway en :${gw.port}` : ''
|
|
45
|
+
}${C.reset}`,
|
|
42
46
|
);
|
|
43
47
|
|
|
44
48
|
out.push('');
|
|
@@ -73,9 +77,7 @@ export function renderInfo(loaded: LoadedStack): string {
|
|
|
73
77
|
const sorted = [...gw.routes].sort(
|
|
74
78
|
(a, b) => effectiveLen(b.path) - effectiveLen(a.path),
|
|
75
79
|
);
|
|
76
|
-
const maxUrl = Math.max(
|
|
77
|
-
...sorted.map((r) => urlFor(gw.port, r.path).length),
|
|
78
|
-
);
|
|
80
|
+
const maxUrl = Math.max(...sorted.map((r) => urlFor(gw.port, r.path).length));
|
|
79
81
|
for (const route of sorted) {
|
|
80
82
|
const url = urlFor(gw.port, route.path);
|
|
81
83
|
const note = route.strip_prefix ? ` ${C.dim}(strip prefix)${C.reset}` : '';
|
|
@@ -86,24 +88,30 @@ export function renderInfo(loaded: LoadedStack): string {
|
|
|
86
88
|
const debugConfigs = services.filter(([, s]) => s.debug_port !== undefined);
|
|
87
89
|
if (debugConfigs.length > 0) {
|
|
88
90
|
out.push('');
|
|
89
|
-
out.push(section('Debug (
|
|
91
|
+
out.push(section('Debug') + ` ${C.dim}(om vscode genera launch.json)${C.reset}`);
|
|
92
|
+
const maxName = Math.max(...debugConfigs.map(([n]) => n.length));
|
|
90
93
|
for (const [name, svc] of debugConfigs) {
|
|
91
|
-
out.push(
|
|
94
|
+
out.push(
|
|
95
|
+
` ${name.padEnd(maxName + 2)}${C.dim}attach →${C.reset} localhost:${svc.debug_port}`,
|
|
96
|
+
);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
const concerns = collectConcerns(stack
|
|
100
|
+
const concerns = collectConcerns(stack);
|
|
96
101
|
if (concerns.length > 0) {
|
|
97
102
|
out.push('');
|
|
98
103
|
out.push(`${C.yellow}⚠${C.reset} ${C.bold}Probablemente quieras tocar${C.reset}`);
|
|
99
|
-
const
|
|
100
|
-
for (const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const grouped = groupByService(concerns);
|
|
105
|
+
for (const [svcName, items] of grouped) {
|
|
106
|
+
out.push('');
|
|
107
|
+
out.push(` ${C.cyan}${svcName}${C.reset}`);
|
|
108
|
+
for (const c of items) {
|
|
109
|
+
out.push(` ${renderConcernLine(c)}`);
|
|
110
|
+
if (c.description) {
|
|
111
|
+
for (const line of wrapText(c.description, WRAP_WIDTH - DESC_INDENT.length)) {
|
|
112
|
+
out.push(` ${DESC_INDENT}${C.dim}${line}${C.reset}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
117
|
} else {
|
|
@@ -114,8 +122,45 @@ export function renderInfo(loaded: LoadedStack): string {
|
|
|
114
122
|
return out.join('\n') + '\n';
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
function
|
|
118
|
-
|
|
125
|
+
function renderConcernLine(c: Concern): string {
|
|
126
|
+
const marker = c.required
|
|
127
|
+
? `${C.red}✗${C.reset}`
|
|
128
|
+
: c.reason.startsWith('placeholder')
|
|
129
|
+
? `${C.yellow}!${C.reset}`
|
|
130
|
+
: `${C.dim}·${C.reset}`;
|
|
131
|
+
const tag = c.required ? ` ${C.red}[required]${C.reset}` : '';
|
|
132
|
+
return `${marker}${tag} ${C.bold}${c.key}${C.reset} ${C.dim}— ${c.reason}${C.reset}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function groupByService(concerns: Concern[]): Map<string, Concern[]> {
|
|
136
|
+
const grouped = new Map<string, Concern[]>();
|
|
137
|
+
for (const c of concerns) {
|
|
138
|
+
const list = grouped.get(c.service) ?? [];
|
|
139
|
+
list.push(c);
|
|
140
|
+
grouped.set(c.service, list);
|
|
141
|
+
}
|
|
142
|
+
// Ordenar items de cada service: required arriba.
|
|
143
|
+
for (const list of grouped.values()) {
|
|
144
|
+
list.sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0));
|
|
145
|
+
}
|
|
146
|
+
return grouped;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function wrapText(text: string, width: number): string[] {
|
|
150
|
+
if (text.length <= width) return [text];
|
|
151
|
+
const words = text.split(/\s+/);
|
|
152
|
+
const lines: string[] = [];
|
|
153
|
+
let current = '';
|
|
154
|
+
for (const word of words) {
|
|
155
|
+
if ((current + ' ' + word).trim().length > width) {
|
|
156
|
+
if (current) lines.push(current);
|
|
157
|
+
current = word;
|
|
158
|
+
} else {
|
|
159
|
+
current = (current + ' ' + word).trim();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (current) lines.push(current);
|
|
163
|
+
return lines;
|
|
119
164
|
}
|
|
120
165
|
|
|
121
166
|
function section(text: string): string {
|
|
@@ -134,11 +179,7 @@ function formatServiceRow(name: string, svc: Service): string {
|
|
|
134
179
|
}
|
|
135
180
|
|
|
136
181
|
function shortenRepo(repo: string): string {
|
|
137
|
-
|
|
138
|
-
return repo
|
|
139
|
-
.replace(/\.git$/, '')
|
|
140
|
-
.split(/[/:]/)
|
|
141
|
-
.pop() ?? repo;
|
|
182
|
+
return repo.replace(/\.git$/, '').split(/[/:]/).pop() ?? repo;
|
|
142
183
|
}
|
|
143
184
|
|
|
144
185
|
function urlFor(port: number, path: string): string {
|
|
@@ -156,10 +197,7 @@ function collectRepos(stack: Stack): RepoEntry[] {
|
|
|
156
197
|
const seen = new Map<string, RepoEntry>();
|
|
157
198
|
for (const svc of Object.values(stack.services)) {
|
|
158
199
|
if (!hasRepo(svc)) continue;
|
|
159
|
-
const slug = svc.repo
|
|
160
|
-
.replace(/\.git$/, '')
|
|
161
|
-
.split(/[/:]/)
|
|
162
|
-
.pop()!;
|
|
200
|
+
const slug = svc.repo.replace(/\.git$/, '').split(/[/:]/).pop()!;
|
|
163
201
|
if (!seen.has(slug)) seen.set(slug, { slug, ref: svc.ref });
|
|
164
202
|
}
|
|
165
203
|
return [...seen.values()];
|
|
@@ -183,7 +221,7 @@ type Concern = {
|
|
|
183
221
|
required?: boolean;
|
|
184
222
|
};
|
|
185
223
|
|
|
186
|
-
function collectConcerns(stack: Stack
|
|
224
|
+
function collectConcerns(stack: Stack): Concern[] {
|
|
187
225
|
const out: Concern[] = [];
|
|
188
226
|
|
|
189
227
|
for (const [svcName, svc] of Object.entries(stack.services)) {
|
|
@@ -195,9 +233,7 @@ function collectConcerns(stack: Stack, _workDir: string): Concern[] {
|
|
|
195
233
|
let reason: string | null = null;
|
|
196
234
|
if (v === '') reason = 'vacío';
|
|
197
235
|
else if (PLACEHOLDER_PATTERNS.some((re) => re.test(v))) {
|
|
198
|
-
reason = `placeholder
|
|
199
|
-
} else if (m.required === true) {
|
|
200
|
-
// Marcado como required en la metadata, pero ya tiene valor — no es concern.
|
|
236
|
+
reason = `placeholder "${truncate(v, 30)}"`;
|
|
201
237
|
}
|
|
202
238
|
|
|
203
239
|
if (reason !== null) {
|
|
@@ -209,13 +245,7 @@ function collectConcerns(stack: Stack, _workDir: string): Concern[] {
|
|
|
209
245
|
}
|
|
210
246
|
}
|
|
211
247
|
|
|
212
|
-
|
|
213
|
-
return out.sort((a, b) => {
|
|
214
|
-
if ((b.required ? 1 : 0) - (a.required ? 1 : 0) !== 0) {
|
|
215
|
-
return (b.required ? 1 : 0) - (a.required ? 1 : 0);
|
|
216
|
-
}
|
|
217
|
-
return 0;
|
|
218
|
-
});
|
|
248
|
+
return out;
|
|
219
249
|
}
|
|
220
250
|
|
|
221
251
|
function truncate(s: string, max: number): string {
|