vitral 0.3.0 → 0.4.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.
Files changed (55) hide show
  1. package/dist/angular.d.ts +2 -2
  2. package/dist/angular.js +7 -7
  3. package/dist/auto.js +7 -7
  4. package/dist/{capture-JYFERAPL.js → capture-TT5WBLJ6.js} +1 -1
  5. package/dist/channel-2YPYGX3J.js +95 -0
  6. package/dist/{chunk-AKNNHQ2H.js → chunk-463H6B56.js} +5 -0
  7. package/dist/chunk-54CBIZES.js +105 -0
  8. package/dist/{chunk-ZAC6QQI6.js → chunk-6EX5KYT5.js} +4 -4
  9. package/dist/{chunk-3YXI3G34.js → chunk-BCC25ANK.js} +11 -5
  10. package/dist/{chunk-TD4S3IHA.js → chunk-EMSB2NVX.js} +10 -8
  11. package/dist/{chunk-SDB5AZEI.js → chunk-IH3ZT3QU.js} +2 -2
  12. package/dist/{chunk-GNHCZRQP.js → chunk-TW7KYJQ6.js} +1 -1
  13. package/dist/{chunk-AH7KNMBW.js → chunk-UYOPJ4XQ.js} +1 -1
  14. package/dist/{chunk-IHTUM6LT.js → chunk-W6322KGP.js} +2 -2
  15. package/dist/{chunk-I3VSKSAZ.js → chunk-WD4CDGDZ.js} +21 -20
  16. package/dist/cli.js +1152 -37
  17. package/dist/devtools.d.ts +32 -2
  18. package/dist/devtools.js +27 -12
  19. package/dist/element.d.ts +12 -3
  20. package/dist/element.js +7 -7
  21. package/dist/embed.js +2 -0
  22. package/dist/{events-D5gs7rff.d.ts → events-Dkpkw_Sv.d.ts} +1 -1
  23. package/dist/expose-OVWTPHH5.js +15 -0
  24. package/dist/federation.d.ts +89 -0
  25. package/dist/federation.js +101 -0
  26. package/dist/iframe.js +7 -7
  27. package/dist/index.d.ts +4 -4
  28. package/dist/index.js +5 -5
  29. package/dist/overrides-NP754HY7.js +27 -0
  30. package/dist/preact.d.ts +4 -4
  31. package/dist/preact.js +6 -6
  32. package/dist/react.d.ts +3 -3
  33. package/dist/react.js +6 -6
  34. package/dist/routes.d.ts +2 -2
  35. package/dist/routes.js +5 -5
  36. package/dist/{shadow-6HELCHCJ.js → shadow-PVSETNMZ.js} +66 -5
  37. package/dist/single-spa.d.ts +40 -0
  38. package/dist/single-spa.js +52 -0
  39. package/dist/{slot-DqXTwGbs.d.ts → slot-UPg6ob9v.d.ts} +1 -1
  40. package/dist/solid.d.ts +3 -3
  41. package/dist/solid.js +6 -6
  42. package/dist/{sri-JMX6JLQP.js → sri-M7ZRU57G.js} +40 -10
  43. package/dist/svelte.d.ts +4 -4
  44. package/dist/svelte.js +6 -6
  45. package/dist/testing.d.ts +1 -1
  46. package/dist/testing.js +5 -5
  47. package/dist/{types-Czvm-siZ.d.ts → types-CA2p9KHC.d.ts} +112 -4
  48. package/dist/vite.d.ts +18 -0
  49. package/dist/vite.js +341 -35
  50. package/dist/vue.d.ts +3 -3
  51. package/dist/vue.js +6 -6
  52. package/package.json +79 -21
  53. package/dist/chunk-DXORSF4X.js +0 -42
  54. package/dist/expose-JUOUN5N5.js +0 -15
  55. package/dist/overrides-NZAIUGK5.js +0 -15
package/dist/cli.js CHANGED
@@ -1,8 +1,63 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli.ts
4
+ import { createRequire } from "module";
5
+
6
+ // src/cli/canal.ts
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { dirname } from "path";
9
+ function editChannel(current, edit) {
10
+ const stable = edit.stable ?? current?.stable;
11
+ if (!stable) throw new Error("[vitral] o canal ainda n\xE3o existe: use --stable");
12
+ const { canary: old, ...rest } = current ?? {};
13
+ const out = { ...rest, stable };
14
+ if (edit.percent !== void 0 && !(edit.percent >= 0 && edit.percent <= 100)) throw new Error("[vitral] --percent vai de 0 a 100");
15
+ if (edit.canary === "off") {
16
+ if (edit.percent !== void 0) throw new Error("[vitral] --percent n\xE3o vale com --canary off");
17
+ return out;
18
+ }
19
+ const version2 = edit.canary ?? old?.version;
20
+ const percent = edit.percent ?? old?.percent;
21
+ if (version2 === void 0) {
22
+ if (edit.percent !== void 0) throw new Error("[vitral] o canal n\xE3o tem canary: use --canary");
23
+ return out;
24
+ }
25
+ if (percent === void 0) throw new Error("[vitral] canary nova precisa de --percent");
26
+ out.canary = { version: version2, percent };
27
+ return out;
28
+ }
29
+ function canal(o) {
30
+ const current = existsSync(o.file) ? JSON.parse(readFileSync(o.file, "utf8")) : void 0;
31
+ const changes = o.stable !== void 0 || o.canary !== void 0 || o.percent !== void 0;
32
+ if (!changes && !current) throw new Error(`[vitral] ${o.file} n\xE3o existe: use --stable para criar`);
33
+ const next = changes ? editChannel(current, o) : current;
34
+ const text2 = JSON.stringify(next, null, 2) + "\n";
35
+ if (changes) {
36
+ mkdirSync(dirname(o.file), { recursive: true });
37
+ writeFileSync(o.file, text2);
38
+ }
39
+ ;
40
+ (o.log ?? console.log)(text2.trimEnd());
41
+ return next;
42
+ }
43
+
44
+ // src/cli/mcp.ts
45
+ import { createInterface } from "readline";
46
+
47
+ // src/cli/registry.ts
48
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
49
+ import { resolve as resolve2 } from "path";
50
+
3
51
  // src/cli/types.ts
4
- import { existsSync, readFileSync, writeFileSync } from "fs";
52
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
5
53
  import { resolve } from "path";
54
+ function flag(args2, name) {
55
+ const i = args2.indexOf(name);
56
+ if (i < 0) return void 0;
57
+ const value = args2[i + 1];
58
+ args2.splice(i, 2);
59
+ return value;
60
+ }
6
61
  var PROPS = /export type Props =\s*([\s\S]*?)\s*$/;
7
62
  function parseProps(dts) {
8
63
  return PROPS.exec(dts.replace(/^\s*\/\/.*$/gm, "").trim())?.[1]?.replace(/;$/, "") || null;
@@ -25,9 +80,9 @@ ${lines.join("\n")}
25
80
  function readHashes(env) {
26
81
  return Object.fromEntries([...env.matchAll(HASH)].map((m) => [JSON.parse(m[1]), m[2]]));
27
82
  }
28
- async function manifestHash(get, url) {
83
+ async function manifestHash(get2, url) {
29
84
  try {
30
- const res = await get(url);
85
+ const res = await get2(url);
31
86
  if (!res.ok) return void 0;
32
87
  const { integrity } = await res.json();
33
88
  return typeof integrity === "string" ? integrity : void 0;
@@ -35,15 +90,15 @@ async function manifestHash(get, url) {
35
90
  return void 0;
36
91
  }
37
92
  }
38
- async function check(remotes, out, get, log) {
39
- if (!existsSync(out)) {
93
+ async function check(remotes, out, get2, log) {
94
+ if (!existsSync2(out)) {
40
95
  log(`[vitral] ${out} n\xE3o existe. Rode \`npx vitral types\`.`);
41
96
  return 1;
42
97
  }
43
- const recorded = readHashes(readFileSync(out, "utf8"));
98
+ const recorded = readHashes(readFileSync2(out, "utf8"));
44
99
  let stale = 0;
45
100
  for (const [name, url] of Object.entries(remotes)) {
46
- const hash = await manifestHash(get, url);
101
+ const hash = await manifestHash(get2, url);
47
102
  if (!hash) log(`[vitral] ${name}: manifesto sem hash, n\xE3o deu para conferir.`);
48
103
  else if (recorded[name] !== hash) {
49
104
  log(`[vitral] ${name}: tipos desatualizados (${recorded[name] ? "o manifesto mudou" : "sem hash registrado"}).`);
@@ -59,33 +114,38 @@ async function check(remotes, out, get, log) {
59
114
  log(stale ? `[vitral] ${stale} remote(s) com tipos desatualizados. Rode \`npx vitral types\`.` : `[vitral] tipos em dia com os manifestos.`);
60
115
  return stale;
61
116
  }
62
- function readRemotes(cwd, args2, base) {
63
- const pairs = args2.filter((a) => a.includes("="));
64
- if (pairs.length) return Object.fromEntries(pairs.map((p) => [p.slice(0, p.indexOf("=")), p.slice(p.indexOf("=") + 1)]));
65
- const file = [args2[0], "public/remotes.json", "remotes.json"].find((f) => f && existsSync(resolve(cwd, f)));
117
+ function readEntries(cwd, args2, base) {
118
+ const pairs2 = args2.filter((a) => a.includes("="));
119
+ if (pairs2.length) return Object.fromEntries(pairs2.map((p) => [p.slice(0, p.indexOf("=")), { url: p.slice(p.indexOf("=") + 1) }]));
120
+ const file = [args2[0], "public/remotes.json", "remotes.json"].find((f) => f && existsSync2(resolve(cwd, f)));
66
121
  if (!file) throw new Error("[vitral] remotes.json n\xE3o encontrado. Passe o caminho ou pares nome=url.");
67
- const map = JSON.parse(readFileSync(resolve(cwd, file), "utf8"));
68
- return Object.fromEntries(
69
- Object.entries(map).map(([name, v]) => {
70
- const url = typeof v === "string" ? v : v.url;
71
- return [name, base ? new URL(url, base).href : url];
72
- })
73
- );
122
+ return entriesOf(JSON.parse(readFileSync2(resolve(cwd, file), "utf8")), base);
123
+ }
124
+ function entriesOf(map, base) {
125
+ const out = {};
126
+ for (const [name, v] of Object.entries(map)) {
127
+ const entry = typeof v === "string" ? { url: v } : v;
128
+ if (typeof entry?.url === "string") out[name] = { ...entry, url: base ? new URL(entry.url, base).href : entry.url };
129
+ }
130
+ return out;
131
+ }
132
+ function readRemotes(cwd, args2, base) {
133
+ return Object.fromEntries(Object.entries(readEntries(cwd, args2, base)).map(([name, e]) => [name, e.url]));
74
134
  }
75
135
  async function types(options = {}) {
76
136
  const cwd = options.cwd ?? process.cwd();
77
- const get = options.fetch ?? fetch;
137
+ const get2 = options.fetch ?? fetch;
78
138
  const log = options.log ?? console.log;
79
139
  const remotes = readRemotes(cwd, options.args ?? [], options.base);
80
140
  const out = resolve(cwd, options.out ?? "vitral-env.d.ts");
81
- if (options.check) return check(remotes, out, get, log);
141
+ if (options.check) return check(remotes, out, get2, log);
82
142
  const props = {};
83
143
  const hashes = {};
84
144
  let falhas = 0;
85
145
  for (const [name, url] of Object.entries(remotes)) {
86
146
  let dts;
87
147
  try {
88
- const res = await get(new URL("vitral.d.ts", url).href);
148
+ const res = await get2(new URL("vitral.d.ts", url).href);
89
149
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
90
150
  dts = await res.text();
91
151
  } catch (err) {
@@ -99,39 +159,1094 @@ async function types(options = {}) {
99
159
  continue;
100
160
  }
101
161
  props[name] = type;
102
- const hash = await manifestHash(get, url);
162
+ const hash = await manifestHash(get2, url);
103
163
  if (hash) hashes[name] = hash;
104
164
  }
105
- writeFileSync(out, envFile(props, hashes));
165
+ writeFileSync2(out, envFile(props, hashes));
106
166
  log(`[vitral] ${Object.keys(props).length} remote(s) em ${out}`);
107
167
  return falhas;
108
168
  }
109
169
 
170
+ // src/cli/registry.ts
171
+ var REGISTRY_SCHEMA = "https://joaogabriel15.github.io/vitral/schema/vitral-registry.json";
172
+ var ITEM_FIELDS = ["name", "url", "version", "props", "owner", "integrity", "framework", "description"];
173
+ var NAME = /^[^/\s]+$/;
174
+ function checkRegistry(data) {
175
+ const errors = [];
176
+ const reg = data;
177
+ if (!reg || typeof reg !== "object" || !Array.isArray(reg.remotes)) return ['falta a lista "remotes"'];
178
+ for (const key of Object.keys(reg)) if (!["$schema", "name", "remotes"].includes(key)) errors.push(`campo desconhecido "${key}"`);
179
+ const seen = /* @__PURE__ */ new Set();
180
+ reg.remotes.forEach((item, i) => {
181
+ const at = `remotes[${i}]`;
182
+ if (!item || typeof item !== "object") return errors.push(`${at}: n\xE3o \xE9 um objeto`);
183
+ if (typeof item.name !== "string" || !NAME.test(item.name)) errors.push(`${at}: "name" inv\xE1lido`);
184
+ else if (seen.has(item.name)) errors.push(`${at}: "${item.name}" repetido`);
185
+ else seen.add(item.name);
186
+ if (typeof item.url !== "string" || !item.url) errors.push(`${at}: falta "url"`);
187
+ for (const key of Object.keys(item)) {
188
+ if (!ITEM_FIELDS.includes(key)) errors.push(`${at}: campo desconhecido "${key}"`);
189
+ else if (typeof item[key] !== "string") errors.push(`${at}: "${key}" deve ser texto`);
190
+ }
191
+ });
192
+ return errors;
193
+ }
194
+ var isUrl = (s) => /^https?:\/\//.test(s);
195
+ async function loadRegistry(source, cwd = process.cwd(), get2 = fetch) {
196
+ let data;
197
+ if (isUrl(source)) {
198
+ const res = await get2(source);
199
+ if (!res.ok) throw new Error(`[vitral] registro ${source}: HTTP ${res.status}`);
200
+ data = await res.json();
201
+ } else {
202
+ const file = resolve2(cwd, source);
203
+ if (!existsSync3(file)) throw new Error(`[vitral] registro ${source} n\xE3o encontrado.`);
204
+ data = JSON.parse(readFileSync3(file, "utf8"));
205
+ }
206
+ const errors = checkRegistry(data);
207
+ if (errors.length) throw new Error(`[vitral] registro ${source} inv\xE1lido:
208
+ ${errors.join("\n ")}`);
209
+ const reg = data;
210
+ if (isUrl(source)) for (const item of reg.remotes) item.url = new URL(item.url, source).href;
211
+ return reg;
212
+ }
213
+ var defaultRegistry = () => process.env.VITRAL_REGISTRY || "vitral-registry.json";
214
+ async function registry(options = {}) {
215
+ const cwd = options.cwd ?? process.cwd();
216
+ const get2 = options.fetch ?? fetch;
217
+ const log = options.log ?? console.log;
218
+ const out = resolve2(cwd, options.out ?? "vitral-registry.json");
219
+ const before = existsSync3(out) ? JSON.parse(readFileSync3(out, "utf8")) : void 0;
220
+ const old = new Map((before?.remotes ?? []).map((item) => [item.name, item]));
221
+ const remotes = [];
222
+ let falhas = 0;
223
+ for (const [name, url] of Object.entries(readRemotes(cwd, options.args ?? [], options.base))) {
224
+ let manifest;
225
+ try {
226
+ const res = await get2(url);
227
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
228
+ manifest = await res.json();
229
+ } catch (err) {
230
+ log(`[vitral] ${name}: manifesto indispon\xEDvel (${err.message}).`);
231
+ falhas++;
232
+ continue;
233
+ }
234
+ let props = null;
235
+ try {
236
+ const res = await get2(new URL("vitral.d.ts", url).href);
237
+ if (res.ok) props = parseProps(await res.text());
238
+ } catch {
239
+ }
240
+ const { owner, framework, description } = old.get(name) ?? {};
241
+ remotes.push(
242
+ clean({
243
+ name,
244
+ url,
245
+ version: typeof manifest.version === "string" ? manifest.version : void 0,
246
+ props: props ?? void 0,
247
+ owner,
248
+ integrity: typeof manifest.integrity === "string" ? manifest.integrity : void 0,
249
+ framework,
250
+ description
251
+ })
252
+ );
253
+ }
254
+ const result = { $schema: REGISTRY_SCHEMA, ...(options.name ?? before?.name) && { name: options.name ?? before?.name }, remotes };
255
+ writeFileSync3(out, JSON.stringify(result, null, 2) + "\n");
256
+ log(`[vitral] ${remotes.length} remote(s) em ${out}`);
257
+ return falhas;
258
+ }
259
+ var clean = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
260
+ function remotesFile(cwd, to) {
261
+ if (to) return resolve2(cwd, to);
262
+ const found = ["public/remotes.json", "remotes.json"].find((f) => existsSync3(resolve2(cwd, f)));
263
+ return resolve2(cwd, found ?? (existsSync3(resolve2(cwd, "public")) ? "public/remotes.json" : "remotes.json"));
264
+ }
265
+ async function add(options) {
266
+ const cwd = options.cwd ?? process.cwd();
267
+ const get2 = options.fetch ?? fetch;
268
+ const log = options.log ?? console.log;
269
+ if (!options.names.length) throw new Error("[vitral] informe o nome do remote: npx vitral add <remote>");
270
+ const reg = await loadRegistry(options.registry ?? defaultRegistry(), cwd, get2);
271
+ const file = remotesFile(cwd, options.to);
272
+ const map = existsSync3(file) ? JSON.parse(readFileSync3(file, "utf8")) : {};
273
+ let falhas = 0;
274
+ let added = 0;
275
+ for (const name of options.names) {
276
+ const item = reg.remotes.find((r) => r.name === name);
277
+ if (!item) {
278
+ log(`[vitral] ${name}: n\xE3o est\xE1 no registro. Dispon\xEDveis: ${reg.remotes.map((r) => r.name).join(", ") || "nenhum"}.`);
279
+ falhas++;
280
+ continue;
281
+ }
282
+ const current = map[name];
283
+ const currentUrl = typeof current === "string" ? current : current?.url;
284
+ if (currentUrl && currentUrl !== item.url && !options.force) {
285
+ log(`[vitral] ${name}: j\xE1 est\xE1 no remotes.json com ${currentUrl}. Use --force para trocar.`);
286
+ falhas++;
287
+ continue;
288
+ }
289
+ if (item.integrity) {
290
+ const hash = await manifestHash(get2, options.base ? new URL(item.url, options.base).href : item.url);
291
+ if (hash && hash !== item.integrity) log(`[vitral] ${name}: o manifesto publicado mudou desde que o registro foi gerado.`);
292
+ }
293
+ map[name] = current && typeof current === "object" ? { ...current, url: item.url } : item.url;
294
+ added++;
295
+ log(`[vitral] ${name}${item.version ? `@${item.version}` : ""} adicionado em ${file}`);
296
+ }
297
+ if (added) {
298
+ writeFileSync3(file, JSON.stringify(map, null, 2) + "\n");
299
+ if (options.types !== false) falhas += await types({ cwd, args: [file], base: options.base, out: options.out, fetch: get2, log });
300
+ }
301
+ return falhas;
302
+ }
303
+
304
+ // src/cli/mcp.ts
305
+ var PROTOCOL = "2025-06-18";
306
+ var registryArg = { registry: { type: "string", description: "URL ou arquivo do vitral-registry.json. Padr\xE3o: o do servidor." } };
307
+ var TOOLS = [
308
+ {
309
+ name: "list_remotes",
310
+ description: "Lista os remotes (micro-frontends) do registro vitral: nome, vers\xE3o, dono, framework e descri\xE7\xE3o.",
311
+ inputSchema: {
312
+ type: "object",
313
+ properties: { ...registryArg, query: { type: "string", description: "Filtra por trecho do nome, dono, framework ou descri\xE7\xE3o." } }
314
+ }
315
+ },
316
+ {
317
+ name: "get_remote",
318
+ description: "Mostra um remote do registro: URL do manifesto, vers\xE3o, hash e o tipo TypeScript das props, com um exemplo de uso no host.",
319
+ inputSchema: { type: "object", properties: { ...registryArg, name: { type: "string", description: "Nome do remote." } }, required: ["name"] }
320
+ },
321
+ {
322
+ name: "add_remote",
323
+ description: "Adiciona um remote do registro ao remotes.json do host e gera os tipos das props (vitral-env.d.ts), como `npx vitral add`.",
324
+ inputSchema: {
325
+ type: "object",
326
+ properties: {
327
+ ...registryArg,
328
+ name: { type: "string", description: "Nome do remote." },
329
+ force: { type: "boolean", description: "Troca a URL se o remote j\xE1 est\xE1 no remotes.json com outra." }
330
+ },
331
+ required: ["name"]
332
+ }
333
+ }
334
+ ];
335
+ var text = (value, isError = false) => ({
336
+ content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }],
337
+ ...isError && { isError: true }
338
+ });
339
+ function usage(item) {
340
+ return [
341
+ `import { Remote } from 'vitral/react'`,
342
+ `<Remote name=${JSON.stringify(item.name)} props={props} />`,
343
+ "",
344
+ `// ou, sem framework:`,
345
+ `import { mountRemote } from 'vitral'`,
346
+ `await mountRemote(elemento, ${JSON.stringify(item.name)}, props)`,
347
+ "",
348
+ `// props: ${item.props ?? "sem tipo publicado (o remote n\xE3o publica vitral.d.ts)"}`
349
+ ].join("\n");
350
+ }
351
+ function mcpServer(options = {}) {
352
+ const cwd = options.cwd ?? process.cwd();
353
+ const get2 = options.fetch ?? fetch;
354
+ const source = (args2) => typeof args2.registry === "string" && args2.registry || options.registry || defaultRegistry();
355
+ async function call(name, args2) {
356
+ const reg = await loadRegistry(source(args2), cwd, get2);
357
+ if (name === "list_remotes") {
358
+ const q = typeof args2.query === "string" ? args2.query.toLowerCase() : "";
359
+ const items = reg.remotes.filter((r) => !q || [r.name, r.owner, r.framework, r.description].some((v) => v?.toLowerCase().includes(q))).map(({ name: name2, version: version2, owner, framework, description }) => ({ name: name2, version: version2, owner, framework, description }));
360
+ return text(items);
361
+ }
362
+ const item = reg.remotes.find((r) => r.name === args2.name);
363
+ if (!item) return text(`"${args2.name}" n\xE3o est\xE1 no registro. Dispon\xEDveis: ${reg.remotes.map((r) => r.name).join(", ") || "nenhum"}.`, true);
364
+ if (name === "get_remote") return text(`${JSON.stringify(item, null, 2)}
365
+
366
+ Uso:
367
+ ${usage(item)}`);
368
+ const logs = [];
369
+ const falhas = await add({ cwd, names: [item.name], registry: source(args2), force: args2.force === true, fetch: get2, log: (m) => logs.push(m) });
370
+ return text(logs.join("\n"), falhas > 0);
371
+ }
372
+ return {
373
+ async handle(msg) {
374
+ const { id, method, params = {} } = msg;
375
+ if (id === void 0) return void 0;
376
+ const ok2 = (result) => ({ jsonrpc: "2.0", id, result });
377
+ const fail = (code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
378
+ switch (method) {
379
+ case "initialize":
380
+ return ok2({
381
+ protocolVersion: typeof params.protocolVersion === "string" ? params.protocolVersion : PROTOCOL,
382
+ capabilities: { tools: {} },
383
+ serverInfo: { name: "vitral", version: options.version ?? "0.0.0" },
384
+ instructions: "Registro de micro-frontends vitral. Use list_remotes para achar um remote, get_remote para ver as props e add_remote para adicion\xE1-lo ao host."
385
+ });
386
+ case "ping":
387
+ return ok2({});
388
+ case "tools/list":
389
+ return ok2({ tools: TOOLS });
390
+ case "tools/call": {
391
+ if (!TOOLS.some((t) => t.name === params.name)) return fail(-32602, `ferramenta desconhecida: ${params.name}`);
392
+ try {
393
+ return ok2(await call(params.name, params.arguments ?? {}));
394
+ } catch (err) {
395
+ return ok2(text(err.message, true));
396
+ }
397
+ }
398
+ default:
399
+ return fail(-32601, `m\xE9todo desconhecido: ${method}`);
400
+ }
401
+ }
402
+ };
403
+ }
404
+ function serveMcp(input = process.stdin, output = process.stdout, options = {}) {
405
+ const server = mcpServer(options);
406
+ const lines = createInterface({ input });
407
+ const write = (res) => res && output.write(JSON.stringify(res) + "\n");
408
+ const pending = [];
409
+ lines.on("line", (line) => {
410
+ if (!line.trim()) return;
411
+ let msg;
412
+ try {
413
+ msg = JSON.parse(line);
414
+ } catch {
415
+ write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "JSON inv\xE1lido" } });
416
+ return;
417
+ }
418
+ pending.push(server.handle(msg).then(write));
419
+ });
420
+ return new Promise((done) => lines.on("close", () => Promise.all(pending).then(() => done())));
421
+ }
422
+
423
+ // src/cli/doctor.ts
424
+ import { createHash } from "crypto";
425
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
426
+ import { dirname as dirname2, join, resolve as resolve3 } from "path";
427
+ import { gzipSync } from "zlib";
428
+ var WARN_KB = 250;
429
+ var ANY_ORIGIN = "https://host.invalid";
430
+ var satisfies;
431
+ var ok = (title) => ({ status: "ok", title });
432
+ var warn = (title, cause, fix) => ({ status: "warn", title, cause, fix });
433
+ var error = (title, cause, fix) => ({ status: "error", title, cause, fix });
434
+ var kb = (bytes) => `${(bytes / 1024).toFixed(1)} kB`;
435
+ async function get(f, url, origin, timeout) {
436
+ try {
437
+ const res = await f(url, { headers: { Origin: origin }, signal: AbortSignal.timeout(timeout) });
438
+ return { res, bytes: new Uint8Array(await res.arrayBuffer()) };
439
+ } catch (e) {
440
+ const err = e;
441
+ return { failure: err.name === "TimeoutError" ? `sem resposta em ${timeout} ms` : err.cause?.code ?? err.cause?.message ?? err.message };
442
+ }
443
+ }
444
+ function cors(what, url, res, origin, known) {
445
+ if (new URL(url).origin === origin) return;
446
+ const allow = res.headers.get("access-control-allow-origin");
447
+ if (allow === "*" || allow === origin) return ok(`CORS do ${what}: Access-Control-Allow-Origin ${allow}`);
448
+ if (!allow)
449
+ return error(
450
+ `CORS do ${what}: a resposta n\xE3o tem Access-Control-Allow-Origin`,
451
+ `o navegador bloqueia ${url} lido a partir de ${known ? origin : "outra origem"}.`,
452
+ `configure o servidor de ${new URL(url).origin} para responder Access-Control-Allow-Origin: ${known ? origin : "*"} (ou *).`
453
+ );
454
+ return (known ? error : warn)(
455
+ `CORS do ${what}: Access-Control-Allow-Origin \xE9 ${allow}`,
456
+ `o navegador s\xF3 libera ${url} para ${allow}${known ? `, e o host est\xE1 em ${origin}` : ""}.`,
457
+ known ? `inclua ${origin} na resposta do servidor (ou use *).` : "confira se \xE9 a origem do host, ou rode com --host/--origin."
458
+ );
459
+ }
460
+ function parseCsp(text2, source) {
461
+ const directives = /* @__PURE__ */ new Map();
462
+ for (const part of text2.split(";")) {
463
+ const [name, ...values] = part.trim().split(/\s+/);
464
+ if (name && !directives.has(name.toLowerCase())) directives.set(name.toLowerCase(), values);
465
+ }
466
+ return { source, directives };
467
+ }
468
+ function sources(p, ...chain) {
469
+ for (const name of [...chain, "default-src"]) {
470
+ const list = p.directives.get(name);
471
+ if (list) return { name, list };
472
+ }
473
+ }
474
+ function cspAllows(list, url, self) {
475
+ const u = new URL(url);
476
+ return list.some((raw) => {
477
+ const s = raw.toLowerCase();
478
+ if (s === "'none'") return false;
479
+ if (s === "'self'") return !!self && u.origin === new URL(self).origin;
480
+ if (s === "*") return /^(https?|wss?):$/.test(u.protocol);
481
+ if (/^[a-z][a-z0-9+.-]*:$/.test(s)) return u.protocol === s || s === "http:" && u.protocol === "https:";
482
+ const m = /^(?:([a-z][a-z0-9+.-]*):\/\/)?(\*\.)?([^/:]+|\*)(?::(\d+|\*))?(\/.*)?$/.exec(s);
483
+ if (!m || s.startsWith("'")) return false;
484
+ const [, scheme, wild, host, port, path] = m;
485
+ if (scheme && u.protocol !== `${scheme}:` && !(scheme === "http" && u.protocol === "https:")) return false;
486
+ if (!scheme && self && new URL(self).protocol === "https:" && u.protocol !== "https:") return false;
487
+ if (wild ? !u.hostname.endsWith(`.${host}`) : host !== "*" && u.hostname !== host) return false;
488
+ const def = u.protocol === "https:" ? "443" : "80";
489
+ if (port !== "*" && (port ?? def) !== (u.port || def)) return false;
490
+ if (path) return path.endsWith("/") ? u.pathname.startsWith(path) : u.pathname === path;
491
+ return true;
492
+ });
493
+ }
494
+ async function hostPolicies(f, cwd, host, timeout, out) {
495
+ const meta = (html) => [...html.matchAll(/<meta\s[^>]*http-equiv=["']?content-security-policy["']?[^>]*>/gi)].map((m) => /content=(["'])([\s\S]*?)\1/i.exec(m[0])?.[2] ?? "");
496
+ const policies = [];
497
+ if (host) {
498
+ const { res, bytes, failure } = await get(f, host, new URL(host).origin, timeout);
499
+ if (!res?.ok) {
500
+ out.push(warn(`CSP: n\xE3o foi poss\xEDvel abrir ${host} (${failure ?? `HTTP ${res.status}`})`, void 0, "confira a URL de --host."));
501
+ return policies;
502
+ }
503
+ for (const h of (res.headers.get("content-security-policy") ?? "").split(",")) if (h.trim()) policies.push(parseCsp(h, `header de ${host}`));
504
+ for (const c of meta(new TextDecoder().decode(bytes))) policies.push(parseCsp(c, `<meta> de ${host}`));
505
+ } else {
506
+ const file = ["index.html", "public/index.html", "src/index.html"].map((p) => resolve3(cwd, p)).find((p) => existsSync4(p));
507
+ if (file) for (const c of meta(readFileSync4(file, "utf8"))) policies.push(parseCsp(c, `<meta> de ${file}`));
508
+ }
509
+ out.push(
510
+ policies.length ? ok(`CSP encontrada (${policies.map((p) => p.source).join(", ")})`) : ok(`CSP: nenhuma encontrada ${host ? `em ${host}` : "no index.html"}${host ? "" : " (passe --host para ler o header da p\xE1gina)"}`)
511
+ );
512
+ return policies;
513
+ }
514
+ function cspChecks(policies, self, m, manifestUrl, js, css) {
515
+ const out = [];
516
+ for (const p of policies) {
517
+ const missing = [];
518
+ const script = sources(p, "script-src-elem", "script-src");
519
+ const connect = sources(p, "connect-src");
520
+ const strict = script?.list.includes("'strict-dynamic'");
521
+ if (script && !strict && !cspAllows(script.list, js, self)) missing.push(`${script.name} n\xE3o inclui ${new URL(js).origin} (JS do remote)`);
522
+ if (connect && manifestUrl.endsWith(".json") && !cspAllows(connect.list, manifestUrl, self))
523
+ missing.push(`${connect.name} n\xE3o inclui ${new URL(manifestUrl).origin} (o manifesto \xE9 baixado com fetch)`);
524
+ if (connect && m.integrity && !cspAllows(connect.list, js, self)) missing.push(`${connect.name} n\xE3o inclui ${new URL(js).origin} (o JS com integrity \xE9 conferido com fetch)`);
525
+ if (missing.length) {
526
+ out.push(error(`CSP (${p.source}) bloqueia o remote`, missing.join("; ") + ".", `acrescente ${new URL(js).origin} a script-src e connect-src.`));
527
+ continue;
528
+ }
529
+ out.push(ok(`CSP (${p.source}) libera o manifesto e o JS`));
530
+ if (m.integrity && script && !strict && !script.list.includes("blob:"))
531
+ out.push(
532
+ warn(
533
+ `CSP (${p.source}): ${script.name} sem blob:`,
534
+ "navegadores que n\xE3o conferem o hash de cada m\xF3dulo executam o JS verificado a partir de uma URL blob:, e a\xED o remote n\xE3o roda.",
535
+ `acrescente blob: a ${script.name}.`
536
+ )
537
+ );
538
+ const style = sources(p, "style-src-elem", "style-src");
539
+ const blocked = style ? css.filter((u) => !cspAllows(style.list, u, self)) : [];
540
+ if (blocked.length)
541
+ out.push(warn(`CSP (${p.source}): ${style.name} n\xE3o inclui ${new URL(blocked[0]).origin}`, "o CSS do remote n\xE3o carrega fora do Shadow DOM.", `acrescente ${new URL(blocked[0]).origin} a ${style.name}.`));
542
+ }
543
+ return out;
544
+ }
545
+ function exportsMount(code) {
546
+ if (/export\s+(async\s+)?function\s*\*?\s*mount\b|export\s+(const|let|var)\s+mount\b|export\s*\{[^}]*\b(as\s+)?mount\s*[,}]/.test(code)) return "mount";
547
+ if (/export\s+default\b|export\s*\{[^}]*\bas\s+default\b/.test(code) && /\bmount\b/.test(code)) return "default";
548
+ return "missing";
549
+ }
550
+ function matchesIntegrity(bytes, integrity) {
551
+ const hashes = integrity.split(/\s+/).map((t) => /^sha(256|384|512)-([\w+/=-]+)/.exec(t)).filter((m) => !!m);
552
+ if (!hashes.length) return void 0;
553
+ const bits = Math.max(...hashes.map((m) => +m[1]));
554
+ const clean2 = (b) => b.replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, "");
555
+ const actual = clean2(createHash(`sha${bits}`).update(bytes).digest("base64"));
556
+ return hashes.some((m) => +m[1] === bits && clean2(m[2]) === actual);
557
+ }
558
+ var FRAMEWORKS = {
559
+ react: /__(SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED|CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE)/,
560
+ vue: /__vue_app__/
561
+ };
562
+ function installed(cwd, pkg) {
563
+ for (let dir = resolve3(cwd); ; dir = dirname2(dir)) {
564
+ const file = join(dir, "node_modules", pkg, "package.json");
565
+ if (existsSync4(file)) return JSON.parse(readFileSync4(file, "utf8")).version;
566
+ if (dir === dirname2(dir)) return;
567
+ }
568
+ }
569
+ async function checkRemote(name, e, ctx, seen) {
570
+ const out = [];
571
+ const isJson = new URL(e.url).pathname.endsWith(".json");
572
+ let m;
573
+ if (isJson) {
574
+ const { res: res2, bytes: bytes2, failure: failure2 } = await get(ctx.f, e.url, ctx.origin, ctx.timeout);
575
+ if (!res2) return [error(`manifesto: n\xE3o foi poss\xEDvel baixar (${failure2})`, `${e.url} n\xE3o respondeu.`, "confira a URL e se o servidor est\xE1 no ar.")];
576
+ if (!res2.ok) return [error(`manifesto: HTTP ${res2.status}`, `${e.url} n\xE3o existe ou n\xE3o est\xE1 publicado.`, "confira a URL no remotes.json e se o deploy do remote publicou o vitral.json.")];
577
+ const c = cors("manifesto", e.url, res2, ctx.origin, ctx.known);
578
+ if (c) out.push(c);
579
+ const text2 = new TextDecoder().decode(bytes2);
580
+ try {
581
+ m = JSON.parse(text2);
582
+ } catch {
583
+ const html = /^\s*</.test(text2);
584
+ out.push(
585
+ error(
586
+ "manifesto inv\xE1lido: n\xE3o \xE9 JSON",
587
+ html ? "o servidor devolveu HTML no lugar do vitral.json (fallback de SPA ou p\xE1gina de erro)." : `${e.url} n\xE3o tem um JSON v\xE1lido.`,
588
+ "abra a URL no navegador; publique o vitral.json gerado pelo build (dist/vitral.json)."
589
+ )
590
+ );
591
+ return out;
592
+ }
593
+ if (typeof m?.entry !== "string") {
594
+ out.push(error("manifesto inv\xE1lido: falta o campo entry", "sem entry o host n\xE3o sabe qual JS carregar (INVALID_MODULE).", 'gere o vitral.json com o plugin vitral/vite, ou escreva { "name", "entry" }.'));
595
+ return out;
596
+ }
597
+ out.push(ok(`manifesto v\xE1lido: ${m.name ?? name}${m.version ? ` ${m.version}` : ""}`));
598
+ } else {
599
+ m = { name, entry: e.url };
600
+ out.push(ok("sem manifesto: a URL \xE9 o pr\xF3prio JS"));
601
+ }
602
+ seen.manifest = m;
603
+ const abs = (u) => new URL(u, e.url).href;
604
+ const js = abs(m.entry);
605
+ if (new URL(js).origin !== new URL(e.url).origin)
606
+ out.push(
607
+ warn(
608
+ `o entry est\xE1 em ${new URL(js).origin}, outra origem que o manifesto`,
609
+ "sem allowedOrigins, o host s\xF3 aceita as origens das URLs registradas (ORIGIN_BLOCKED).",
610
+ `inclua ${new URL(js).origin} em configure({ allowedOrigins }).`
611
+ )
612
+ );
613
+ const { res, bytes, failure } = await get(ctx.f, js, ctx.origin, ctx.timeout);
614
+ if (!res) out.push(error(`JS: n\xE3o foi poss\xEDvel baixar (${failure})`, `${js} n\xE3o respondeu.`, "confira o entry do manifesto e se o servidor est\xE1 no ar."));
615
+ else if (!res.ok) out.push(error(`JS: HTTP ${res.status} em ${js}`, "o manifesto aponta para um arquivo que n\xE3o foi publicado (LOAD_FAILED).", "publique a pasta dist inteira, n\xE3o s\xF3 o vitral.json."));
616
+ if (res?.ok && bytes) {
617
+ const c = cors("JS", js, res, ctx.origin, ctx.known);
618
+ if (c) out.push(c);
619
+ const type = res.headers.get("content-type") ?? "";
620
+ if (!/javascript|ecmascript/i.test(type))
621
+ out.push(
622
+ error(
623
+ `JS servido como ${type || "sem Content-Type"}`,
624
+ "o navegador s\xF3 executa m\xF3dulo ES com Content-Type de JavaScript.",
625
+ "sirva os .js com Content-Type: text/javascript."
626
+ )
627
+ );
628
+ const code = new TextDecoder().decode(bytes);
629
+ const exp = exportsMount(code);
630
+ if (exp === "missing")
631
+ out.push(
632
+ (m.exposes?.length ? warn : error)(
633
+ "o JS n\xE3o exporta mount (nem default com mount)",
634
+ m.exposes?.length ? "s\xF3 as partes expostas podem ser usadas (loadModule)." : "o host recusa o m\xF3dulo (INVALID_MODULE).",
635
+ "exporte com defineRemote() ou com um adaptador (reactRemote, vueRemote...): export default defineRemote({ mount })."
636
+ )
637
+ );
638
+ else out.push(ok(exp === "mount" ? "exporta mount" : "exporta default com mount"));
639
+ for (const [who, integrity] of [
640
+ ["host (remotes.json)", e.integrity],
641
+ ["manifesto", m.integrity]
642
+ ]) {
643
+ if (!integrity) continue;
644
+ const match = matchesIntegrity(bytes, integrity);
645
+ if (match === void 0) out.push(error(`integrity do ${who} em formato inv\xE1lido: ${integrity}`, "o host recusa o remote (INTEGRITY).", "use sha256-, sha384- ou sha512- seguido do hash em base64."));
646
+ else if (!match)
647
+ out.push(
648
+ error(
649
+ `integrity do ${who} n\xE3o confere com o JS publicado`,
650
+ who === "manifesto" ? "o JS foi trocado sem gerar o vitral.json de novo, ou um cache serve uma vers\xE3o antiga." : "o remote foi publicado de novo e o hash fixado no host ficou velho.",
651
+ who === "manifesto" ? "publique o JS e o vitral.json do mesmo build; limpe o cache do CDN." : "atualize o integrity no remotes.json com o do vitral.json novo."
652
+ )
653
+ );
654
+ else out.push(ok(`integrity do ${who} confere`));
655
+ }
656
+ if (!e.integrity && !m.integrity) out.push(warn("sem integrity: o JS roda sem confer\xEAncia", void 0, "gere o manifesto com o plugin vitral/vite ou fixe integrity no host."));
657
+ const gz = gzipSync(bytes).length;
658
+ const size = `tamanho do JS: ${kb(bytes.length)} (${kb(gz)} com gzip)`;
659
+ if (ctx.maxKb !== void 0 && gz > ctx.maxKb * 1024)
660
+ out.push(error(size, `passa do limite de ${ctx.maxKb} kB.`, "veja o que entrou no bundle (vite build --mode production com rollup-plugin-visualizer) e use dedupe para o framework."));
661
+ else if (ctx.maxKb === void 0 && gz > WARN_KB * 1024) out.push(warn(size, `acima de ${WARN_KB} kB com gzip, o remote atrasa a p\xE1gina.`, "confira o que entrou no bundle; use dedupe para o framework; passe --max-kb para tornar isto um erro."));
662
+ else out.push(ok(size));
663
+ const deduped = /* @__PURE__ */ new Set([...(m.dedupe ?? []).map((d) => d.name), ...Object.keys(m.contract?.peers ?? {})]);
664
+ for (const [fw, mark] of Object.entries(FRAMEWORKS)) {
665
+ if (deduped.has(fw) || !ctx.deps[fw] || !mark.test(code)) continue;
666
+ out.push(
667
+ warn(
668
+ `o remote leva o pr\xF3prio ${fw} e o host tamb\xE9m tem ${fw} ${ctx.deps[fw]}`,
669
+ "s\xE3o duas c\xF3pias na p\xE1gina: mais download, e estado ou contexto de uma c\xF3pia n\xE3o \xE9 visto pela outra.",
670
+ `no remote: vitral({ dedupe: ['${fw}'] }).`
671
+ )
672
+ );
673
+ }
674
+ }
675
+ const files = Object.entries(m.files ?? {}).filter(([f]) => abs(f) !== js);
676
+ const bad = [];
677
+ for (const [file, integrity] of files) {
678
+ const got = await get(ctx.f, abs(file), ctx.origin, ctx.timeout);
679
+ if (!got.res?.ok) bad.push(`${file} (${got.failure ?? `HTTP ${got.res.status}`})`);
680
+ else if (!matchesIntegrity(got.bytes, integrity)) bad.push(`${file} (hash n\xE3o confere)`);
681
+ else if (/\.js$/.test(file)) {
682
+ const c = cors(`arquivo ${file}`, abs(file), got.res, ctx.origin, ctx.known);
683
+ if (c?.status !== "ok" && c) out.push(c);
684
+ }
685
+ }
686
+ if (bad.length)
687
+ out.push(error(`arquivos do manifesto com problema: ${bad.join(", ")}`, "o host n\xE3o carrega o CSS ou o build dividido (INTEGRITY ou LOAD_FAILED).", "publique todos os arquivos do mesmo build junto com o vitral.json."));
688
+ else if (files.length) out.push(ok(`${files.length} arquivo(s) do manifesto conferem com o hash`));
689
+ const range = e.contract ?? ctx.contract;
690
+ if (m.contract?.version && range) {
691
+ if (satisfies(m.contract.version, range)) out.push(ok(`contrato ${m.contract.version} na faixa ${range} do host`));
692
+ else
693
+ out.push(
694
+ error(
695
+ `contrato incompat\xEDvel: o host aceita ${range}, o remote publica ${m.contract.version}`,
696
+ "o host recusa o remote antes de executar (CONTRACT_MISMATCH).",
697
+ "publique uma vers\xE3o do remote compat\xEDvel, ou atualize o host para aceitar a nova vers\xE3o do contrato."
698
+ )
699
+ );
700
+ } else if (m.contract?.version) out.push(ok(`contrato ${m.contract.version} (o host n\xE3o declarou faixa; passe --contract para conferir)`));
701
+ else if (range && isJson) out.push(warn(`o host aceita contrato ${range}, mas o manifesto n\xE3o declara vers\xE3o`, "nada \xE9 conferido para este remote.", "no remote: vitral({ contract: { version: '\u2026' } })."));
702
+ for (const [dep, want] of Object.entries(m.contract?.peers ?? {})) {
703
+ const have = ctx.deps[dep];
704
+ if (!have) continue;
705
+ if (satisfies(have, want)) out.push(ok(`${dep} ${have} do host serve para o remote (${want})`));
706
+ else
707
+ out.push(
708
+ (range ? error : warn)(
709
+ `${dep}: o remote espera ${want}, o host tem ${have}`,
710
+ range ? "com contrato ligado e o host oferecendo essa vers\xE3o, o remote n\xE3o monta (CONTRACT_MISMATCH)." : "o remote baixa a pr\xF3pria c\xF3pia: duas c\xF3pias na p\xE1gina.",
711
+ `alinhe ${dep} nos dois projetos.`
712
+ )
713
+ );
714
+ }
715
+ if (ctx.policies.length) out.push(...cspChecks(ctx.policies, ctx.self, m, e.url, js, [...m.css ?? []].map(abs)));
716
+ return out;
717
+ }
718
+ function divergent(seen, deps) {
719
+ const groups = /* @__PURE__ */ new Map();
720
+ for (const s of seen) for (const d of s.manifest?.dedupe ?? []) groups.set(d.name, [...groups.get(d.name) ?? [], { who: s.name, range: d.range, version: d.version }]);
721
+ const out = [];
722
+ for (const [dep, list] of groups) {
723
+ const candidates = [deps[dep], ...list.map((l) => l.version)].filter((v) => !!v);
724
+ if (list.length < 2 || candidates.some((v) => list.every((l) => satisfies(v, l.range)))) continue;
725
+ out.push(
726
+ warn(
727
+ `${dep} em vers\xF5es divergentes: ${list.map((l) => `${l.who} ${l.range}`).join(", ")}`,
728
+ `nenhuma vers\xE3o serve para todos os remotes, ent\xE3o a p\xE1gina baixa mais de uma c\xF3pia de ${dep}.`,
729
+ `alinhe ${dep} nos remotes (mesmo major).`
730
+ )
731
+ );
732
+ }
733
+ return out;
734
+ }
735
+ async function remotesOf(o, f, cwd, timeout) {
736
+ const args2 = o.args ?? [];
737
+ const url = args2.find((a) => /^https?:\/\//.test(a) && !a.includes("="));
738
+ if (!url) return readEntries(cwd, args2, o.base ?? o.host);
739
+ const res = await f(url, { signal: AbortSignal.timeout(timeout) }).catch(() => void 0);
740
+ const data = res?.ok ? await res.json().catch(() => void 0) : void 0;
741
+ if (!data || typeof data !== "object") throw new Error(`[vitral] n\xE3o foi poss\xEDvel ler ${url}.`);
742
+ if (typeof data.entry === "string") return { [data.name ?? url]: { url } };
743
+ return entriesOf(data, o.base ?? url);
744
+ }
745
+ async function doctor(options = {}) {
746
+ ;
747
+ ({ satisfies } = await import("./dedupe-S7BHQMOZ.js"));
748
+ const cwd = options.cwd ?? process.cwd();
749
+ const f = options.fetch ?? fetch;
750
+ const timeout = options.timeout ?? 15e3;
751
+ const remotes = await remotesOf(options, f, cwd, timeout);
752
+ const known = !!(options.origin ?? options.host);
753
+ const origin = known ? new URL(options.origin ?? options.host).origin : ANY_ORIGIN;
754
+ const host = [];
755
+ const policies = await hostPolicies(f, cwd, options.host, timeout, host);
756
+ const deps = Object.fromEntries(Object.keys(FRAMEWORKS).map((d) => [d, installed(cwd, d)]));
757
+ const ctx = { f, cwd, origin, known, self: known ? origin : void 0, contract: options.contract, maxKb: options.maxKb, timeout, policies, deps };
758
+ if (!known) host.push(warn("origem do host desconhecida: o CORS \xE9 conferido com uma origem qualquer", void 0, "passe --host url (ou --origin) para conferir com a origem certa."));
759
+ const seen = [];
760
+ const list = await Promise.all(
761
+ Object.entries(remotes).map(async ([name, e]) => {
762
+ const s = { name };
763
+ seen.push(s);
764
+ return { name, url: e.url, checks: await checkRemote(name, e, ctx, s) };
765
+ })
766
+ );
767
+ host.push(...divergent(seen, deps));
768
+ const all = [...host, ...list.flatMap((r) => r.checks)];
769
+ const errors = all.filter((c) => c.status === "error").length;
770
+ return { ok: !errors, errors, warnings: all.filter((c) => c.status === "warn").length, host, remotes: list };
771
+ }
772
+ var MARK = { ok: "\u2713", warn: "!", error: "\u2717" };
773
+ function format(r) {
774
+ const lines = [];
775
+ const add2 = (c) => {
776
+ lines.push(` ${MARK[c.status]} ${c.title}`);
777
+ if (c.cause) lines.push(` Causa: ${c.cause}`);
778
+ if (c.fix) lines.push(` O que fazer: ${c.fix}`);
779
+ };
780
+ lines.push("Host");
781
+ r.host.forEach(add2);
782
+ for (const rem of r.remotes) {
783
+ lines.push("", `${rem.name} (${rem.url})`);
784
+ rem.checks.forEach(add2);
785
+ }
786
+ lines.push("", r.errors || r.warnings ? `${r.errors} erro(s), ${r.warnings} aviso(s).` : "Tudo certo.");
787
+ return lines.join("\n");
788
+ }
789
+ async function doctorCli(argv, options = {}, log = console.log) {
790
+ const args2 = [...argv];
791
+ const json = args2.includes("--json");
792
+ const maxKb = flag(args2, "--max-kb");
793
+ const r = await doctor({
794
+ ...options,
795
+ base: flag(args2, "--base"),
796
+ host: flag(args2, "--host"),
797
+ origin: flag(args2, "--origin"),
798
+ contract: flag(args2, "--contract"),
799
+ maxKb: maxKb === void 0 ? void 0 : Number(maxKb),
800
+ args: args2.filter((a) => a !== "--json")
801
+ });
802
+ log(json ? JSON.stringify(r, null, 2) : format(r));
803
+ return r.ok ? 0 : 1;
804
+ }
805
+
806
+ // src/cli/migrate.ts
807
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
808
+ import { basename, dirname as dirname3, extname, join as join2, relative, resolve as resolve4 } from "path";
809
+ var FRAMEWORKS2 = {
810
+ preact: { wrap: ["vitral/preact", "preactRemote"], plugin: ["@preact/preset-vite", "preact", "preact()"] },
811
+ react: { wrap: ["vitral/react", "reactRemote"], plugin: ["@vitejs/plugin-react", "react", "react()"] },
812
+ vue: { wrap: ["vitral/vue", "vueRemote"], plugin: ["@vitejs/plugin-vue", "vue", "vue()"] },
813
+ svelte: { wrap: ["vitral/svelte", "svelteRemote"], plugin: ["@sveltejs/vite-plugin-svelte", "{ svelte }", "svelte()"] },
814
+ solid: { wrap: ["vitral/solid", "solidRemote"], plugin: ["vite-plugin-solid", "solid", "solid()"] },
815
+ angular: { wrap: ["vitral/angular", "angularRemote"], plugin: ["@analogjs/vite-plugin-angular", "angular", "angular()"] }
816
+ };
817
+ var DETECT = [
818
+ ["preact", "preact"],
819
+ ["react", "react"],
820
+ ["vue", "vue"],
821
+ ["svelte", "svelte"],
822
+ ["solid-js", "solid"],
823
+ ["@angular/core", "angular"]
824
+ ];
825
+ var SOURCE_EXT = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".vue", ".svelte"];
826
+ var CONFIGS = ["webpack.config", "rspack.config", "rsbuild.config", "vite.config", "module-federation.config"].flatMap(
827
+ (b) => [".js", ".cjs", ".mjs", ".ts", ".mts", ".cts"].map((e) => b + e)
828
+ );
829
+ var MF_CALL = /new\s+(?:[\w$]+\.)*ModuleFederationPlugin\s*\(|\b(?:federation|pluginModuleFederation|createModuleFederationConfig)\s*\(/;
830
+ var VITE_CONFIG = (ts) => `vite.vitral.config.${ts ? "mts" : "mjs"}`;
831
+ var OUT_DIR = "dist-vitral";
832
+ function read(file) {
833
+ try {
834
+ return readFileSync5(file, "utf8");
835
+ } catch {
836
+ return null;
837
+ }
838
+ }
839
+ function rel(from, to) {
840
+ const r = relative(from, to).replace(/\\/g, "/");
841
+ return r.startsWith(".") ? r : "./" + r;
842
+ }
843
+ function spec(from, to) {
844
+ return rel(from, to).replace(/\.(tsx?|jsx?|mjs)$/, "");
845
+ }
846
+ function balanced(text2, start) {
847
+ const open = text2[start];
848
+ const close = open === "{" ? "}" : open === "(" ? ")" : open === "[" ? "]" : "";
849
+ if (!close) return null;
850
+ let depth = 0;
851
+ for (let i = start; i < text2.length; i++) {
852
+ const c = text2[i];
853
+ if (c === '"' || c === "'" || c === "`") {
854
+ for (i++; i < text2.length && text2[i] !== c; i++) if (text2[i] === "\\") i++;
855
+ } else if (c === "/" && text2[i + 1] === "/") i = text2.indexOf("\n", i) < 0 ? text2.length : text2.indexOf("\n", i);
856
+ else if (c === "/" && text2[i + 1] === "*") i = text2.indexOf("*/", i) < 0 ? text2.length : text2.indexOf("*/", i) + 1;
857
+ else if (c === open) depth++;
858
+ else if (c === close && --depth === 0) return text2.slice(start, i + 1);
859
+ }
860
+ return null;
861
+ }
862
+ function objectOf(text2, key) {
863
+ const m = new RegExp(`\\b${key}\\s*:\\s*\\{`).exec(text2);
864
+ return m ? balanced(text2, m.index + m[0].length - 1) : null;
865
+ }
866
+ function pairs(obj) {
867
+ const re = /(['"`]?)([\w@./-]+)\1\s*:\s*(?:(['"`])([^'"`]+)\3|\{[^}]*?\bimport\s*:\s*(['"`])([^'"`]+)\5[^}]*\})/g;
868
+ return [...obj.matchAll(re)].map((m) => [m[2], m[4] ?? m[6]]);
869
+ }
870
+ function project(cwd) {
871
+ const raw = read(join2(cwd, "package.json"));
872
+ const pkg = raw ? JSON.parse(raw) : null;
873
+ const deps = { ...pkg?.dependencies, ...pkg?.devDependencies, ...pkg?.peerDependencies };
874
+ return { cwd, pkg, deps, ts: existsSync5(join2(cwd, "tsconfig.json")), framework: DETECT.find(([d]) => d in deps)?.[1] ?? null };
875
+ }
876
+ function sources2(dir, out = []) {
877
+ let list;
878
+ try {
879
+ list = readdirSync(dir, { withFileTypes: true });
880
+ } catch {
881
+ return out;
882
+ }
883
+ for (const e of list) {
884
+ if (e.isDirectory()) {
885
+ if (!/^(node_modules|dist|build|\..*)$/.test(e.name)) sources2(join2(dir, e.name), out);
886
+ } else if (/\.(tsx?|jsx?|mjs)$/.test(e.name) && !/\.(test|spec|d)\./.test(e.name)) out.push(join2(dir, e.name));
887
+ }
888
+ return out;
889
+ }
890
+ var LIFECYCLE_EXPORT = /export\s+(?:(?:async\s+)?function\s*\*?\s*mount\b|(?:const|let|var)\s+mount\b|(?:const|let|var)\s*\{[^}]*\bmount\b[^}]*\}|\{[^}]*\bmount\b[^}]*\})/;
891
+ function configs(cwd) {
892
+ return CONFIGS.map((f) => ({ file: f, text: read(join2(cwd, f)) })).filter((c) => c.text !== null);
893
+ }
894
+ function detect(p) {
895
+ if (configs(p.cwd).some((c) => MF_CALL.test(c.text) && /\bexposes\s*:/.test(c.text))) return "module-federation";
896
+ if (Object.keys(p.deps).some((d) => d === "single-spa" || d.startsWith("single-spa-"))) return "single-spa";
897
+ if (configs(p.cwd).some((c) => MF_CALL.test(c.text))) return "module-federation";
898
+ return null;
899
+ }
900
+ function viteConfig(p, vitral, notes) {
901
+ const path = VITE_CONFIG(p.ts);
902
+ if (existsSync5(join2(p.cwd, path))) {
903
+ notes.push(`${path} j\xE1 existe e n\xE3o foi alterado. Confira se ele usa: ${vitral}`);
904
+ return null;
905
+ }
906
+ const fw = p.framework && FRAMEWORKS2[p.framework].plugin;
907
+ const lines = [
908
+ ...fw ? [`import ${fw[1]} from '${fw[0]}'`] : [],
909
+ `import { defineConfig } from 'vite'`,
910
+ `import vitral from 'vitral/vite'`,
911
+ ``,
912
+ `// Build do remote vitral (\`npm run build:vitral\`). Gera ${OUT_DIR}/vitral.json, ao lado do build atual.`,
913
+ `export default defineConfig({`,
914
+ ` plugins: [${fw ? fw[2] + ", " : ""}${vitral}],`,
915
+ ` build: { outDir: '${OUT_DIR}' },`,
916
+ `})`,
917
+ ``
918
+ ];
919
+ const missing = ["vite", "vitral", ...fw ? [fw[0]] : []].filter((d) => !(d in p.deps) && !(d === "@vitejs/plugin-react" && "@vitejs/plugin-react-swc" in p.deps));
920
+ if (missing.length) notes.push(`Instale as depend\xEAncias do build novo: npm i -D ${missing.join(" ")}`);
921
+ return { path, before: null, after: lines.join("\n") };
922
+ }
923
+ function buildScript(p) {
924
+ if (!p.pkg || p.pkg.scripts?.["build:vitral"]) return null;
925
+ const before = read(join2(p.cwd, "package.json"));
926
+ const indent = /^(\s+)"/m.exec(before)?.[1] ?? " ";
927
+ const pkg = { ...p.pkg, scripts: { ...p.pkg.scripts, "build:vitral": `vite build -c ${VITE_CONFIG(p.ts)}` } };
928
+ return { path: "package.json", before, after: JSON.stringify(pkg, null, indent) + (before.endsWith("\n") ? "\n" : "") };
929
+ }
930
+ function resolveSource(base, file) {
931
+ const abs = resolve4(base, file);
932
+ if (extname(abs) && existsSync5(abs)) return abs;
933
+ for (const ext of SOURCE_EXT) if (existsSync5(abs + ext)) return abs + ext;
934
+ for (const ext of SOURCE_EXT) if (existsSync5(join2(abs, "index" + ext))) return join2(abs, "index" + ext);
935
+ return abs;
936
+ }
937
+ function reactRoot(text2, file) {
938
+ const call = /\bsingleSpaReact\s*\(/.exec(text2);
939
+ const args2 = call && balanced(text2, call.index + call[0].length - 1);
940
+ const obj = args2 && /^\(\s*(\{[\s\S]*\})\s*\)$/.exec(args2)?.[1];
941
+ if (!obj) return null;
942
+ const body = obj.slice(1, -1).replace(/\/\/.*$|\/\*[\s\S]*?\*\//gm, "");
943
+ const keys = body.split(",").map((k) => k.split(":")[0].trim()).filter(Boolean);
944
+ if (keys.some((k) => !["React", "ReactDOM", "ReactDOMClient", "rootComponent"].includes(k))) return null;
945
+ const root = /\brootComponent\s*:\s*([\w$]+)\s*(?:,|$)/.exec(body)?.[1];
946
+ const from = root && new RegExp(`import\\s+${root.replace("$", "\\$")}\\s+from\\s+(['"])([^'"]+)\\1`).exec(text2)?.[2];
947
+ if (!from) return null;
948
+ return from.startsWith(".") ? resolve4(dirname3(file), from) : from;
949
+ }
950
+ function singleSpa(p) {
951
+ const changes = [];
952
+ const notes = [];
953
+ const src = existsSync5(join2(p.cwd, "src")) ? join2(p.cwd, "src") : p.cwd;
954
+ const files = sources2(src);
955
+ const found = files.filter((f) => {
956
+ const t = read(f);
957
+ return LIFECYCLE_EXPORT.test(t) && /\bunmount\b/.test(t);
958
+ });
959
+ const roots = files.filter((f) => /\bregisterApplication\s*\(/.test(read(f)));
960
+ if (roots.length) {
961
+ notes.push(
962
+ `Root config em ${roots.map((f) => rel(p.cwd, f)).join(", ")}: troque cada registerApplication por configure({ remotes }) e o activeWhen por uma rota do host ou routes() de vitral/routes. Veja https://joaogabriel15.github.io/vitral/migracao/single-spa`
963
+ );
964
+ }
965
+ if (!found.length) {
966
+ if (!roots.length) notes.push("Nenhum arquivo exporta os lifecycles mount e unmount do single-spa.");
967
+ return { from: "single-spa", changes, notes };
968
+ }
969
+ const [file, ...others] = found;
970
+ if (others.length) notes.push(`Outros arquivos tamb\xE9m exportam lifecycles e n\xE3o foram migrados: ${others.map((f) => rel(p.cwd, f)).join(", ")}`);
971
+ const out = join2(src, `vitral-remote.${p.ts ? "ts" : "js"}`);
972
+ const outRel = rel(p.cwd, out).replace(/^\.\//, "");
973
+ const text2 = read(file);
974
+ const root = /single-spa-react/.test(text2) ? reactRoot(text2, file) : null;
975
+ let code;
976
+ if (root) {
977
+ const imp = root.startsWith("/") || /^[A-Za-z]:/.test(root) ? spec(src, resolveSource(dirname3(root), root)) : root;
978
+ code = `import { reactRemote } from 'vitral/react'
979
+ import Root from '${imp}'
980
+
981
+ export default reactRemote(Root)
982
+ `;
983
+ notes.push(`${rel(p.cwd, file)} usava singleSpaReact s\xF3 com rootComponent: ${outRel} usa reactRemote. Quando nenhum host single-spa usar mais o app, remova single-spa-react e o arquivo antigo.`);
984
+ } else {
985
+ code = `import { singleSpaRemote } from 'vitral/single-spa'
986
+ import * as lifecycles from '${spec(src, file)}'
987
+
988
+ export default singleSpaRemote(lifecycles)
989
+ `;
990
+ notes.push(
991
+ `${outRel} embrulha os lifecycles de ${rel(p.cwd, file)} sem mudar o app. Para tirar o single-spa do bundle, troque por reactRemote, vueRemote etc. Veja https://joaogabriel15.github.io/vitral/migracao/single-spa`
992
+ );
993
+ }
994
+ if (existsSync5(out)) notes.push(`${outRel} j\xE1 existe e n\xE3o foi alterado.`);
995
+ else changes.push({ path: outRel, before: null, after: code });
996
+ const vite = viteConfig(p, `vitral({ entry: '${outRel}' })`, notes);
997
+ if (vite) changes.push(vite);
998
+ const script = buildScript(p);
999
+ if (script) changes.push(script);
1000
+ if (/\b(navigateToUrl|mountParcel|mountRootParcel)\b/.test(files.map((f) => read(f)).join("\n"))) {
1001
+ notes.push("O c\xF3digo usa navigateToUrl ou parcels do single-spa: fora de um host single-spa, troque navigateToUrl por ctx.navigate e parcels por <Remote> ou createSlot.");
1002
+ }
1003
+ if (/vite-plugin-single-spa|webpack-config-single-spa|systemjs/.test(Object.keys(p.deps).join(" "))) {
1004
+ notes.push("O build atual (SystemJS, vite-plugin-single-spa ou webpack-config-single-spa) continua para os hosts single-spa. Remova-o quando nenhum host usar mais o app.");
1005
+ }
1006
+ return { from: "single-spa", changes, notes };
1007
+ }
1008
+ function moduleFederation(p) {
1009
+ const changes = [];
1010
+ const notes = [];
1011
+ const found = configs(p.cwd).map((c) => {
1012
+ const m = MF_CALL.exec(c.text);
1013
+ return m ? { ...c, call: balanced(c.text, m.index + m[0].length - 1) ?? "" } : null;
1014
+ }).filter((c) => !!c);
1015
+ for (const c of found) {
1016
+ const remotes = objectOf(c.call, "remotes");
1017
+ const list = remotes ? pairs(remotes) : [];
1018
+ if (list.length) {
1019
+ const sugestoes = list.map(([n, v]) => {
1020
+ const url = v.includes("@") ? v.slice(v.indexOf("@") + 1) : v;
1021
+ return ` ${n}: federationRemote('${url.replace(/[^/]*$/, "mf-manifest.json")}'),`;
1022
+ });
1023
+ notes.push(
1024
+ `${c.file} consome remotes (${list.map(([n]) => n).join(", ")}). Para usar no host vitral antes de eles migrarem:
1025
+ configure({ remotes: {
1026
+ ${sugestoes.join("\n")}
1027
+ } })
1028
+ com federationRemote de vitral/federation. Quando cada remote migrar, troque pela URL do vitral.json dele.`
1029
+ );
1030
+ }
1031
+ }
1032
+ const remote = found.find((c) => /\bexposes\s*:/.test(c.call));
1033
+ if (!remote) {
1034
+ if (!found.some((c) => objectOf(c.call, "remotes"))) notes.push("Nenhuma configura\xE7\xE3o do Module Federation com exposes encontrada.");
1035
+ return { from: "module-federation", changes, notes };
1036
+ }
1037
+ const obj = objectOf(remote.call, "exposes");
1038
+ const exposes = obj ? pairs(obj) : [];
1039
+ if (!exposes.length) {
1040
+ notes.push(`${remote.file}: exposes n\xE3o \xE9 um objeto literal com caminhos. Configure exposes do vitral \xE0 m\xE3o. Veja https://joaogabriel15.github.io/vitral/aprender/exposes`);
1041
+ return { from: "module-federation", changes, notes };
1042
+ }
1043
+ const name = /\bname\s*:\s*(['"`])([^'"`]+)\1/.exec(remote.call)?.[2];
1044
+ const base = join2(p.cwd, dirname3(remote.file));
1045
+ const wrapDir = join2(p.cwd, "src", "vitral");
1046
+ const fw = p.framework && p.framework !== "angular" ? FRAMEWORKS2[p.framework] : null;
1047
+ const entries = [];
1048
+ let entry;
1049
+ const modules = [];
1050
+ for (const [k, f] of exposes) {
1051
+ const key = "./" + k.replace(/^\.?\/?/, "");
1052
+ const file = resolveSource(base, f);
1053
+ const fileRel = rel(p.cwd, file).replace(/^\.\//, "");
1054
+ const component = /\.(jsx|tsx|vue|svelte)$/.test(file) || /^[A-Z]/.test(basename(file).replace(/^index\./, basename(dirname3(file))));
1055
+ if (!component || !fw) {
1056
+ entries.push([key, fileRel]);
1057
+ modules.push(key);
1058
+ continue;
1059
+ }
1060
+ const wrapper = join2(wrapDir, `${key.slice(2).replace(/[^\w-]/g, "-")}.${p.ts ? "ts" : "js"}`);
1061
+ const wrapperRel = rel(p.cwd, wrapper).replace(/^\.\//, "");
1062
+ const imp = spec(wrapDir, file);
1063
+ const [from, fn] = fw.wrap;
1064
+ const code = `import { ${fn} } from '${from}'
1065
+ import Componente from '${imp}'
1066
+
1067
+ export * from '${imp}'
1068
+ export default ${fn}(Componente)
1069
+ `;
1070
+ if (existsSync5(wrapper)) notes.push(`${wrapperRel} j\xE1 existe e n\xE3o foi alterado.`);
1071
+ else changes.push({ path: wrapperRel, before: null, after: code });
1072
+ entries.push([key, wrapperRel]);
1073
+ entry ??= wrapperRel;
1074
+ }
1075
+ if (!fw) notes.push(`Framework ${p.framework ?? "n\xE3o identificado"}: cada parte de exposes aponta para o arquivo original. Um componente precisa exportar default com mount (ex.: ${p.framework === "angular" ? "angularRemote(Componente)" : "defineRemote({ mount })"}).`);
1076
+ if (modules.length && fw) notes.push(`Tratados como m\xF3dulos sem UI (host usa loadModule): ${modules.join(", ")}. Se algum for componente, embrulhe com ${fw.wrap[1]} como em src/vitral/.`);
1077
+ entry ??= entries[0][1];
1078
+ const lines = entries.map(([k, f]) => ` '${k}': '${f}',`);
1079
+ const vitral = `vitral({
1080
+ ${name ? `name: '${name}',
1081
+ ` : ""}entry: '${entry}',
1082
+ exposes: {
1083
+ ${lines.join("\n")}
1084
+ },
1085
+ })`;
1086
+ const vite = viteConfig(p, vitral, notes);
1087
+ if (vite) changes.push(vite);
1088
+ const script = buildScript(p);
1089
+ if (script) changes.push(script);
1090
+ if (objectOf(remote.call, "shared") || /\bshared\s*:/.test(remote.call)) {
1091
+ notes.push("shared n\xE3o migra: cada remote vitral leva as pr\xF3prias depend\xEAncias. Para usar as do host, veja a op\xE7\xE3o dedupe do plugin: https://joaogabriel15.github.io/vitral/aprender/dedupe");
1092
+ }
1093
+ notes.push(`${remote.file} continua gerando o remoteEntry.js para os hosts Module Federation. Remova o plugin quando nenhum host usar mais o remote.`);
1094
+ notes.push(`No host vitral: configure({ remotes: { ${name ?? "nome"}: 'https://\u2026/${OUT_DIR}/vitral.json' } }) e <Remote name="${name ?? "nome"}${exposes[0][0].replace(/^\.?\//, "/")}" />.`);
1095
+ return { from: "module-federation", changes, notes };
1096
+ }
1097
+ function diff(path, before, after, context = 2) {
1098
+ const a = before === null ? [] : before.replace(/\n$/, "").split("\n");
1099
+ const b = after.replace(/\n$/, "").split("\n");
1100
+ const head = before === null ? `--- /dev/null
1101
+ +++ ${path}` : `--- ${path}
1102
+ +++ ${path}`;
1103
+ const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
1104
+ for (let i2 = a.length - 1; i2 >= 0; i2--) for (let j2 = b.length - 1; j2 >= 0; j2--) lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
1105
+ const ops = [];
1106
+ let i = 0;
1107
+ let j = 0;
1108
+ while (i < a.length || j < b.length) {
1109
+ if (i < a.length && j < b.length && a[i] === b[j]) ops.push([" ", a[i++]]), j++;
1110
+ else if (i < a.length && (j >= b.length || lcs[i + 1][j] >= lcs[i][j + 1])) ops.push(["-", a[i++]]);
1111
+ else ops.push(["+", b[j++]]);
1112
+ }
1113
+ const keep = ops.map((_, k) => ops.slice(Math.max(0, k - context), k + context + 1).some(([op]) => op !== " "));
1114
+ const out = [];
1115
+ ops.forEach(([op, line], k) => {
1116
+ if (keep[k] && k > 0 && !keep[k - 1]) out.push("@@");
1117
+ if (keep[k]) out.push(op + line);
1118
+ });
1119
+ return `${head}
1120
+ ${out.join("\n")}`;
1121
+ }
1122
+ function plan(cwd, from) {
1123
+ const p = project(cwd);
1124
+ const source = from ?? detect(p);
1125
+ if (source === "single-spa") return singleSpa(p);
1126
+ if (source === "module-federation") return moduleFederation(p);
1127
+ return { from: null, changes: [], notes: ["N\xE3o achei single-spa nem Module Federation neste projeto. Use --from single-spa ou --from module-federation."] };
1128
+ }
1129
+ function migrate(options = {}) {
1130
+ const cwd = resolve4(options.cwd ?? process.cwd());
1131
+ const log = options.log ?? console.log;
1132
+ const result = plan(cwd, options.from);
1133
+ if (result.from) log(`[vitral] migrate: ${result.from === "single-spa" ? "single-spa" : "Module Federation"} em ${cwd}`);
1134
+ for (const c of result.changes) log(` ${c.before === null ? "+" : "~"} ${c.path}${c.before === null ? " (novo)" : ""}`);
1135
+ for (const c of result.changes) log("\n" + diff(c.path, c.before, c.after));
1136
+ if (result.notes.length) log(`
1137
+ Fa\xE7a \xE0 m\xE3o:
1138
+ ${result.notes.map((n) => ` - ${n}`).join("\n")}`);
1139
+ if (options.dryRun) {
1140
+ log("\n[vitral] --dry-run: nada foi escrito.");
1141
+ return result;
1142
+ }
1143
+ for (const c of result.changes) {
1144
+ const file = join2(cwd, c.path);
1145
+ mkdirSync2(dirname3(file), { recursive: true });
1146
+ writeFileSync4(file, c.after);
1147
+ }
1148
+ if (result.changes.length) log(`
1149
+ [vitral] ${result.changes.length} arquivo(s) escrito(s). Rode \`npm run build:vitral\`.`);
1150
+ return result;
1151
+ }
1152
+
110
1153
  // src/cli.ts
111
- var AJUDA = `Uso: vitral types [remotes.json | nome=url ...] [--base url] [--out arquivo] [--check]
1154
+ var AJUDA = `Uso:
1155
+ vitral types [remotes.json | nome=url ...] [--base url] [--out arquivo] [--check]
1156
+ vitral registry [remotes.json | nome=url ...] [--base url] [--out arquivo] [--name nome]
1157
+ vitral add <remote ...> [--registry url|arquivo] [--to remotes.json] [--base url] [--out arquivo] [--force] [--no-types]
1158
+ vitral mcp [--registry url|arquivo]
1159
+ vitral doctor [remotes.json | url | nome=url ...] [--host url] [--origin url] [--base url]
1160
+ [--contract faixa] [--max-kb n] [--json]
1161
+ vitral migrate [pasta] [--from single-spa|module-federation] [--dry-run]
1162
+ vitral canal <canal.json> [--stable x.y.z] [--canary x.y.z | off] [--percent n]
112
1163
 
113
- Baixa o vitral.d.ts de cada remote e gera vitral-env.d.ts.
114
- --check s\xF3 confere se o hash de cada manifesto ainda \xE9 o registrado;
115
- sai com erro se algum mudou (\xFAtil no CI).`;
116
- function flag(args2, name) {
1164
+ types Baixa o vitral.d.ts de cada remote e gera vitral-env.d.ts.
1165
+ --check s\xF3 confere se o hash de cada manifesto ainda \xE9 o
1166
+ registrado; sai com erro se algum mudou (\xFAtil no CI).
1167
+ registry Gera vitral-registry.json a partir dos manifestos.
1168
+ add Copia remotes do registro para o remotes.json e roda types.
1169
+ Registro padr\xE3o: VITRAL_REGISTRY ou vitral-registry.json.
1170
+ mcp Servidor MCP por stdio para agentes (list_remotes, get_remote,
1171
+ add_remote).
1172
+ doctor Confere cada remote como o host faria (manifesto, CORS, CSP, mount,
1173
+ integrity, contrato, dedupe, tamanho) e explica o que fazer.
1174
+ Sai com erro se alguma confer\xEAncia falhar (\xFAtil no CI).
1175
+ migrate Converte um app do single-spa ou um remote do Module Federation num
1176
+ remote vitral. Mostra o diff de cada arquivo.
1177
+ --dry-run s\xF3 mostra o que mudaria, sem escrever.
1178
+ canal Cria ou edita o canal de um remote (canary e rollback) e mostra
1179
+ como ficou. --stable \xE9 obrigat\xF3ria ao criar; --canary off tira a
1180
+ canary; --percent vai de 0 a 100.`;
1181
+ var falhou = (err) => {
1182
+ console.error(err.message);
1183
+ process.exitCode = 1;
1184
+ };
1185
+ function bool(args2, name) {
117
1186
  const i = args2.indexOf(name);
118
- if (i < 0) return void 0;
119
- const value = args2[i + 1];
120
- args2.splice(i, 2);
121
- return value;
1187
+ if (i >= 0) args2.splice(i, 1);
1188
+ return i >= 0;
1189
+ }
1190
+ function version() {
1191
+ try {
1192
+ return createRequire(import.meta.url)("../package.json").version;
1193
+ } catch {
1194
+ return "0.0.0";
1195
+ }
122
1196
  }
1197
+ var finish = (run) => run.then(
1198
+ (falhas) => process.exitCode = falhas ? 1 : 0,
1199
+ (err) => {
1200
+ console.error(err.message);
1201
+ process.exitCode = 1;
1202
+ }
1203
+ );
123
1204
  var [command, ...args] = process.argv.slice(2);
124
1205
  if (command === "types") {
125
1206
  const base = flag(args, "--base");
126
1207
  const out = flag(args, "--out");
127
- const check2 = args.includes("--check");
128
- types({ args: args.filter((a) => a !== "--check"), base, out, check: check2 }).then(
129
- (falhas) => process.exitCode = falhas ? 1 : 0,
130
- (err) => {
1208
+ const check2 = bool(args, "--check");
1209
+ finish(types({ args, base, out, check: check2 }));
1210
+ } else if (command === "registry") {
1211
+ const base = flag(args, "--base");
1212
+ const out = flag(args, "--out");
1213
+ const name = flag(args, "--name");
1214
+ finish(registry({ args, base, out, name }));
1215
+ } else if (command === "add") {
1216
+ const reg = flag(args, "--registry");
1217
+ const to = flag(args, "--to");
1218
+ const base = flag(args, "--base");
1219
+ const out = flag(args, "--out");
1220
+ const force = bool(args, "--force");
1221
+ const noTypes = bool(args, "--no-types");
1222
+ finish(add({ names: args, registry: reg, to, base, out, force, types: !noTypes }));
1223
+ } else if (command === "mcp") {
1224
+ serveMcp(process.stdin, process.stdout, { registry: flag(args, "--registry"), version: version() });
1225
+ } else if (command === "doctor") {
1226
+ doctorCli(args).then((code) => process.exitCode = code, falhou);
1227
+ } else if (command === "migrate") {
1228
+ const from = flag(args, "--from");
1229
+ if (from && from !== "single-spa" && from !== "module-federation") {
1230
+ console.error(`[vitral] --from inv\xE1lido: ${from}. Use single-spa ou module-federation.`);
1231
+ process.exitCode = 1;
1232
+ } else {
1233
+ try {
1234
+ const plan2 = migrate({ cwd: args.find((a) => !a.startsWith("--")), from, dryRun: args.includes("--dry-run") });
1235
+ process.exitCode = plan2.from ? 0 : 1;
1236
+ } catch (err) {
131
1237
  console.error(err.message);
132
1238
  process.exitCode = 1;
133
1239
  }
134
- );
1240
+ }
1241
+ } else if (command === "canal" && args.length) {
1242
+ const stable = flag(args, "--stable");
1243
+ const canary = flag(args, "--canary");
1244
+ const percent = flag(args, "--percent");
1245
+ try {
1246
+ canal({ file: args[0], stable, canary, percent: percent === void 0 ? void 0 : Number(percent) });
1247
+ } catch (err) {
1248
+ falhou(err);
1249
+ }
135
1250
  } else {
136
1251
  console.log(AJUDA);
137
1252
  process.exitCode = command && command !== "--help" ? 1 : 0;