theforge-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +433 -1
- package/README.md +44 -18
- package/dist/index.js +2565 -453
- package/docs/arquitetura.md +28 -26
- package/docs/capabilities.md +47 -0
- package/docs/registry-state-contract.md +81 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -95,7 +95,7 @@ var require_windows = __commonJS({
|
|
|
95
95
|
module.exports = isexe;
|
|
96
96
|
isexe.sync = sync;
|
|
97
97
|
var fs2 = __require("fs");
|
|
98
|
-
function checkPathExt(
|
|
98
|
+
function checkPathExt(path49, options) {
|
|
99
99
|
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
100
100
|
if (!pathext) {
|
|
101
101
|
return true;
|
|
@@ -106,25 +106,25 @@ var require_windows = __commonJS({
|
|
|
106
106
|
}
|
|
107
107
|
for (var i3 = 0; i3 < pathext.length; i3++) {
|
|
108
108
|
var p = pathext[i3].toLowerCase();
|
|
109
|
-
if (p &&
|
|
109
|
+
if (p && path49.substr(-p.length).toLowerCase() === p) {
|
|
110
110
|
return true;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
return false;
|
|
114
114
|
}
|
|
115
|
-
function checkStat(stat,
|
|
115
|
+
function checkStat(stat, path49, options) {
|
|
116
116
|
if (!stat.isSymbolicLink() && !stat.isFile()) {
|
|
117
117
|
return false;
|
|
118
118
|
}
|
|
119
|
-
return checkPathExt(
|
|
119
|
+
return checkPathExt(path49, options);
|
|
120
120
|
}
|
|
121
|
-
function isexe(
|
|
122
|
-
fs2.stat(
|
|
123
|
-
cb(er, er ? false : checkStat(stat,
|
|
121
|
+
function isexe(path49, options, cb) {
|
|
122
|
+
fs2.stat(path49, function(er, stat) {
|
|
123
|
+
cb(er, er ? false : checkStat(stat, path49, options));
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
|
-
function sync(
|
|
127
|
-
return checkStat(fs2.statSync(
|
|
126
|
+
function sync(path49, options) {
|
|
127
|
+
return checkStat(fs2.statSync(path49), path49, options);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
});
|
|
@@ -136,13 +136,13 @@ var require_mode = __commonJS({
|
|
|
136
136
|
module.exports = isexe;
|
|
137
137
|
isexe.sync = sync;
|
|
138
138
|
var fs2 = __require("fs");
|
|
139
|
-
function isexe(
|
|
140
|
-
fs2.stat(
|
|
139
|
+
function isexe(path49, options, cb) {
|
|
140
|
+
fs2.stat(path49, function(er, stat) {
|
|
141
141
|
cb(er, er ? false : checkStat(stat, options));
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
|
-
function sync(
|
|
145
|
-
return checkStat(fs2.statSync(
|
|
144
|
+
function sync(path49, options) {
|
|
145
|
+
return checkStat(fs2.statSync(path49), options);
|
|
146
146
|
}
|
|
147
147
|
function checkStat(stat, options) {
|
|
148
148
|
return stat.isFile() && checkMode(stat, options);
|
|
@@ -176,7 +176,7 @@ var require_isexe = __commonJS({
|
|
|
176
176
|
}
|
|
177
177
|
module.exports = isexe;
|
|
178
178
|
isexe.sync = sync;
|
|
179
|
-
function isexe(
|
|
179
|
+
function isexe(path49, options, cb) {
|
|
180
180
|
if (typeof options === "function") {
|
|
181
181
|
cb = options;
|
|
182
182
|
options = {};
|
|
@@ -186,7 +186,7 @@ var require_isexe = __commonJS({
|
|
|
186
186
|
throw new TypeError("callback not provided");
|
|
187
187
|
}
|
|
188
188
|
return new Promise(function(resolve, reject) {
|
|
189
|
-
isexe(
|
|
189
|
+
isexe(path49, options || {}, function(er, is) {
|
|
190
190
|
if (er) {
|
|
191
191
|
reject(er);
|
|
192
192
|
} else {
|
|
@@ -195,7 +195,7 @@ var require_isexe = __commonJS({
|
|
|
195
195
|
});
|
|
196
196
|
});
|
|
197
197
|
}
|
|
198
|
-
core(
|
|
198
|
+
core(path49, options || {}, function(er, is) {
|
|
199
199
|
if (er) {
|
|
200
200
|
if (er.code === "EACCES" || options && options.ignoreErrors) {
|
|
201
201
|
er = null;
|
|
@@ -205,9 +205,9 @@ var require_isexe = __commonJS({
|
|
|
205
205
|
cb(er, is);
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
|
-
function sync(
|
|
208
|
+
function sync(path49, options) {
|
|
209
209
|
try {
|
|
210
|
-
return core.sync(
|
|
210
|
+
return core.sync(path49, options || {});
|
|
211
211
|
} catch (er) {
|
|
212
212
|
if (options && options.ignoreErrors || er.code === "EACCES") {
|
|
213
213
|
return false;
|
|
@@ -224,7 +224,7 @@ var require_which = __commonJS({
|
|
|
224
224
|
"node_modules/which/which.js"(exports, module) {
|
|
225
225
|
"use strict";
|
|
226
226
|
var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
|
|
227
|
-
var
|
|
227
|
+
var path49 = __require("path");
|
|
228
228
|
var COLON = isWindows ? ";" : ":";
|
|
229
229
|
var isexe = require_isexe();
|
|
230
230
|
var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
|
|
@@ -262,7 +262,7 @@ var require_which = __commonJS({
|
|
|
262
262
|
return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
|
|
263
263
|
const ppRaw = pathEnv[i3];
|
|
264
264
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
265
|
-
const pCmd =
|
|
265
|
+
const pCmd = path49.join(pathPart, cmd);
|
|
266
266
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
267
267
|
resolve(subStep(p, i3, 0));
|
|
268
268
|
});
|
|
@@ -289,7 +289,7 @@ var require_which = __commonJS({
|
|
|
289
289
|
for (let i3 = 0; i3 < pathEnv.length; i3++) {
|
|
290
290
|
const ppRaw = pathEnv[i3];
|
|
291
291
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
292
|
-
const pCmd =
|
|
292
|
+
const pCmd = path49.join(pathPart, cmd);
|
|
293
293
|
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
294
294
|
for (let j = 0; j < pathExt.length; j++) {
|
|
295
295
|
const cur = p + pathExt[j];
|
|
@@ -337,7 +337,7 @@ var require_path_key = __commonJS({
|
|
|
337
337
|
var require_resolveCommand = __commonJS({
|
|
338
338
|
"node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
|
|
339
339
|
"use strict";
|
|
340
|
-
var
|
|
340
|
+
var path49 = __require("path");
|
|
341
341
|
var which = require_which();
|
|
342
342
|
var getPathKey = require_path_key();
|
|
343
343
|
function resolveCommandAttempt(parsed, withoutPathExt) {
|
|
@@ -348,14 +348,14 @@ var require_resolveCommand = __commonJS({
|
|
|
348
348
|
if (shouldSwitchCwd) {
|
|
349
349
|
try {
|
|
350
350
|
process.chdir(parsed.options.cwd);
|
|
351
|
-
} catch (
|
|
351
|
+
} catch (err2) {
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
let resolved;
|
|
355
355
|
try {
|
|
356
356
|
resolved = which.sync(parsed.command, {
|
|
357
357
|
path: env[getPathKey({ env })],
|
|
358
|
-
pathExt: withoutPathExt ?
|
|
358
|
+
pathExt: withoutPathExt ? path49.delimiter : void 0
|
|
359
359
|
});
|
|
360
360
|
} catch (e) {
|
|
361
361
|
} finally {
|
|
@@ -364,7 +364,7 @@ var require_resolveCommand = __commonJS({
|
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
if (resolved) {
|
|
367
|
-
resolved =
|
|
367
|
+
resolved = path49.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
|
|
368
368
|
}
|
|
369
369
|
return resolved;
|
|
370
370
|
}
|
|
@@ -418,8 +418,8 @@ var require_shebang_command = __commonJS({
|
|
|
418
418
|
if (!match) {
|
|
419
419
|
return null;
|
|
420
420
|
}
|
|
421
|
-
const [
|
|
422
|
-
const binary =
|
|
421
|
+
const [path49, argument] = match[0].replace(/#! ?/, "").split(" ");
|
|
422
|
+
const binary = path49.split("/").pop();
|
|
423
423
|
if (binary === "env") {
|
|
424
424
|
return argument;
|
|
425
425
|
}
|
|
@@ -454,7 +454,7 @@ var require_readShebang = __commonJS({
|
|
|
454
454
|
var require_parse = __commonJS({
|
|
455
455
|
"node_modules/cross-spawn/lib/parse.js"(exports, module) {
|
|
456
456
|
"use strict";
|
|
457
|
-
var
|
|
457
|
+
var path49 = __require("path");
|
|
458
458
|
var resolveCommand2 = require_resolveCommand();
|
|
459
459
|
var escape = require_escape();
|
|
460
460
|
var readShebang = require_readShebang();
|
|
@@ -479,7 +479,7 @@ var require_parse = __commonJS({
|
|
|
479
479
|
const needsShell = !isExecutableRegExp.test(commandFile);
|
|
480
480
|
if (parsed.options.forceShell || needsShell) {
|
|
481
481
|
const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
|
|
482
|
-
parsed.command =
|
|
482
|
+
parsed.command = path49.normalize(parsed.command);
|
|
483
483
|
parsed.command = escape.command(parsed.command);
|
|
484
484
|
parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
|
|
485
485
|
const shellCommand = [parsed.command].concat(parsed.args).join(" ");
|
|
@@ -533,9 +533,9 @@ var require_enoent = __commonJS({
|
|
|
533
533
|
const originalEmit = cp.emit;
|
|
534
534
|
cp.emit = function(name, arg1) {
|
|
535
535
|
if (name === "exit") {
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
return originalEmit.call(cp, "error",
|
|
536
|
+
const err2 = verifyENOENT(arg1, parsed);
|
|
537
|
+
if (err2) {
|
|
538
|
+
return originalEmit.call(cp, "error", err2);
|
|
539
539
|
}
|
|
540
540
|
}
|
|
541
541
|
return originalEmit.apply(cp, arguments);
|
|
@@ -569,7 +569,7 @@ var require_cross_spawn = __commonJS({
|
|
|
569
569
|
var cp = __require("child_process");
|
|
570
570
|
var parse = require_parse();
|
|
571
571
|
var enoent = require_enoent();
|
|
572
|
-
function
|
|
572
|
+
function spawn3(command, args, options) {
|
|
573
573
|
const parsed = parse(command, args, options);
|
|
574
574
|
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
575
575
|
enoent.hookChildProcess(spawned, parsed);
|
|
@@ -581,8 +581,8 @@ var require_cross_spawn = __commonJS({
|
|
|
581
581
|
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
|
|
582
582
|
return result;
|
|
583
583
|
}
|
|
584
|
-
module.exports =
|
|
585
|
-
module.exports.spawn =
|
|
584
|
+
module.exports = spawn3;
|
|
585
|
+
module.exports.spawn = spawn3;
|
|
586
586
|
module.exports.sync = spawnSync2;
|
|
587
587
|
module.exports._parse = parse;
|
|
588
588
|
module.exports._enoent = enoent;
|
|
@@ -7044,13 +7044,13 @@ var logOutputSync = ({ serializedResult, fdNumber, state, verboseInfo, encoding,
|
|
|
7044
7044
|
}
|
|
7045
7045
|
};
|
|
7046
7046
|
var writeToFiles = (serializedResult, stdioItems, outputFiles) => {
|
|
7047
|
-
for (const { path:
|
|
7048
|
-
const pathString = typeof
|
|
7047
|
+
for (const { path: path49, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
|
|
7048
|
+
const pathString = typeof path49 === "string" ? path49 : path49.toString();
|
|
7049
7049
|
if (append || outputFiles.has(pathString)) {
|
|
7050
|
-
appendFileSync2(
|
|
7050
|
+
appendFileSync2(path49, serializedResult);
|
|
7051
7051
|
} else {
|
|
7052
7052
|
outputFiles.add(pathString);
|
|
7053
|
-
writeFileSync2(
|
|
7053
|
+
writeFileSync2(path49, serializedResult);
|
|
7054
7054
|
}
|
|
7055
7055
|
}
|
|
7056
7056
|
};
|
|
@@ -9446,8 +9446,12 @@ var {
|
|
|
9446
9446
|
// src/utils/system.ts
|
|
9447
9447
|
import { existsSync as existsSync3 } from "fs";
|
|
9448
9448
|
import { mkdir } from "fs/promises";
|
|
9449
|
+
import os from "os";
|
|
9449
9450
|
import path8 from "path";
|
|
9450
9451
|
var DEFAULT_WORKSPACE = "./forge-workspace";
|
|
9452
|
+
function cliHome() {
|
|
9453
|
+
return process.env.FORGE_CLI_HOME || os.homedir();
|
|
9454
|
+
}
|
|
9451
9455
|
var WIN_SETUP_CANDIDATES = ["setup.ps1", "install.ps1"];
|
|
9452
9456
|
var POSIX_SETUP_CANDIDATES = ["setup.sh", "install.sh"];
|
|
9453
9457
|
function resolveTargetDir(input, cwd = process.cwd()) {
|
|
@@ -9590,8 +9594,8 @@ async function runStreamed(argv, cwd, onLine, timeoutMs) {
|
|
|
9590
9594
|
}
|
|
9591
9595
|
function errorDetail(error) {
|
|
9592
9596
|
if (error && typeof error === "object") {
|
|
9593
|
-
const
|
|
9594
|
-
return redactSecrets(
|
|
9597
|
+
const err2 = error;
|
|
9598
|
+
return redactSecrets(err2.stderr?.trim() || err2.shortMessage || err2.message || String(error));
|
|
9595
9599
|
}
|
|
9596
9600
|
return redactSecrets(String(error));
|
|
9597
9601
|
}
|
|
@@ -9606,16 +9610,16 @@ function compareVersions(a3, b) {
|
|
|
9606
9610
|
return b.localeCompare(a3, void 0, { numeric: true });
|
|
9607
9611
|
}
|
|
9608
9612
|
async function updateCheck(repoDir) {
|
|
9613
|
+
const up = await execa("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
|
|
9614
|
+
cwd: repoDir,
|
|
9615
|
+
reject: false
|
|
9616
|
+
});
|
|
9617
|
+
if (up.exitCode !== 0) return { behind: 0, ahead: 0, failure: "no-upstream" };
|
|
9609
9618
|
try {
|
|
9610
9619
|
await execa("git", ["fetch", "--quiet", "--tags"], { cwd: repoDir, env: NO_PROMPT });
|
|
9611
9620
|
} catch (error) {
|
|
9612
9621
|
return { behind: 0, ahead: 0, failure: classifyGitFailure(errorDetail(error)) };
|
|
9613
9622
|
}
|
|
9614
|
-
const up = await execa("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
|
|
9615
|
-
cwd: repoDir,
|
|
9616
|
-
reject: false
|
|
9617
|
-
});
|
|
9618
|
-
if (up.exitCode !== 0) return { behind: 0, ahead: 0, failure: "unknown" };
|
|
9619
9623
|
const counts = await execa("git", ["rev-list", "--left-right", "--count", "HEAD...@{u}"], {
|
|
9620
9624
|
cwd: repoDir,
|
|
9621
9625
|
reject: false
|
|
@@ -9777,10 +9781,26 @@ var S = {
|
|
|
9777
9781
|
errRefDirty: "Working tree com altera\xE7\xF5es locais \u2014 n\xE3o \xE9 seguro trocar de revis\xE3o",
|
|
9778
9782
|
refCheckedOut: "Revis\xE3o",
|
|
9779
9783
|
errRefNeedsOne: "--ref/--version exigem exatamente uma Forja",
|
|
9784
|
+
errMethodUnsupported: "--method registry s\xF3 \xE9 suportado em install|uninstall|update|reinstall|rollback|pin|unpin",
|
|
9785
|
+
errRegistryReinstallNeedsNames: "reinstall --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
|
|
9786
|
+
errRegistryRollbackNeedsNames: "rollback --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
|
|
9787
|
+
errRegistryPinNeedsNames: "pin --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
|
|
9788
|
+
errRegistryUnpinNeedsNames: "unpin --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
|
|
9789
|
+
registryPinnedAt: "fixada em",
|
|
9790
|
+
errRegistryNeedsNames: "informe as Forjas (ex.: forge-cli install api-forge --method registry) ou --all",
|
|
9791
|
+
errRegistryUninstallNeedsNames: "uninstall --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
|
|
9792
|
+
registryUninstallConfirm: "remover venvs e receipts das Forjas instaladas via PyPI",
|
|
9793
|
+
registryUninstallNeedsYes: "Nada foi removido. Reexecute com --yes para confirmar.",
|
|
9794
|
+
registryIgnoredFlags: "flags ignoradas no m\xE9todo registry",
|
|
9795
|
+
registryStaleCache: "cache do PyPI expirado \u2014 metadados podem estar desatualizados",
|
|
9796
|
+
registryOfflineMissing: "artefato ausente no cache local \u2014 instala\xE7\xE3o offline imposs\xEDvel",
|
|
9797
|
+
registryHashMismatch: "digest sha256 diverge do publicado no PyPI \u2014 instala\xE7\xE3o abortada",
|
|
9798
|
+
registryVerifyFailed: "(verify falhou)",
|
|
9780
9799
|
errRefExclusive: "Use apenas um entre --ref e --version",
|
|
9781
9800
|
errRefInvalid: "Revis\xE3o/vers\xE3o inv\xE1lida",
|
|
9782
9801
|
updateCheckTitle: "Verifica\xE7\xE3o de atualiza\xE7\xF5es",
|
|
9783
9802
|
updateCheckFailed: "n\xE3o foi poss\xEDvel verificar",
|
|
9803
|
+
updateNoUpstream: "sem upstream configurado",
|
|
9784
9804
|
updateCommitsBehind: "commit(s) dispon\xEDveis",
|
|
9785
9805
|
updateUpToDate: "atualizado",
|
|
9786
9806
|
updateNothing: "Nenhuma Forja clonada neste diret\xF3rio \u2014 nada para atualizar",
|
|
@@ -10013,13 +10033,17 @@ Uso:
|
|
|
10013
10033
|
forge-cli doctor [--fix] Diagnostica; --fix repara os que falharem
|
|
10014
10034
|
forge-cli list Lista o cat\xE1logo e o \xFAltimo estado conhecido
|
|
10015
10035
|
forge-cli install [forjas] Instala uma ou mais Forjas (sem nomes: interativo)
|
|
10036
|
+
--method registry instala do PyPI em venv isolada
|
|
10016
10037
|
forge-cli clone <forjas> S\xF3 clona/sincroniza; n\xE3o executa scripts
|
|
10017
10038
|
forge-cli update [forjas] Atualiza (pull + setup); --check s\xF3 verifica
|
|
10039
|
+
--method registry: mais nova no PyPI; respeita pin; --check plano
|
|
10018
10040
|
forge-cli versions [forjas] Lista tags/vers\xF5es e revis\xE3o atual
|
|
10019
10041
|
forge-cli reinstall <forjas> Reexecuta o setup mantendo a revis\xE3o atual; --clean recria o clone
|
|
10042
|
+
--method registry: reinstala a mesma vers\xE3o do receipt (drift)
|
|
10020
10043
|
forge-cli pin <forja> --version <tag> Fixa a vers\xE3o (update/install respeitam)
|
|
10021
10044
|
forge-cli unpin <forja> Remove a fixa\xE7\xE3o
|
|
10022
10045
|
forge-cli rollback <forja> Volta o checkout ao HEAD anterior registrado
|
|
10046
|
+
--method registry: desempilha a vers\xE3o anterior do receipt
|
|
10023
10047
|
forge-cli uninstall <forjas> Remove clones limpos (backup de .env/configs; exige --yes)
|
|
10024
10048
|
forge-cli components [forjas] Mostra o instalador nativo e as flags suportadas por Forja
|
|
10025
10049
|
forge-cli help Mostra esta ajuda
|
|
@@ -10028,6 +10052,7 @@ Uso:
|
|
|
10028
10052
|
forge-cli recommend Recomenda\xE7\xF5es explic\xE1veis a partir dos sinais
|
|
10029
10053
|
forge-cli env <verbo> [forja] Intelig\xEAncia de .env: inspect|plan|init|validate|migrate|doctor
|
|
10030
10054
|
forge-cli hosts [verbo] Hosts de IA: list|plan|register|validate|rollback
|
|
10055
|
+
forge-cli mcp [forja\u2026] Comando MCP por Forja (clone ou venv registry); --verify testa --version
|
|
10031
10056
|
forge-cli self install Registra forge-cli no PATH do usu\xE1rio
|
|
10032
10057
|
forge-cli self uninstall Remove o registro do PATH
|
|
10033
10058
|
forge-cli self update Atualiza o pr\xF3prio CLI conforme o m\xE9todo de instala\xE7\xE3o
|
|
@@ -10038,6 +10063,7 @@ Uso:
|
|
|
10038
10063
|
forge-cli bundle <verbo> Portabilidade: export [chaves-n\xE3o-secretas...]|import|verify
|
|
10039
10064
|
forge-cli learning <verbo> Observa\xE7\xF5es locais opt-in: status|enable|disable|report
|
|
10040
10065
|
forge-cli uninstall Sem nomes: remove o registro do PATH (legado)
|
|
10066
|
+
forge-cli registry Lista o cat\xE1logo PyPI e o que est\xE1 instalado via registry
|
|
10041
10067
|
|
|
10042
10068
|
Op\xE7\xF5es:
|
|
10043
10069
|
--dir <caminho> Diret\xF3rio de instala\xE7\xE3o (padr\xE3o: ./forge-workspace)
|
|
@@ -10056,6 +10082,8 @@ Op\xE7\xF5es:
|
|
|
10056
10082
|
--shallow Clona com git clone --depth 1 (s\xF3 clones novos)
|
|
10057
10083
|
--ref <rev> Fixa a Forja (uma s\xF3) em commit/tag (checkout destacado)
|
|
10058
10084
|
--version <tag> Fixa a Forja (uma s\xF3) em uma vers\xE3o (tag, com ou sem 'v')
|
|
10085
|
+
--method <m> M\xE9todo de instala\xE7\xE3o: git (clone+setup) ou registry (PyPI+venv)
|
|
10086
|
+
--offline No m\xE9todo registry: resolve s\xF3 pelo cache local do PyPI
|
|
10059
10087
|
--clean Com 'reinstall': remove e reclona na mesma revis\xE3o (com backup)
|
|
10060
10088
|
--profile <p> Instalador nativo da Forja: minimal|recommended|full
|
|
10061
10089
|
--scope <s> Instalador nativo da Forja: project|workspace|user
|
|
@@ -10145,9 +10173,11 @@ function parseCliArgs(argv) {
|
|
|
10145
10173
|
clean: { type: "boolean" },
|
|
10146
10174
|
ref: { type: "string" },
|
|
10147
10175
|
version: { type: "string" },
|
|
10176
|
+
method: { type: "string" },
|
|
10148
10177
|
"trust-custom": { type: "boolean" },
|
|
10149
10178
|
"run-after-sync-failure": { type: "boolean" },
|
|
10150
10179
|
rollback: { type: "boolean" },
|
|
10180
|
+
verify: { type: "boolean" },
|
|
10151
10181
|
help: { type: "boolean", short: "h" }
|
|
10152
10182
|
},
|
|
10153
10183
|
strict: true,
|
|
@@ -10179,6 +10209,9 @@ function parseCliArgs(argv) {
|
|
|
10179
10209
|
if (ref !== void 0 && !isValidRef(ref)) {
|
|
10180
10210
|
throw new CliError(`${S.errRefInvalid}: ${ref}`);
|
|
10181
10211
|
}
|
|
10212
|
+
if (values.method !== void 0 && !["git", "registry"].includes(values.method)) {
|
|
10213
|
+
throw new CliError(`--method inv\xE1lido: ${values.method} (use git|registry)`);
|
|
10214
|
+
}
|
|
10182
10215
|
return {
|
|
10183
10216
|
dir: values.dir,
|
|
10184
10217
|
all: values.all ?? false,
|
|
@@ -10202,6 +10235,7 @@ function parseCliArgs(argv) {
|
|
|
10202
10235
|
check: values.check ?? false,
|
|
10203
10236
|
native: values.native ?? false,
|
|
10204
10237
|
rollback: values.rollback ?? false,
|
|
10238
|
+
verify: values.verify ?? false,
|
|
10205
10239
|
host: values.host,
|
|
10206
10240
|
gatewayProfile: values["gateway-profile"],
|
|
10207
10241
|
verbose: values.verbose ?? false,
|
|
@@ -10211,6 +10245,7 @@ function parseCliArgs(argv) {
|
|
|
10211
10245
|
components: values.components,
|
|
10212
10246
|
clean: values.clean ?? false,
|
|
10213
10247
|
ref,
|
|
10248
|
+
method: values.method,
|
|
10214
10249
|
trustCustom: values["trust-custom"] ?? false,
|
|
10215
10250
|
runAfterSyncFailure: values["run-after-sync-failure"] ?? false,
|
|
10216
10251
|
positionals
|
|
@@ -10265,6 +10300,7 @@ function resolveCommand(cli) {
|
|
|
10265
10300
|
if (head === "recommend") return { command: "recommend", names: rest };
|
|
10266
10301
|
if (head === "env") return { command: "env", names: rest };
|
|
10267
10302
|
if (head === "hosts") return { command: "hosts", names: rest };
|
|
10303
|
+
if (head === "mcp") return { command: "mcp", names: rest };
|
|
10268
10304
|
if (head === "wizard") return { command: "wizard", names: rest };
|
|
10269
10305
|
if (head === "lock") return { command: "lock", names: rest };
|
|
10270
10306
|
if (head === "profile") return { command: "profile", names: rest };
|
|
@@ -10275,6 +10311,7 @@ function resolveCommand(cli) {
|
|
|
10275
10311
|
if (head === "ai") return { command: "ai", names: rest };
|
|
10276
10312
|
if (head === "bundle") return { command: "bundle", names: rest };
|
|
10277
10313
|
if (head === "learning") return { command: "learning", names: rest };
|
|
10314
|
+
if (head === "registry") return { command: "registry", names: rest };
|
|
10278
10315
|
if (head === "uninstall") return { command: "uninstall", names: rest };
|
|
10279
10316
|
if (REPO_COMMANDS.has(head)) return { command: head, names: rest };
|
|
10280
10317
|
return { command: "unknown", names: [], unknown: head };
|
|
@@ -10420,6 +10457,18 @@ var MENU_ITEMS = [
|
|
|
10420
10457
|
{ value: "doctor", label: S.menuDoctor },
|
|
10421
10458
|
{ value: "exit", label: S.menuExit }
|
|
10422
10459
|
];
|
|
10460
|
+
var REGISTRY_MENU_ACTIONS = [
|
|
10461
|
+
"install",
|
|
10462
|
+
"update",
|
|
10463
|
+
"reinstall",
|
|
10464
|
+
"uninstall",
|
|
10465
|
+
"status",
|
|
10466
|
+
"exit"
|
|
10467
|
+
];
|
|
10468
|
+
function menuItemsFor(cli) {
|
|
10469
|
+
if (cli.method !== "registry") return MENU_ITEMS;
|
|
10470
|
+
return MENU_ITEMS.filter((i3) => REGISTRY_MENU_ACTIONS.includes(i3.value));
|
|
10471
|
+
}
|
|
10423
10472
|
function shouldShowMenu(cli, tty3) {
|
|
10424
10473
|
return tty3 && cli.positionals.length === 0 && !cli.help && !cli.plan && !isNonInteractive(cli);
|
|
10425
10474
|
}
|
|
@@ -10440,12 +10489,12 @@ function actionToPositionals(action, names) {
|
|
|
10440
10489
|
return [action, ...names];
|
|
10441
10490
|
}
|
|
10442
10491
|
}
|
|
10443
|
-
async function askMenu(io = {}) {
|
|
10492
|
+
async function askMenu(io = {}, items = MENU_ITEMS) {
|
|
10444
10493
|
const choice = await select({
|
|
10445
10494
|
...io.input ? { input: io.input } : {},
|
|
10446
10495
|
...io.output ? { output: io.output } : {},
|
|
10447
10496
|
message: S.menuTitle,
|
|
10448
|
-
options:
|
|
10497
|
+
options: items.map((item) => ({
|
|
10449
10498
|
value: item.value,
|
|
10450
10499
|
label: item.label,
|
|
10451
10500
|
hint: item.hint
|
|
@@ -10458,6 +10507,15 @@ async function askMenu(io = {}) {
|
|
|
10458
10507
|
async function applyMenu(cli, deps) {
|
|
10459
10508
|
const action = await deps.ask();
|
|
10460
10509
|
if (action === "exit") return null;
|
|
10510
|
+
if (cli.method === "registry") {
|
|
10511
|
+
if (action === "status") return { ...cli, positionals: ["registry"] };
|
|
10512
|
+
if (action === "install") {
|
|
10513
|
+
const names3 = deps.pickRegistryCatalog ? await deps.pickRegistryCatalog() : [];
|
|
10514
|
+
return { ...cli, positionals: ["install", ...names3] };
|
|
10515
|
+
}
|
|
10516
|
+
const names2 = deps.pickRegistryInstalled ? await deps.pickRegistryInstalled() : [];
|
|
10517
|
+
return { ...cli, positionals: [action, ...names2] };
|
|
10518
|
+
}
|
|
10461
10519
|
if (action === "install") return cli;
|
|
10462
10520
|
const dir = cli.dir ? deps.resolveDir(cli.dir) : await deps.workspace();
|
|
10463
10521
|
const names = actionNeedsRepos(action) ? await deps.pickRepos(dir) : [];
|
|
@@ -11460,8 +11518,8 @@ var figlet = (() => {
|
|
|
11460
11518
|
const generatedTxt = fontOpts ? generateText(fontName, _reworkFontOpts(fontOpts, options), txt) : "";
|
|
11461
11519
|
if (next) next(null, generatedTxt);
|
|
11462
11520
|
return generatedTxt;
|
|
11463
|
-
} catch (
|
|
11464
|
-
const error =
|
|
11521
|
+
} catch (err2) {
|
|
11522
|
+
const error = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
11465
11523
|
if (next) {
|
|
11466
11524
|
next(error);
|
|
11467
11525
|
return "";
|
|
@@ -11487,8 +11545,8 @@ var figlet = (() => {
|
|
|
11487
11545
|
const result = [fontOpts, font.comment || ""];
|
|
11488
11546
|
if (callback) callback(null, fontOpts, font.comment);
|
|
11489
11547
|
return result;
|
|
11490
|
-
} catch (
|
|
11491
|
-
const error =
|
|
11548
|
+
} catch (err2) {
|
|
11549
|
+
const error = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
11492
11550
|
if (callback) {
|
|
11493
11551
|
callback(error);
|
|
11494
11552
|
return null;
|
|
@@ -11586,12 +11644,12 @@ var figlet = (() => {
|
|
|
11586
11644
|
if (callback) callback(null, result);
|
|
11587
11645
|
return result;
|
|
11588
11646
|
} catch (error) {
|
|
11589
|
-
const
|
|
11647
|
+
const err2 = error instanceof Error ? error : new Error(String(error));
|
|
11590
11648
|
if (callback) {
|
|
11591
|
-
callback(
|
|
11649
|
+
callback(err2);
|
|
11592
11650
|
return null;
|
|
11593
11651
|
}
|
|
11594
|
-
throw
|
|
11652
|
+
throw err2;
|
|
11595
11653
|
}
|
|
11596
11654
|
};
|
|
11597
11655
|
me.loadFontSync = function(name) {
|
|
@@ -11610,9 +11668,9 @@ var figlet = (() => {
|
|
|
11610
11668
|
}
|
|
11611
11669
|
if (callback) callback();
|
|
11612
11670
|
} catch (error) {
|
|
11613
|
-
const
|
|
11671
|
+
const err2 = error instanceof Error ? error : new Error(String(error));
|
|
11614
11672
|
if (callback) {
|
|
11615
|
-
callback(
|
|
11673
|
+
callback(err2);
|
|
11616
11674
|
return;
|
|
11617
11675
|
}
|
|
11618
11676
|
throw error;
|
|
@@ -11647,10 +11705,10 @@ nodeFiglet.loadFont = function(name, callback) {
|
|
|
11647
11705
|
resolve(nodeFiglet.figFonts[actualFontName].options);
|
|
11648
11706
|
return;
|
|
11649
11707
|
}
|
|
11650
|
-
fs.readFile(path9.join(nodeFiglet.defaults().fontPath, actualFontName + ".flf"), { encoding: "utf-8" }, (
|
|
11651
|
-
if (
|
|
11652
|
-
if (callback) callback(
|
|
11653
|
-
reject(
|
|
11708
|
+
fs.readFile(path9.join(nodeFiglet.defaults().fontPath, actualFontName + ".flf"), { encoding: "utf-8" }, (err2, fontData) => {
|
|
11709
|
+
if (err2) {
|
|
11710
|
+
if (callback) callback(err2);
|
|
11711
|
+
reject(err2);
|
|
11654
11712
|
return;
|
|
11655
11713
|
}
|
|
11656
11714
|
fontData = fontData + "";
|
|
@@ -11675,10 +11733,10 @@ nodeFiglet.loadFontSync = function(font) {
|
|
|
11675
11733
|
nodeFiglet.fonts = function(next) {
|
|
11676
11734
|
return new Promise((resolve, reject) => {
|
|
11677
11735
|
const fontList2 = [];
|
|
11678
|
-
fs.readdir(nodeFiglet.defaults().fontPath, (
|
|
11679
|
-
if (
|
|
11680
|
-
next && next(
|
|
11681
|
-
reject(
|
|
11736
|
+
fs.readdir(nodeFiglet.defaults().fontPath, (err2, files) => {
|
|
11737
|
+
if (err2) {
|
|
11738
|
+
next && next(err2);
|
|
11739
|
+
reject(err2);
|
|
11682
11740
|
return;
|
|
11683
11741
|
}
|
|
11684
11742
|
files.forEach((file) => {
|
|
@@ -14210,26 +14268,26 @@ var ContractValidator = class {
|
|
|
14210
14268
|
}
|
|
14211
14269
|
return this.validate(declared, document);
|
|
14212
14270
|
}
|
|
14213
|
-
check(value, schema,
|
|
14271
|
+
check(value, schema, path49, errs) {
|
|
14214
14272
|
if (typeof schema.$ref === "string") {
|
|
14215
14273
|
const target = schema.$ref;
|
|
14216
14274
|
if (!target.startsWith("forge/")) {
|
|
14217
|
-
errs.push({ code: "CONTRACT-REF", message: `external ref refused: ${target}`, path:
|
|
14275
|
+
errs.push({ code: "CONTRACT-REF", message: `external ref refused: ${target}`, path: path49 });
|
|
14218
14276
|
return;
|
|
14219
14277
|
}
|
|
14220
14278
|
const resolved = this.schemas[target];
|
|
14221
14279
|
if (!resolved) {
|
|
14222
|
-
errs.push({ code: "CONTRACT-UNKNOWN", message: `unknown contract: ${target}`, path:
|
|
14280
|
+
errs.push({ code: "CONTRACT-UNKNOWN", message: `unknown contract: ${target}`, path: path49 });
|
|
14223
14281
|
return;
|
|
14224
14282
|
}
|
|
14225
|
-
this.check(value, resolved,
|
|
14283
|
+
this.check(value, resolved, path49, errs);
|
|
14226
14284
|
return;
|
|
14227
14285
|
}
|
|
14228
14286
|
if ("const" in schema && value !== schema.const) {
|
|
14229
14287
|
errs.push({
|
|
14230
14288
|
code: "CONTRACT-CONST",
|
|
14231
14289
|
message: `expected ${JSON.stringify(schema.const)}`,
|
|
14232
|
-
path:
|
|
14290
|
+
path: path49
|
|
14233
14291
|
});
|
|
14234
14292
|
return;
|
|
14235
14293
|
}
|
|
@@ -14237,7 +14295,7 @@ var ContractValidator = class {
|
|
|
14237
14295
|
errs.push({
|
|
14238
14296
|
code: "CONTRACT-TYPE",
|
|
14239
14297
|
message: `expected type ${JSON.stringify(schema.type)}`,
|
|
14240
|
-
path:
|
|
14298
|
+
path: path49
|
|
14241
14299
|
});
|
|
14242
14300
|
return;
|
|
14243
14301
|
}
|
|
@@ -14245,27 +14303,27 @@ var ContractValidator = class {
|
|
|
14245
14303
|
errs.push({
|
|
14246
14304
|
code: "CONTRACT-ENUM",
|
|
14247
14305
|
message: `value not in ${JSON.stringify(schema.enum)}`,
|
|
14248
|
-
path:
|
|
14306
|
+
path: path49
|
|
14249
14307
|
});
|
|
14250
14308
|
return;
|
|
14251
14309
|
}
|
|
14252
14310
|
if (typeof value === "string") {
|
|
14253
14311
|
if (typeof schema.pattern === "string" && !new RegExp(schema.pattern).test(value)) {
|
|
14254
|
-
errs.push({ code: "CONTRACT-PATTERN", message: `does not match ${schema.pattern}`, path:
|
|
14312
|
+
errs.push({ code: "CONTRACT-PATTERN", message: `does not match ${schema.pattern}`, path: path49 });
|
|
14255
14313
|
}
|
|
14256
14314
|
if (typeof schema.minLength === "number" && value.length < schema.minLength) {
|
|
14257
|
-
errs.push({ code: "CONTRACT-LENGTH", message: "string too short", path:
|
|
14315
|
+
errs.push({ code: "CONTRACT-LENGTH", message: "string too short", path: path49 });
|
|
14258
14316
|
}
|
|
14259
14317
|
if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
|
|
14260
|
-
errs.push({ code: "CONTRACT-LENGTH", message: "string too long", path:
|
|
14318
|
+
errs.push({ code: "CONTRACT-LENGTH", message: "string too long", path: path49 });
|
|
14261
14319
|
}
|
|
14262
14320
|
}
|
|
14263
14321
|
if (typeof value === "number") {
|
|
14264
14322
|
if (typeof schema.min === "number" && value < schema.min) {
|
|
14265
|
-
errs.push({ code: "CONTRACT-RANGE", message: `< ${schema.min}`, path:
|
|
14323
|
+
errs.push({ code: "CONTRACT-RANGE", message: `< ${schema.min}`, path: path49 });
|
|
14266
14324
|
}
|
|
14267
14325
|
if (typeof schema.max === "number" && value > schema.max) {
|
|
14268
|
-
errs.push({ code: "CONTRACT-RANGE", message: `> ${schema.max}`, path:
|
|
14326
|
+
errs.push({ code: "CONTRACT-RANGE", message: `> ${schema.max}`, path: path49 });
|
|
14269
14327
|
}
|
|
14270
14328
|
}
|
|
14271
14329
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
@@ -14275,7 +14333,7 @@ var ContractValidator = class {
|
|
|
14275
14333
|
errs.push({
|
|
14276
14334
|
code: "CONTRACT-REQUIRED",
|
|
14277
14335
|
message: `missing '${req}'`,
|
|
14278
|
-
path: `${
|
|
14336
|
+
path: `${path49}/${req}`
|
|
14279
14337
|
});
|
|
14280
14338
|
}
|
|
14281
14339
|
}
|
|
@@ -14283,25 +14341,25 @@ var ContractValidator = class {
|
|
|
14283
14341
|
const additional = schema.additionalProperties;
|
|
14284
14342
|
for (const [key, val] of Object.entries(obj)) {
|
|
14285
14343
|
if (key in props) {
|
|
14286
|
-
this.check(val, props[key], `${
|
|
14344
|
+
this.check(val, props[key], `${path49}/${key}`, errs);
|
|
14287
14345
|
} else if (additional === false) {
|
|
14288
14346
|
errs.push({
|
|
14289
14347
|
code: "CONTRACT-UNKNOWN-FIELD",
|
|
14290
14348
|
message: `unexpected '${key}'`,
|
|
14291
|
-
path: `${
|
|
14349
|
+
path: `${path49}/${key}`
|
|
14292
14350
|
});
|
|
14293
14351
|
} else if (typeof additional === "object" && additional !== null) {
|
|
14294
|
-
this.check(val, additional, `${
|
|
14352
|
+
this.check(val, additional, `${path49}/${key}`, errs);
|
|
14295
14353
|
}
|
|
14296
14354
|
}
|
|
14297
14355
|
}
|
|
14298
14356
|
if (Array.isArray(value)) {
|
|
14299
14357
|
if (typeof schema.minItems === "number" && value.length < schema.minItems) {
|
|
14300
|
-
errs.push({ code: "CONTRACT-SIZE", message: "too few items", path:
|
|
14358
|
+
errs.push({ code: "CONTRACT-SIZE", message: "too few items", path: path49 });
|
|
14301
14359
|
}
|
|
14302
14360
|
const itemSchema = schema.items;
|
|
14303
14361
|
if (typeof itemSchema === "object" && itemSchema !== null) {
|
|
14304
|
-
value.forEach((item, i3) => this.check(item, itemSchema, `${
|
|
14362
|
+
value.forEach((item, i3) => this.check(item, itemSchema, `${path49}/${i3}`, errs));
|
|
14305
14363
|
}
|
|
14306
14364
|
}
|
|
14307
14365
|
}
|
|
@@ -15259,10 +15317,681 @@ async function uninstall(opts = {}) {
|
|
|
15259
15317
|
return { dirRemoved, shimsRemoved: dirRemoved, profilesTouched, pathRemoved };
|
|
15260
15318
|
}
|
|
15261
15319
|
|
|
15262
|
-
// src/
|
|
15320
|
+
// src/cmd/query.ts
|
|
15321
|
+
import os2 from "os";
|
|
15322
|
+
import { existsSync as existsSync12 } from "fs";
|
|
15323
|
+
|
|
15324
|
+
// src/registry/install.ts
|
|
15263
15325
|
import { createHash } from "crypto";
|
|
15264
|
-
import {
|
|
15326
|
+
import {
|
|
15327
|
+
chmodSync as chmodSync2,
|
|
15328
|
+
existsSync as existsSync10,
|
|
15329
|
+
mkdirSync as mkdirSync4,
|
|
15330
|
+
readdirSync as readdirSync3,
|
|
15331
|
+
readFileSync as readFileSync9,
|
|
15332
|
+
renameSync as renameSync2,
|
|
15333
|
+
rmSync as rmSync2,
|
|
15334
|
+
writeFileSync as writeFileSync4
|
|
15335
|
+
} from "fs";
|
|
15336
|
+
import path18 from "path";
|
|
15337
|
+
|
|
15338
|
+
// src/registry/pypi.ts
|
|
15339
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync8 } from "fs";
|
|
15265
15340
|
import path17 from "path";
|
|
15341
|
+
|
|
15342
|
+
// src/registry/versions.ts
|
|
15343
|
+
var VERSION_RE = /^(\d+(?:\.\d+)*)(?:(a|b|rc)(\d+))?(?:\.post(\d+))?(?:\.dev(\d+))?$/i;
|
|
15344
|
+
function parseVersion2(input) {
|
|
15345
|
+
const m3 = VERSION_RE.exec(input.trim());
|
|
15346
|
+
if (!m3) return null;
|
|
15347
|
+
const release = m3[1].split(".").map((x) => Number.parseInt(x, 10));
|
|
15348
|
+
if (release.some((n4) => !Number.isFinite(n4))) return null;
|
|
15349
|
+
const out = { release };
|
|
15350
|
+
if (m3[2]) out.pre = { tag: m3[2].toLowerCase(), n: Number.parseInt(m3[3], 10) };
|
|
15351
|
+
if (m3[4]) out.post = Number.parseInt(m3[4], 10);
|
|
15352
|
+
if (m3[5]) out.dev = Number.parseInt(m3[5], 10);
|
|
15353
|
+
return out;
|
|
15354
|
+
}
|
|
15355
|
+
var PRE_ORDER = { a: 0, b: 1, rc: 2 };
|
|
15356
|
+
function cmpRelease(a3, b) {
|
|
15357
|
+
const len = Math.max(a3.length, b.length);
|
|
15358
|
+
for (let i3 = 0; i3 < len; i3++) {
|
|
15359
|
+
const d = (a3[i3] ?? 0) - (b[i3] ?? 0);
|
|
15360
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
15361
|
+
}
|
|
15362
|
+
return 0;
|
|
15363
|
+
}
|
|
15364
|
+
function phaseRank(v) {
|
|
15365
|
+
if (v.dev !== void 0 && !v.pre && v.post === void 0) return 0;
|
|
15366
|
+
if (v.pre) return 1;
|
|
15367
|
+
if (v.post !== void 0) return 3;
|
|
15368
|
+
return 2;
|
|
15369
|
+
}
|
|
15370
|
+
function compareVersions3(a3, b) {
|
|
15371
|
+
const rel2 = cmpRelease(a3.release, b.release);
|
|
15372
|
+
if (rel2 !== 0) return rel2;
|
|
15373
|
+
const aPhase = phaseRank(a3);
|
|
15374
|
+
const bPhase = phaseRank(b);
|
|
15375
|
+
if (aPhase !== bPhase) return aPhase < bPhase ? -1 : 1;
|
|
15376
|
+
if (aPhase === 0) return (a3.dev ?? 0) - (b.dev ?? 0);
|
|
15377
|
+
if (aPhase === 1) {
|
|
15378
|
+
const tag2 = PRE_ORDER[a3.pre.tag] - PRE_ORDER[b.pre.tag];
|
|
15379
|
+
if (tag2 !== 0) return tag2;
|
|
15380
|
+
const pre = a3.pre.n - b.pre.n;
|
|
15381
|
+
if (pre !== 0) return pre;
|
|
15382
|
+
}
|
|
15383
|
+
if (aPhase === 1 || aPhase === 3) {
|
|
15384
|
+
const post = (a3.post ?? 0) - (b.post ?? 0);
|
|
15385
|
+
if (post !== 0) return post;
|
|
15386
|
+
}
|
|
15387
|
+
if (a3.dev === void 0 && b.dev === void 0) return 0;
|
|
15388
|
+
if (a3.dev === void 0) return 1;
|
|
15389
|
+
if (b.dev === void 0) return -1;
|
|
15390
|
+
return a3.dev - b.dev;
|
|
15391
|
+
}
|
|
15392
|
+
function isPrerelease(v) {
|
|
15393
|
+
return v.pre !== void 0 || v.dev !== void 0;
|
|
15394
|
+
}
|
|
15395
|
+
var SPEC_RE = /^(~=|==|!=|>=|<=|>|<)\s*([0-9][0-9a-zA-Z.]*?)(\.\*)?$/;
|
|
15396
|
+
function parseSpecifier(input) {
|
|
15397
|
+
const m3 = SPEC_RE.exec(input.trim());
|
|
15398
|
+
if (!m3) return null;
|
|
15399
|
+
const version = parseVersion2(m3[2]);
|
|
15400
|
+
if (!version) return null;
|
|
15401
|
+
const wildcard = Boolean(m3[3]);
|
|
15402
|
+
if (wildcard && m3[1] !== "==" && m3[1] !== "!=") return null;
|
|
15403
|
+
if (m3[1] === "~=" && version.release.length < 2) return null;
|
|
15404
|
+
return { op: m3[1], version, wildcard };
|
|
15405
|
+
}
|
|
15406
|
+
function matchWildcard(candidate, spec) {
|
|
15407
|
+
if (candidate.length < spec.length) return false;
|
|
15408
|
+
return spec.every((n4, i3) => candidate[i3] === n4);
|
|
15409
|
+
}
|
|
15410
|
+
function evalSpecifier(candidate, spec) {
|
|
15411
|
+
if (spec.wildcard) {
|
|
15412
|
+
const hit = matchWildcard(candidate.release, spec.version.release);
|
|
15413
|
+
return spec.op === "==" ? hit : !hit;
|
|
15414
|
+
}
|
|
15415
|
+
const cmp4 = compareVersions3(candidate, spec.version);
|
|
15416
|
+
switch (spec.op) {
|
|
15417
|
+
case "==":
|
|
15418
|
+
return cmp4 === 0;
|
|
15419
|
+
case "!=":
|
|
15420
|
+
return cmp4 !== 0;
|
|
15421
|
+
case ">=":
|
|
15422
|
+
return cmp4 >= 0;
|
|
15423
|
+
case ">":
|
|
15424
|
+
return cmp4 > 0;
|
|
15425
|
+
case "<=":
|
|
15426
|
+
return cmp4 <= 0;
|
|
15427
|
+
case "<":
|
|
15428
|
+
return cmp4 < 0;
|
|
15429
|
+
case "~=": {
|
|
15430
|
+
if (cmp4 < 0) return false;
|
|
15431
|
+
const bound = [...spec.version.release];
|
|
15432
|
+
bound.pop();
|
|
15433
|
+
bound[bound.length - 1] += 1;
|
|
15434
|
+
return cmpRelease(candidate.release, bound) < 0;
|
|
15435
|
+
}
|
|
15436
|
+
}
|
|
15437
|
+
}
|
|
15438
|
+
function satisfiesRequiresPython(requiresPython, candidate) {
|
|
15439
|
+
const parts = requiresPython.split(",").map((s) => s.trim()).filter(Boolean);
|
|
15440
|
+
if (!parts.length) return true;
|
|
15441
|
+
let anyUnknown = false;
|
|
15442
|
+
for (const part of parts) {
|
|
15443
|
+
const spec = parseSpecifier(part);
|
|
15444
|
+
if (!spec) {
|
|
15445
|
+
anyUnknown = true;
|
|
15446
|
+
continue;
|
|
15447
|
+
}
|
|
15448
|
+
if (!evalSpecifier(candidate, spec)) return false;
|
|
15449
|
+
}
|
|
15450
|
+
return anyUnknown ? null : true;
|
|
15451
|
+
}
|
|
15452
|
+
function latestStable(versions) {
|
|
15453
|
+
let best = null;
|
|
15454
|
+
let bestParsed = null;
|
|
15455
|
+
for (const raw of versions) {
|
|
15456
|
+
const parsed = parseVersion2(raw);
|
|
15457
|
+
if (!parsed || isPrerelease(parsed)) continue;
|
|
15458
|
+
if (!bestParsed || compareVersions3(parsed, bestParsed) > 0) {
|
|
15459
|
+
best = raw;
|
|
15460
|
+
bestParsed = parsed;
|
|
15461
|
+
}
|
|
15462
|
+
}
|
|
15463
|
+
return best;
|
|
15464
|
+
}
|
|
15465
|
+
|
|
15466
|
+
// src/registry/pypi.ts
|
|
15467
|
+
var PYPI_JSON = "https://pypi.org/pypi";
|
|
15468
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
15469
|
+
function pypiCacheFile(cacheDir, dist) {
|
|
15470
|
+
return path17.join(cacheDir, `${dist}.json`);
|
|
15471
|
+
}
|
|
15472
|
+
function asString(v) {
|
|
15473
|
+
return typeof v === "string" && v.length ? v : void 0;
|
|
15474
|
+
}
|
|
15475
|
+
function parseFiles(raw) {
|
|
15476
|
+
if (!Array.isArray(raw)) return [];
|
|
15477
|
+
const out = [];
|
|
15478
|
+
for (const item of raw) {
|
|
15479
|
+
if (!item || typeof item !== "object") continue;
|
|
15480
|
+
const rec = item;
|
|
15481
|
+
const filename = asString(rec.filename);
|
|
15482
|
+
const url = asString(rec.url);
|
|
15483
|
+
if (!filename || !url) continue;
|
|
15484
|
+
const digests = rec.digests;
|
|
15485
|
+
out.push({
|
|
15486
|
+
filename,
|
|
15487
|
+
url,
|
|
15488
|
+
sha256: digests ? asString(digests.sha256) : void 0,
|
|
15489
|
+
packagetype: asString(rec.packagetype),
|
|
15490
|
+
requiresPython: asString(rec.requires_python) ?? void 0,
|
|
15491
|
+
yanked: rec.yanked === true || typeof rec.yanked === "string" ? true : void 0
|
|
15492
|
+
});
|
|
15493
|
+
}
|
|
15494
|
+
return out;
|
|
15495
|
+
}
|
|
15496
|
+
function readCache(cacheDir, dist) {
|
|
15497
|
+
try {
|
|
15498
|
+
const raw = readFileSync8(pypiCacheFile(cacheDir, dist), "utf8");
|
|
15499
|
+
const wrap = JSON.parse(raw);
|
|
15500
|
+
if (typeof wrap.fetchedAt !== "number" || !wrap.payload) return null;
|
|
15501
|
+
const age = Date.now() - wrap.fetchedAt;
|
|
15502
|
+
return { payload: wrap.payload, fresh: age >= 0 && age < CACHE_TTL_MS };
|
|
15503
|
+
} catch {
|
|
15504
|
+
return null;
|
|
15505
|
+
}
|
|
15506
|
+
}
|
|
15507
|
+
function writeCache(cacheDir, dist, payload) {
|
|
15508
|
+
try {
|
|
15509
|
+
mkdirSync3(cacheDir, { recursive: true });
|
|
15510
|
+
writeFileAtomic(
|
|
15511
|
+
pypiCacheFile(cacheDir, dist),
|
|
15512
|
+
JSON.stringify({ fetchedAt: Date.now(), payload })
|
|
15513
|
+
);
|
|
15514
|
+
} catch {
|
|
15515
|
+
}
|
|
15516
|
+
}
|
|
15517
|
+
var MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
|
|
15518
|
+
async function defaultFetch(url) {
|
|
15519
|
+
const res = await fetch(url, {
|
|
15520
|
+
headers: { accept: "application/json" },
|
|
15521
|
+
signal: AbortSignal.timeout(15e3)
|
|
15522
|
+
});
|
|
15523
|
+
const declared = Number(res.headers.get("content-length") ?? "0");
|
|
15524
|
+
if (declared > MAX_PAYLOAD_BYTES) {
|
|
15525
|
+
void res.body?.cancel();
|
|
15526
|
+
throw new Error(`payload excede ${MAX_PAYLOAD_BYTES} bytes`);
|
|
15527
|
+
}
|
|
15528
|
+
const reader = res.body?.getReader();
|
|
15529
|
+
if (!reader) {
|
|
15530
|
+
const body = await res.text();
|
|
15531
|
+
if (body.length > MAX_PAYLOAD_BYTES) {
|
|
15532
|
+
throw new Error(`payload excede ${MAX_PAYLOAD_BYTES} bytes`);
|
|
15533
|
+
}
|
|
15534
|
+
return { status: res.status, body };
|
|
15535
|
+
}
|
|
15536
|
+
const chunks = [];
|
|
15537
|
+
let total = 0;
|
|
15538
|
+
for (; ; ) {
|
|
15539
|
+
const { done, value } = await reader.read();
|
|
15540
|
+
if (done) break;
|
|
15541
|
+
total += value.byteLength;
|
|
15542
|
+
if (total > MAX_PAYLOAD_BYTES) {
|
|
15543
|
+
await reader.cancel();
|
|
15544
|
+
throw new Error(`payload excede ${MAX_PAYLOAD_BYTES} bytes`);
|
|
15545
|
+
}
|
|
15546
|
+
chunks.push(value);
|
|
15547
|
+
}
|
|
15548
|
+
return { status: res.status, body: new TextDecoder().decode(Buffer.concat(chunks)) };
|
|
15549
|
+
}
|
|
15550
|
+
var RETRYABLE = (status) => status === 429 || status >= 500;
|
|
15551
|
+
async function fetchWithRetry(fetchImpl, url) {
|
|
15552
|
+
let lastError;
|
|
15553
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
15554
|
+
try {
|
|
15555
|
+
const res = await fetchImpl(url);
|
|
15556
|
+
if (!RETRYABLE(res.status) || attempt === 2) return res;
|
|
15557
|
+
} catch (error) {
|
|
15558
|
+
lastError = error;
|
|
15559
|
+
if (attempt === 2) throw error;
|
|
15560
|
+
}
|
|
15561
|
+
await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
|
|
15562
|
+
}
|
|
15563
|
+
throw lastError;
|
|
15564
|
+
}
|
|
15565
|
+
async function resolveRelease(dist, opts) {
|
|
15566
|
+
const fetchImpl = opts.fetchImpl ?? defaultFetch;
|
|
15567
|
+
let payload;
|
|
15568
|
+
let fromCache = false;
|
|
15569
|
+
let staleCache = false;
|
|
15570
|
+
if (!opts.offline) {
|
|
15571
|
+
try {
|
|
15572
|
+
const res = await fetchWithRetry(fetchImpl, `${PYPI_JSON}/${encodeURIComponent(dist)}/json`);
|
|
15573
|
+
if (res.status === 404) return { kind: "not-found", dist };
|
|
15574
|
+
if (res.status !== 200) {
|
|
15575
|
+
const cached = readCache(opts.cacheDir, dist);
|
|
15576
|
+
if (cached) {
|
|
15577
|
+
payload = cached.payload;
|
|
15578
|
+
fromCache = true;
|
|
15579
|
+
staleCache = !cached.fresh;
|
|
15580
|
+
} else {
|
|
15581
|
+
return { kind: "network", detail: `PyPI respondeu HTTP ${res.status}` };
|
|
15582
|
+
}
|
|
15583
|
+
} else {
|
|
15584
|
+
const parsed = JSON.parse(res.body);
|
|
15585
|
+
if (typeof parsed?.releases !== "object" || !parsed.releases) {
|
|
15586
|
+
const cached = readCache(opts.cacheDir, dist);
|
|
15587
|
+
if (cached) {
|
|
15588
|
+
payload = cached.payload;
|
|
15589
|
+
fromCache = true;
|
|
15590
|
+
staleCache = !cached.fresh;
|
|
15591
|
+
} else {
|
|
15592
|
+
return { kind: "malformed", dist, detail: "resposta sem campo releases" };
|
|
15593
|
+
}
|
|
15594
|
+
} else {
|
|
15595
|
+
if (!opts.ephemeral) writeCache(opts.cacheDir, dist, parsed);
|
|
15596
|
+
payload = parsed;
|
|
15597
|
+
}
|
|
15598
|
+
}
|
|
15599
|
+
} catch (error) {
|
|
15600
|
+
const cached = readCache(opts.cacheDir, dist);
|
|
15601
|
+
if (cached) {
|
|
15602
|
+
payload = cached.payload;
|
|
15603
|
+
fromCache = true;
|
|
15604
|
+
staleCache = !cached.fresh;
|
|
15605
|
+
} else {
|
|
15606
|
+
return {
|
|
15607
|
+
kind: "network",
|
|
15608
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
15609
|
+
};
|
|
15610
|
+
}
|
|
15611
|
+
}
|
|
15612
|
+
} else {
|
|
15613
|
+
const cached = readCache(opts.cacheDir, dist);
|
|
15614
|
+
if (!cached) return { kind: "offline-miss", dist };
|
|
15615
|
+
payload = cached.payload;
|
|
15616
|
+
fromCache = true;
|
|
15617
|
+
staleCache = !cached.fresh;
|
|
15618
|
+
}
|
|
15619
|
+
const releases = payload?.releases;
|
|
15620
|
+
if (!payload || typeof releases !== "object" || !releases) {
|
|
15621
|
+
return { kind: "malformed", dist, detail: "resposta sem campo releases" };
|
|
15622
|
+
}
|
|
15623
|
+
const installable = Object.keys(releases).filter((v) => {
|
|
15624
|
+
const files2 = parseFiles(releases[v]);
|
|
15625
|
+
if (!files2.length) return false;
|
|
15626
|
+
return files2.some((f) => !f.yanked);
|
|
15627
|
+
});
|
|
15628
|
+
const latest = latestStable(installable);
|
|
15629
|
+
let version = opts.pin ?? latest;
|
|
15630
|
+
if (!version) {
|
|
15631
|
+
return { kind: "malformed", dist, detail: "nenhuma vers\xE3o est\xE1vel instal\xE1vel" };
|
|
15632
|
+
}
|
|
15633
|
+
if (!Object.hasOwn(releases, version) && opts.pin) {
|
|
15634
|
+
const pinned = parseVersion2(version);
|
|
15635
|
+
if (pinned) {
|
|
15636
|
+
const canonical2 = Object.keys(releases).filter((k) => {
|
|
15637
|
+
const parsed = parseVersion2(k);
|
|
15638
|
+
return parsed !== null && compareVersions3(parsed, pinned) === 0;
|
|
15639
|
+
}).sort().at(0);
|
|
15640
|
+
if (canonical2) version = canonical2;
|
|
15641
|
+
}
|
|
15642
|
+
}
|
|
15643
|
+
if (!Object.hasOwn(releases, version)) {
|
|
15644
|
+
return {
|
|
15645
|
+
kind: "version-not-found",
|
|
15646
|
+
dist,
|
|
15647
|
+
requested: opts.pin ?? version,
|
|
15648
|
+
available: Object.keys(releases).filter((v) => parseVersion2(v)).sort((a3, b) => compareVersions3(parseVersion2(a3), parseVersion2(b)))
|
|
15649
|
+
};
|
|
15650
|
+
}
|
|
15651
|
+
const files = parseFiles(releases[version]);
|
|
15652
|
+
const requiresPython = files.find((f) => f.packagetype === "bdist_wheel" && f.requiresPython)?.requiresPython ?? files.find((f) => f.requiresPython)?.requiresPython ?? (version === latest ? asString(payload.info?.requires_python) : void 0);
|
|
15653
|
+
return {
|
|
15654
|
+
dist,
|
|
15655
|
+
version,
|
|
15656
|
+
requiresPython,
|
|
15657
|
+
files,
|
|
15658
|
+
source: opts.pin ? "pinned" : fromCache ? "cache" : "latest-stable",
|
|
15659
|
+
staleCache: staleCache || void 0,
|
|
15660
|
+
yanked: files.length && files.every((f) => f.yanked) ? true : void 0
|
|
15661
|
+
};
|
|
15662
|
+
}
|
|
15663
|
+
|
|
15664
|
+
// src/registry/install.ts
|
|
15665
|
+
function forgesRoot(home) {
|
|
15666
|
+
return path18.join(home, ".forge-cli", "forges");
|
|
15667
|
+
}
|
|
15668
|
+
function forgeStatePath(home) {
|
|
15669
|
+
return path18.join(forgesRoot(home), "state.json");
|
|
15670
|
+
}
|
|
15671
|
+
function forgeVenv(home, name) {
|
|
15672
|
+
return path18.join(forgesRoot(home), name, "venv");
|
|
15673
|
+
}
|
|
15674
|
+
function receiptPath(home, name) {
|
|
15675
|
+
return path18.join(forgesRoot(home), name, "install.json");
|
|
15676
|
+
}
|
|
15677
|
+
function loadForgeState(home) {
|
|
15678
|
+
const file = forgeStatePath(home);
|
|
15679
|
+
if (!existsSync10(file)) return { forges: {} };
|
|
15680
|
+
try {
|
|
15681
|
+
const raw = JSON.parse(readFileSync9(file, "utf8"));
|
|
15682
|
+
if (typeof raw.forges !== "object" || !raw.forges || Array.isArray(raw.forges)) {
|
|
15683
|
+
return { forges: {} };
|
|
15684
|
+
}
|
|
15685
|
+
const forges = {};
|
|
15686
|
+
for (const [k, v] of Object.entries(raw.forges)) {
|
|
15687
|
+
if (v && typeof v === "object" && !Array.isArray(v)) forges[k] = v;
|
|
15688
|
+
}
|
|
15689
|
+
return { ...raw, forges };
|
|
15690
|
+
} catch {
|
|
15691
|
+
try {
|
|
15692
|
+
writeFileSync4(`${file}.corrupt`, readFileSync9(file), { flag: "wx" });
|
|
15693
|
+
} catch {
|
|
15694
|
+
}
|
|
15695
|
+
return { forges: {} };
|
|
15696
|
+
}
|
|
15697
|
+
}
|
|
15698
|
+
function ghostEntry(name, receipt) {
|
|
15699
|
+
return {
|
|
15700
|
+
name,
|
|
15701
|
+
dist: receipt.dist,
|
|
15702
|
+
bin: path18.basename(receipt.bin).replace(/\.exe$/i, ""),
|
|
15703
|
+
minPython: receipt.python.split(".").slice(0, 2).join("."),
|
|
15704
|
+
origin: "official"
|
|
15705
|
+
};
|
|
15706
|
+
}
|
|
15707
|
+
function saveForgeState(home, state) {
|
|
15708
|
+
mkdirSync4(forgesRoot(home), { recursive: true });
|
|
15709
|
+
if (process.platform !== "win32") {
|
|
15710
|
+
try {
|
|
15711
|
+
chmodSync2(forgesRoot(home), 448);
|
|
15712
|
+
chmodSync2(path18.dirname(forgesRoot(home)), 448);
|
|
15713
|
+
} catch {
|
|
15714
|
+
}
|
|
15715
|
+
}
|
|
15716
|
+
writeFileAtomic(forgeStatePath(home), `${JSON.stringify(state, null, 2)}
|
|
15717
|
+
`);
|
|
15718
|
+
}
|
|
15719
|
+
function wheelCacheDir(cacheDir, dist) {
|
|
15720
|
+
return path18.join(path18.dirname(cacheDir), "wheels", dist);
|
|
15721
|
+
}
|
|
15722
|
+
function venvBin(venvDir, platform2 = process.platform) {
|
|
15723
|
+
return platform2 === "win32" ? path18.join(venvDir, "Scripts") : path18.join(venvDir, "bin");
|
|
15724
|
+
}
|
|
15725
|
+
function venvPython(venvDir, platform2 = process.platform) {
|
|
15726
|
+
return path18.join(venvBin(venvDir, platform2), platform2 === "win32" ? "python.exe" : "python");
|
|
15727
|
+
}
|
|
15728
|
+
async function execaRun(cmd, args, opts = {}) {
|
|
15729
|
+
const res = await execa(cmd, args, {
|
|
15730
|
+
reject: false,
|
|
15731
|
+
timeout: opts.timeoutMs ?? 3e5
|
|
15732
|
+
});
|
|
15733
|
+
return {
|
|
15734
|
+
exitCode: res.exitCode ?? 1,
|
|
15735
|
+
stdout: res.stdout,
|
|
15736
|
+
// Timeout preserva o stderr real quando o processo já escreveu algo.
|
|
15737
|
+
stderr: res.timedOut ? `timeout ap\xF3s ${opts.timeoutMs ?? 3e5}ms${res.stderr ? `: ${res.stderr}` : ""}` : res.stderr
|
|
15738
|
+
};
|
|
15739
|
+
}
|
|
15740
|
+
function selectInterpreter(entry2, resolved, pythons) {
|
|
15741
|
+
const floor = parseVersion2(entry2.minPython);
|
|
15742
|
+
let sawUnknownSpec = false;
|
|
15743
|
+
const usable = pythons.filter((p) => {
|
|
15744
|
+
if (floor && compareVersions3(p.parsed, floor) < 0) return false;
|
|
15745
|
+
if (!resolved.requiresPython) return true;
|
|
15746
|
+
const sat = satisfiesRequiresPython(resolved.requiresPython, p.parsed);
|
|
15747
|
+
if (sat === null) {
|
|
15748
|
+
sawUnknownSpec = true;
|
|
15749
|
+
return false;
|
|
15750
|
+
}
|
|
15751
|
+
return sat;
|
|
15752
|
+
});
|
|
15753
|
+
usable.sort((a3, b) => compareVersions3(b.parsed, a3.parsed));
|
|
15754
|
+
const stable = usable.filter((p) => !isPrerelease(p.parsed));
|
|
15755
|
+
const candidates = stable.length ? stable : usable;
|
|
15756
|
+
if (!candidates.length) {
|
|
15757
|
+
return {
|
|
15758
|
+
error: sawUnknownSpec ? {
|
|
15759
|
+
kind: "specifier-unknown",
|
|
15760
|
+
dist: entry2.dist,
|
|
15761
|
+
version: resolved.version,
|
|
15762
|
+
requires: resolved.requiresPython
|
|
15763
|
+
} : {
|
|
15764
|
+
kind: "python-incompatible",
|
|
15765
|
+
dist: entry2.dist,
|
|
15766
|
+
version: resolved.version,
|
|
15767
|
+
requires: [resolved.requiresPython, `>=${entry2.minPython} (piso forge-cli)`].filter(Boolean).join(" e "),
|
|
15768
|
+
have: pythons.map((p) => p.version).join(", ")
|
|
15769
|
+
}
|
|
15770
|
+
};
|
|
15771
|
+
}
|
|
15772
|
+
return { python: candidates[0] };
|
|
15773
|
+
}
|
|
15774
|
+
function checkDistHashes(distFiles, wheels) {
|
|
15775
|
+
const have = existsSync10(wheels) ? readdirSync3(wheels) : [];
|
|
15776
|
+
let verifiedHash;
|
|
15777
|
+
let verifiedFile;
|
|
15778
|
+
for (const f of distFiles) {
|
|
15779
|
+
if (!have.includes(f.filename) || !f.sha256) continue;
|
|
15780
|
+
const digest = createHash("sha256").update(readFileSync9(path18.join(wheels, f.filename))).digest("hex");
|
|
15781
|
+
if (digest !== f.sha256) {
|
|
15782
|
+
const culprit = path18.join(wheels, f.filename);
|
|
15783
|
+
try {
|
|
15784
|
+
rmSync2(`${culprit}.rejected`, { force: true });
|
|
15785
|
+
renameSync2(culprit, `${culprit}.rejected`);
|
|
15786
|
+
} catch {
|
|
15787
|
+
}
|
|
15788
|
+
return {
|
|
15789
|
+
detail: `${f.filename}: sha256 ${digest.slice(0, 12)}\u2026 != PyPI ${f.sha256.slice(0, 12)}\u2026 \u2014 artefato movido para ${f.filename}.rejected`
|
|
15790
|
+
};
|
|
15791
|
+
}
|
|
15792
|
+
if (f.packagetype === "bdist_wheel" || verifiedFile === void 0) {
|
|
15793
|
+
verifiedFile = f.filename;
|
|
15794
|
+
verifiedHash = digest;
|
|
15795
|
+
}
|
|
15796
|
+
}
|
|
15797
|
+
return { verifiedHash, verifiedFile };
|
|
15798
|
+
}
|
|
15799
|
+
function distArtifactFiles(files, dist) {
|
|
15800
|
+
const norm2 = dist.toLowerCase();
|
|
15801
|
+
const prefixes = [`${norm2.replace(/[-_.]/g, "_")}-`, `${norm2.replace(/[-_.]/g, "-")}-`];
|
|
15802
|
+
return files.filter((f) => prefixes.some((p) => f.filename.toLowerCase().startsWith(p)));
|
|
15803
|
+
}
|
|
15804
|
+
async function registryInstall(entry2, pin, deps) {
|
|
15805
|
+
const run = deps.run ?? execaRun;
|
|
15806
|
+
const platform2 = deps.platform ?? process.platform;
|
|
15807
|
+
if (!deps.pythons.length) {
|
|
15808
|
+
return { stage: "no-python", detail: "nenhum interpretador Python detectado" };
|
|
15809
|
+
}
|
|
15810
|
+
const venvDir = forgeVenv(deps.home, entry2.name);
|
|
15811
|
+
if (!deps.clean && existsSync10(venvDir)) {
|
|
15812
|
+
return {
|
|
15813
|
+
stage: "already-installed",
|
|
15814
|
+
detail: `${entry2.name}: j\xE1 instalado em ${venvDir} \u2014 reinstale com --clean`
|
|
15815
|
+
};
|
|
15816
|
+
}
|
|
15817
|
+
const resolved = await resolveRelease(entry2.dist, {
|
|
15818
|
+
pin,
|
|
15819
|
+
offline: deps.offline,
|
|
15820
|
+
cacheDir: deps.cacheDir,
|
|
15821
|
+
fetchImpl: deps.fetchImpl
|
|
15822
|
+
});
|
|
15823
|
+
if ("kind" in resolved) return { stage: "resolve", error: resolved };
|
|
15824
|
+
const picked = selectInterpreter(entry2, resolved, deps.pythons);
|
|
15825
|
+
if ("error" in picked) return { stage: "resolve", error: picked.error };
|
|
15826
|
+
const python = picked.python;
|
|
15827
|
+
if (deps.clean) {
|
|
15828
|
+
rmSync2(path18.join(forgesRoot(deps.home), entry2.name), { recursive: true, force: true });
|
|
15829
|
+
const st = loadForgeState(deps.home);
|
|
15830
|
+
if (st.forges[entry2.name]) {
|
|
15831
|
+
delete st.forges[entry2.name];
|
|
15832
|
+
saveForgeState(deps.home, st);
|
|
15833
|
+
}
|
|
15834
|
+
}
|
|
15835
|
+
const wheels = wheelCacheDir(deps.cacheDir, entry2.dist);
|
|
15836
|
+
const forgeDir = path18.join(forgesRoot(deps.home), entry2.name);
|
|
15837
|
+
const cleanup = () => {
|
|
15838
|
+
rmSync2(venvDir, { recursive: true, force: true });
|
|
15839
|
+
if (existsSync10(forgeDir) && !readdirSync3(forgeDir).length) {
|
|
15840
|
+
rmSync2(forgeDir, { recursive: true, force: true });
|
|
15841
|
+
}
|
|
15842
|
+
};
|
|
15843
|
+
const venv = await run(python.exe[0], [...python.exe.slice(1), "-m", "venv", venvDir]);
|
|
15844
|
+
if (venv.exitCode !== 0) {
|
|
15845
|
+
cleanup();
|
|
15846
|
+
return {
|
|
15847
|
+
stage: "venv",
|
|
15848
|
+
detail: redactSecrets(venv.stderr ?? venv.stdout ?? "falha ao criar venv")
|
|
15849
|
+
};
|
|
15850
|
+
}
|
|
15851
|
+
const vpy = venvPython(venvDir, platform2);
|
|
15852
|
+
const spec = `${entry2.dist}==${resolved.version}`;
|
|
15853
|
+
mkdirSync4(wheels, { recursive: true });
|
|
15854
|
+
const distFiles = distArtifactFiles(resolved.files, entry2.dist);
|
|
15855
|
+
let verifiedHash;
|
|
15856
|
+
let verifiedFile;
|
|
15857
|
+
if (deps.offline) {
|
|
15858
|
+
const have = existsSync10(wheels) ? readdirSync3(wheels) : [];
|
|
15859
|
+
if (!distFiles.length || !distFiles.some((f) => have.includes(f.filename))) {
|
|
15860
|
+
cleanup();
|
|
15861
|
+
return {
|
|
15862
|
+
stage: "offline-missing-artifact",
|
|
15863
|
+
detail: `${spec}: sem wheel/sdist em ${wheels} \u2014 instale online uma vez`
|
|
15864
|
+
};
|
|
15865
|
+
}
|
|
15866
|
+
const check = checkDistHashes(distFiles, wheels);
|
|
15867
|
+
if ("detail" in check) {
|
|
15868
|
+
cleanup();
|
|
15869
|
+
return { stage: "hash-mismatch", detail: check.detail };
|
|
15870
|
+
}
|
|
15871
|
+
verifiedHash = check.verifiedHash;
|
|
15872
|
+
verifiedFile = check.verifiedFile;
|
|
15873
|
+
} else {
|
|
15874
|
+
const dl = await run(vpy, [
|
|
15875
|
+
"-m",
|
|
15876
|
+
"pip",
|
|
15877
|
+
"download",
|
|
15878
|
+
"--disable-pip-version-check",
|
|
15879
|
+
"--no-input",
|
|
15880
|
+
"--dest",
|
|
15881
|
+
wheels,
|
|
15882
|
+
spec
|
|
15883
|
+
]);
|
|
15884
|
+
if (dl.exitCode !== 0) {
|
|
15885
|
+
cleanup();
|
|
15886
|
+
return {
|
|
15887
|
+
stage: "download",
|
|
15888
|
+
// stderr do pip pode ecoar credenciais de PIP_INDEX_URL — redação
|
|
15889
|
+
// vale para stdout-JSON, --report e log humano.
|
|
15890
|
+
detail: redactSecrets((dl.stderr ?? dl.stdout ?? "falha no pip download").trim())
|
|
15891
|
+
};
|
|
15892
|
+
}
|
|
15893
|
+
const check = checkDistHashes(distFiles, wheels);
|
|
15894
|
+
if ("detail" in check) {
|
|
15895
|
+
cleanup();
|
|
15896
|
+
return { stage: "hash-mismatch", detail: check.detail };
|
|
15897
|
+
}
|
|
15898
|
+
verifiedHash = check.verifiedHash;
|
|
15899
|
+
verifiedFile = check.verifiedFile;
|
|
15900
|
+
}
|
|
15901
|
+
if (!verifiedFile) {
|
|
15902
|
+
cleanup();
|
|
15903
|
+
return {
|
|
15904
|
+
stage: "hash-mismatch",
|
|
15905
|
+
detail: `${spec}: nenhum artefato da dist com sha256 verific\xE1vel nos metadados`
|
|
15906
|
+
};
|
|
15907
|
+
}
|
|
15908
|
+
const pipArgs = [
|
|
15909
|
+
"-m",
|
|
15910
|
+
"pip",
|
|
15911
|
+
"install",
|
|
15912
|
+
"--disable-pip-version-check",
|
|
15913
|
+
"--no-input",
|
|
15914
|
+
// `--find-links` só no offline: online as dependências resolvem no
|
|
15915
|
+
// índice, não em wheels cacheadas potencialmente velhas/adulteradas.
|
|
15916
|
+
// Limite conhecido (§11.2): offline, deps transitivas resolvem por
|
|
15917
|
+
// nome no --find-links sem pin de hash.
|
|
15918
|
+
...deps.offline ? ["--no-index", "--find-links", wheels] : [],
|
|
15919
|
+
// Caminho explícito do artefato verificado — pip não resolve por nome,
|
|
15920
|
+
// então um arquivo plantado com a mesma dist==versão nunca é escolhido.
|
|
15921
|
+
path18.join(wheels, verifiedFile)
|
|
15922
|
+
];
|
|
15923
|
+
const pip = await run(vpy, pipArgs);
|
|
15924
|
+
if (pip.exitCode !== 0) {
|
|
15925
|
+
cleanup();
|
|
15926
|
+
return {
|
|
15927
|
+
stage: "pip",
|
|
15928
|
+
detail: redactSecrets((pip.stderr ?? pip.stdout ?? "falha no pip install").trim())
|
|
15929
|
+
};
|
|
15930
|
+
}
|
|
15931
|
+
const binPath = path18.join(
|
|
15932
|
+
venvBin(venvDir, platform2),
|
|
15933
|
+
platform2 === "win32" ? `${entry2.bin}.exe` : entry2.bin
|
|
15934
|
+
);
|
|
15935
|
+
const verifyCmd = [binPath, "--help"];
|
|
15936
|
+
const verify = await run(binPath, ["--help"]);
|
|
15937
|
+
const receipt = {
|
|
15938
|
+
forge: entry2.name,
|
|
15939
|
+
method: "pypi",
|
|
15940
|
+
dist: entry2.dist,
|
|
15941
|
+
version: resolved.version,
|
|
15942
|
+
requiresPython: resolved.requiresPython,
|
|
15943
|
+
python: python.version,
|
|
15944
|
+
source: resolved.source,
|
|
15945
|
+
venv: venvDir,
|
|
15946
|
+
bin: binPath,
|
|
15947
|
+
installedAt: (deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))(),
|
|
15948
|
+
verify: { command: verifyCmd, ok: verify.exitCode === 0 },
|
|
15949
|
+
files: resolved.files.map((f) => ({
|
|
15950
|
+
filename: f.filename,
|
|
15951
|
+
sha256: f.sha256,
|
|
15952
|
+
packagetype: f.packagetype
|
|
15953
|
+
})),
|
|
15954
|
+
verifiedHash,
|
|
15955
|
+
provenance: {
|
|
15956
|
+
registry: "pypi.org",
|
|
15957
|
+
file: verifiedFile,
|
|
15958
|
+
url: resolved.files.find((f) => f.filename === verifiedFile)?.url,
|
|
15959
|
+
resolvedVia: deps.offline ? "offline-cache" : "pypi"
|
|
15960
|
+
},
|
|
15961
|
+
yanked: resolved.yanked,
|
|
15962
|
+
staleCache: resolved.staleCache
|
|
15963
|
+
};
|
|
15964
|
+
mkdirSync4(path18.dirname(receiptPath(deps.home, entry2.name)), { recursive: true });
|
|
15965
|
+
writeFileAtomic(receiptPath(deps.home, entry2.name), `${JSON.stringify(receipt, null, 2)}
|
|
15966
|
+
`);
|
|
15967
|
+
const state = loadForgeState(deps.home);
|
|
15968
|
+
state.forges[entry2.name] = receipt;
|
|
15969
|
+
saveForgeState(deps.home, state);
|
|
15970
|
+
if (verify.exitCode !== 0) {
|
|
15971
|
+
return { stage: "verify", detail: `${entry2.bin} --help saiu com c\xF3digo ${verify.exitCode}` };
|
|
15972
|
+
}
|
|
15973
|
+
return receipt;
|
|
15974
|
+
}
|
|
15975
|
+
function registryUninstall(target, deps) {
|
|
15976
|
+
const dir = resolveInside(forgesRoot(deps.home), target.name);
|
|
15977
|
+
if (!dir)
|
|
15978
|
+
return { removed: false, receiptRemoved: false, cacheRemoved: false, refused: "unsafe-name" };
|
|
15979
|
+
const removed = existsSync10(dir);
|
|
15980
|
+
if (removed) rmSync2(dir, { recursive: true, force: true });
|
|
15981
|
+
const wheels = target.dist && isSafeRepoName(target.dist) ? resolveInside(path18.join(deps.home, ".forge-cli", "cache", "wheels"), target.dist) : null;
|
|
15982
|
+
const cacheRemoved = wheels !== null && existsSync10(wheels);
|
|
15983
|
+
if (wheels) rmSync2(wheels, { recursive: true, force: true });
|
|
15984
|
+
const state = loadForgeState(deps.home);
|
|
15985
|
+
const hadReceipt = Object.hasOwn(state.forges, target.name);
|
|
15986
|
+
if (hadReceipt) delete state.forges[target.name];
|
|
15987
|
+
saveForgeState(deps.home, state);
|
|
15988
|
+
return { removed, receiptRemoved: hadReceipt, cacheRemoved };
|
|
15989
|
+
}
|
|
15990
|
+
|
|
15991
|
+
// src/compat.ts
|
|
15992
|
+
import { createHash as createHash2 } from "crypto";
|
|
15993
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
15994
|
+
import path19 from "path";
|
|
15266
15995
|
var ECOSYSTEM_SCHEMA = "forge/EcosystemCompatibility/v1";
|
|
15267
15996
|
function isRecord(v) {
|
|
15268
15997
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -15349,10 +16078,10 @@ function manifestProblems(doc) {
|
|
|
15349
16078
|
return problems;
|
|
15350
16079
|
}
|
|
15351
16080
|
function loadCompatManifest(manifestPath) {
|
|
15352
|
-
if (!
|
|
16081
|
+
if (!existsSync11(manifestPath)) return null;
|
|
15353
16082
|
let raw;
|
|
15354
16083
|
try {
|
|
15355
|
-
raw =
|
|
16084
|
+
raw = readFileSync10(manifestPath, "utf-8");
|
|
15356
16085
|
} catch {
|
|
15357
16086
|
return null;
|
|
15358
16087
|
}
|
|
@@ -15369,12 +16098,12 @@ function loadCompatManifest(manifestPath) {
|
|
|
15369
16098
|
release: d.release,
|
|
15370
16099
|
components: d.components,
|
|
15371
16100
|
contracts: d.contracts,
|
|
15372
|
-
fileSha256:
|
|
16101
|
+
fileSha256: createHash2("sha256").update(raw).digest("hex"),
|
|
15373
16102
|
path: manifestPath
|
|
15374
16103
|
};
|
|
15375
16104
|
}
|
|
15376
16105
|
function defaultManifestPath(workspaceDir) {
|
|
15377
|
-
return
|
|
16106
|
+
return path19.join(workspaceDir, "the-forge", "ecosystem-compatibility.json");
|
|
15378
16107
|
}
|
|
15379
16108
|
function compatVerdict(manifest, component, version) {
|
|
15380
16109
|
const entry2 = manifest.components.find((c4) => c4.component === component);
|
|
@@ -15382,21 +16111,21 @@ function compatVerdict(manifest, component, version) {
|
|
|
15382
16111
|
return entry2.version === version ? "certified" : "incompatible";
|
|
15383
16112
|
}
|
|
15384
16113
|
function readDeclaredVersion(repoDir) {
|
|
15385
|
-
const pkg =
|
|
15386
|
-
if (
|
|
16114
|
+
const pkg = path19.join(repoDir, "package.json");
|
|
16115
|
+
if (existsSync11(pkg)) {
|
|
15387
16116
|
try {
|
|
15388
|
-
const parsed = JSON.parse(
|
|
16117
|
+
const parsed = JSON.parse(readFileSync10(pkg, "utf-8"));
|
|
15389
16118
|
if (isRecord(parsed) && typeof parsed.version === "string" && parsed.version) {
|
|
15390
16119
|
return parsed.version;
|
|
15391
16120
|
}
|
|
15392
16121
|
} catch {
|
|
15393
16122
|
}
|
|
15394
16123
|
}
|
|
15395
|
-
const pyproject =
|
|
15396
|
-
if (
|
|
16124
|
+
const pyproject = path19.join(repoDir, "pyproject.toml");
|
|
16125
|
+
if (existsSync11(pyproject)) {
|
|
15397
16126
|
try {
|
|
15398
16127
|
const table = /^\[project\][^\n]*\n((?:(?!\[)[^\n]*(?:\n|$))*)/m.exec(
|
|
15399
|
-
|
|
16128
|
+
readFileSync10(pyproject, "utf-8")
|
|
15400
16129
|
);
|
|
15401
16130
|
const m3 = table && /^version\s*=\s*"([^"]+)"/m.exec(table[1]);
|
|
15402
16131
|
if (m3) return m3[1];
|
|
@@ -15410,25 +16139,49 @@ function readDeclaredVersion(repoDir) {
|
|
|
15410
16139
|
async function runListCommand(cli) {
|
|
15411
16140
|
const targetDir = resolveTargetDir(cli.dir);
|
|
15412
16141
|
const state = loadState(targetDir);
|
|
15413
|
-
const
|
|
16142
|
+
const registry = loadForgeState(os2.homedir()).forges;
|
|
16143
|
+
const body = catalogFor(targetDir).map((repo) => {
|
|
16144
|
+
const line = listLine(repo, state?.repos[repo.name]);
|
|
16145
|
+
const reg = registry[repo.name];
|
|
16146
|
+
return reg ? `${line} ${theme.dim(`+ pypi ${reg.dist}==${reg.version}`)}` : line;
|
|
16147
|
+
}).join("\n");
|
|
15414
16148
|
if (cli.jsonReport) {
|
|
15415
16149
|
emitJson(
|
|
15416
16150
|
catalogFor(targetDir).map((repo) => ({
|
|
15417
16151
|
name: repo.name,
|
|
15418
16152
|
url: repo.url,
|
|
15419
16153
|
description: repo.description,
|
|
15420
|
-
last: state?.repos[repo.name] ?? null
|
|
16154
|
+
last: state?.repos[repo.name] ?? null,
|
|
16155
|
+
registry: registry[repo.name] ? { dist: registry[repo.name].dist, version: registry[repo.name].version } : null
|
|
15421
16156
|
}))
|
|
15422
16157
|
);
|
|
15423
16158
|
}
|
|
15424
16159
|
note(body, S.listTitle);
|
|
15425
16160
|
process.exit(0);
|
|
15426
16161
|
}
|
|
16162
|
+
function mergeRegistryPhase(infos, registry) {
|
|
16163
|
+
for (const i3 of infos) {
|
|
16164
|
+
const reg = Object.hasOwn(registry, i3.name) ? registry[i3.name] : void 0;
|
|
16165
|
+
if (!reg || typeof reg !== "object") continue;
|
|
16166
|
+
const tag2 = `pypi ${reg.dist ?? "?"}==${reg.version ?? "?"}`;
|
|
16167
|
+
const binMissing = typeof reg.bin !== "string" || !existsSync12(reg.bin);
|
|
16168
|
+
if (binMissing) {
|
|
16169
|
+
if (i3.phase === "not-installed" || i3.phase === "installed") i3.phase = "drifted";
|
|
16170
|
+
i3.describe = [i3.describe, `${tag2} (bin ausente)`].filter(Boolean).join(" + ");
|
|
16171
|
+
} else if (i3.phase === "not-installed") {
|
|
16172
|
+
i3.phase = "installed";
|
|
16173
|
+
i3.describe = `${tag2}${reg.verify?.ok ? "" : " (verify falhou)"}`;
|
|
16174
|
+
} else {
|
|
16175
|
+
i3.describe = [i3.describe, tag2].filter(Boolean).join(" + ");
|
|
16176
|
+
}
|
|
16177
|
+
}
|
|
16178
|
+
}
|
|
15427
16179
|
async function runStatusCommand(cli, names) {
|
|
15428
16180
|
const targetDir = resolveTargetDir(cli.dir);
|
|
15429
16181
|
const catalog = catalogFor(targetDir);
|
|
15430
16182
|
const wanted = names.length ? catalog.filter((r2) => names.includes(r2.name)) : catalog;
|
|
15431
16183
|
const infos = await collectStatus(wanted, targetDir, loadState(targetDir));
|
|
16184
|
+
mergeRegistryPhase(infos, loadForgeState(os2.homedir()).forges);
|
|
15432
16185
|
const label = {
|
|
15433
16186
|
"not-installed": S.statusNotInstalled,
|
|
15434
16187
|
cloned: S.statusCloned,
|
|
@@ -15477,7 +16230,8 @@ async function runUpdateCheckCommand(cli, names) {
|
|
|
15477
16230
|
const compat = compatOf(repo.name, dir);
|
|
15478
16231
|
const check = await updateCheck(dir);
|
|
15479
16232
|
if (check.failure) {
|
|
15480
|
-
|
|
16233
|
+
const label = check.failure === "no-upstream" ? S.updateNoUpstream : `${S.updateCheckFailed} (${check.failure})`;
|
|
16234
|
+
lines.push(`${repo.name} \u2014 ${theme.warn(label)}`);
|
|
15481
16235
|
rows.push({ name: repo.name, state: "unknown", behind: 0, ahead: 0, failure: check.failure });
|
|
15482
16236
|
} else if (check.behind > 0) {
|
|
15483
16237
|
pending++;
|
|
@@ -15548,21 +16302,21 @@ async function runVersionsCommand(cli, names) {
|
|
|
15548
16302
|
}
|
|
15549
16303
|
|
|
15550
16304
|
// src/doctor2/index.ts
|
|
15551
|
-
import
|
|
15552
|
-
import
|
|
16305
|
+
import os5 from "os";
|
|
16306
|
+
import path30 from "path";
|
|
15553
16307
|
|
|
15554
16308
|
// src/hosts/claude.ts
|
|
15555
|
-
import
|
|
16309
|
+
import path21 from "path";
|
|
15556
16310
|
|
|
15557
16311
|
// src/hosts/types.ts
|
|
15558
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
15559
|
-
import
|
|
15560
|
-
import
|
|
16312
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync11, readdirSync as readdirSync4, rmSync as rmSync3 } from "fs";
|
|
16313
|
+
import os3 from "os";
|
|
16314
|
+
import path20 from "path";
|
|
15561
16315
|
var defaultProbe = {
|
|
15562
16316
|
output: (command, args) => commandOutput(command, args)
|
|
15563
16317
|
};
|
|
15564
16318
|
function hostHome(home) {
|
|
15565
|
-
return home ||
|
|
16319
|
+
return home || os3.homedir();
|
|
15566
16320
|
}
|
|
15567
16321
|
function parseVersionOutput(out) {
|
|
15568
16322
|
if (!out) return null;
|
|
@@ -15572,7 +16326,7 @@ function parseVersionOutput(out) {
|
|
|
15572
16326
|
return first && first !== "ok" ? first : null;
|
|
15573
16327
|
}
|
|
15574
16328
|
function existingConfigPaths(home, rels) {
|
|
15575
|
-
return rels.map((rel2) =>
|
|
16329
|
+
return rels.map((rel2) => path20.join(home, rel2)).filter((p) => existsSync13(p));
|
|
15576
16330
|
}
|
|
15577
16331
|
async function detectHost(home, spec) {
|
|
15578
16332
|
let binary = false;
|
|
@@ -15615,9 +16369,9 @@ async function detectHost(home, spec) {
|
|
|
15615
16369
|
};
|
|
15616
16370
|
}
|
|
15617
16371
|
function readJsonObject(file) {
|
|
15618
|
-
if (!
|
|
16372
|
+
if (!existsSync13(file)) return null;
|
|
15619
16373
|
try {
|
|
15620
|
-
const parsed = JSON.parse(
|
|
16374
|
+
const parsed = JSON.parse(readFileSync11(file, "utf8"));
|
|
15621
16375
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : "invalid";
|
|
15622
16376
|
} catch {
|
|
15623
16377
|
return "invalid";
|
|
@@ -15653,22 +16407,22 @@ function backupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
|
15653
16407
|
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}${p(d.getMilliseconds(), 3)}`;
|
|
15654
16408
|
}
|
|
15655
16409
|
function backupsRoot(home) {
|
|
15656
|
-
return
|
|
16410
|
+
return path20.join(artifactsRoot(home), "backups");
|
|
15657
16411
|
}
|
|
15658
16412
|
function backupDirFor(home, hostId) {
|
|
15659
|
-
return
|
|
16413
|
+
return path20.join(backupsRoot(home), `hosts-${hostId}-${backupTimestamp()}`);
|
|
15660
16414
|
}
|
|
15661
16415
|
function backupTargets(dir, hostId, targets) {
|
|
15662
|
-
|
|
16416
|
+
mkdirSync5(dir, { recursive: true });
|
|
15663
16417
|
const files = targets.map((target) => {
|
|
15664
|
-
const name =
|
|
15665
|
-
const absent = !
|
|
15666
|
-
if (absent) writeFileAtomic(
|
|
15667
|
-
else copyFileSync2(target,
|
|
16418
|
+
const name = path20.basename(target);
|
|
16419
|
+
const absent = !existsSync13(target);
|
|
16420
|
+
if (absent) writeFileAtomic(path20.join(dir, `${name}.absent`), "");
|
|
16421
|
+
else copyFileSync2(target, path20.join(dir, name));
|
|
15668
16422
|
return { backup: name, target, absent };
|
|
15669
16423
|
});
|
|
15670
16424
|
writeFileAtomic(
|
|
15671
|
-
|
|
16425
|
+
path20.join(dir, "manifest.json"),
|
|
15672
16426
|
`${JSON.stringify({ host: hostId, files }, null, 2)}
|
|
15673
16427
|
`
|
|
15674
16428
|
);
|
|
@@ -15676,23 +16430,23 @@ function backupTargets(dir, hostId, targets) {
|
|
|
15676
16430
|
function latestBackupDir(home, hostId) {
|
|
15677
16431
|
let names;
|
|
15678
16432
|
try {
|
|
15679
|
-
names =
|
|
16433
|
+
names = readdirSync4(backupsRoot(home));
|
|
15680
16434
|
} catch {
|
|
15681
16435
|
return null;
|
|
15682
16436
|
}
|
|
15683
16437
|
const hit = names.filter((n4) => n4.startsWith(`hosts-${hostId}-`)).sort().reverse()[0];
|
|
15684
|
-
return hit ?
|
|
16438
|
+
return hit ? path20.join(backupsRoot(home), hit) : null;
|
|
15685
16439
|
}
|
|
15686
16440
|
function restoreBackupDir(dir) {
|
|
15687
|
-
const manifest = JSON.parse(
|
|
16441
|
+
const manifest = JSON.parse(readFileSync11(path20.join(dir, "manifest.json"), "utf8"));
|
|
15688
16442
|
const restored = [];
|
|
15689
16443
|
const removed = [];
|
|
15690
16444
|
for (const entry2 of manifest.files ?? []) {
|
|
15691
16445
|
if (entry2.absent) {
|
|
15692
|
-
|
|
16446
|
+
rmSync3(entry2.target, { force: true });
|
|
15693
16447
|
removed.push(entry2.target);
|
|
15694
16448
|
} else {
|
|
15695
|
-
writeFileAtomic(entry2.target,
|
|
16449
|
+
writeFileAtomic(entry2.target, readFileSync11(path20.join(dir, entry2.backup), "utf8"));
|
|
15696
16450
|
restored.push(entry2.target);
|
|
15697
16451
|
}
|
|
15698
16452
|
}
|
|
@@ -15741,7 +16495,7 @@ async function applyHostPlan(home, plan, resolveWrite, opts = {}) {
|
|
|
15741
16495
|
);
|
|
15742
16496
|
for (const w of writes) {
|
|
15743
16497
|
try {
|
|
15744
|
-
|
|
16498
|
+
mkdirSync5(path20.dirname(w.target), { recursive: true });
|
|
15745
16499
|
writeFileAtomic(w.target, w.content);
|
|
15746
16500
|
applied.push(w.target);
|
|
15747
16501
|
} catch (error) {
|
|
@@ -15788,9 +16542,9 @@ async function rollbackHostPlan(home, plan, backup) {
|
|
|
15788
16542
|
|
|
15789
16543
|
// src/hosts/claude.ts
|
|
15790
16544
|
var CONFIG_FILE = ".claude.json";
|
|
15791
|
-
var CONFIG_REL = [CONFIG_FILE, ".claude",
|
|
16545
|
+
var CONFIG_REL = [CONFIG_FILE, ".claude", path21.join(".claude", "settings.json")];
|
|
15792
16546
|
function targetFor(home) {
|
|
15793
|
-
return
|
|
16547
|
+
return path21.join(home, CONFIG_FILE);
|
|
15794
16548
|
}
|
|
15795
16549
|
function createClaudeAdapter(probe = defaultProbe) {
|
|
15796
16550
|
return {
|
|
@@ -15858,12 +16612,12 @@ function createClaudeAdapter(probe = defaultProbe) {
|
|
|
15858
16612
|
var claudeAdapter = createClaudeAdapter();
|
|
15859
16613
|
|
|
15860
16614
|
// src/hosts/codex.ts
|
|
15861
|
-
import { existsSync as
|
|
15862
|
-
import
|
|
15863
|
-
var CONFIG_FILE2 =
|
|
16615
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
16616
|
+
import path22 from "path";
|
|
16617
|
+
var CONFIG_FILE2 = path22.join(".codex", "config.toml");
|
|
15864
16618
|
var CONFIG_REL2 = [CONFIG_FILE2, ".codex"];
|
|
15865
16619
|
function targetFor2(home) {
|
|
15866
|
-
return
|
|
16620
|
+
return path22.join(home, CONFIG_FILE2);
|
|
15867
16621
|
}
|
|
15868
16622
|
function escapeRe(s) {
|
|
15869
16623
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -15929,7 +16683,7 @@ function createCodexAdapter(probe = defaultProbe) {
|
|
|
15929
16683
|
authArgv: ["codex", "login", "status"],
|
|
15930
16684
|
hasIntegration: (dir) => {
|
|
15931
16685
|
const file = targetFor2(dir);
|
|
15932
|
-
return
|
|
16686
|
+
return existsSync14(file) && hasAnyCodexMcpSection(readFileSync12(file, "utf8"));
|
|
15933
16687
|
}
|
|
15934
16688
|
});
|
|
15935
16689
|
},
|
|
@@ -15938,7 +16692,7 @@ function createCodexAdapter(probe = defaultProbe) {
|
|
|
15938
16692
|
if (!argv?.length) return null;
|
|
15939
16693
|
const h3 = hostHome(home);
|
|
15940
16694
|
const target = targetFor2(h3);
|
|
15941
|
-
const existing =
|
|
16695
|
+
const existing = existsSync14(target) ? readFileSync12(target, "utf8") : null;
|
|
15942
16696
|
if (existing !== null && hasCodexMcpSection(existing, forge.forge_id)) {
|
|
15943
16697
|
return {
|
|
15944
16698
|
host: "codex",
|
|
@@ -15969,7 +16723,7 @@ function createCodexAdapter(probe = defaultProbe) {
|
|
|
15969
16723
|
plan,
|
|
15970
16724
|
(action) => {
|
|
15971
16725
|
if (!action.target || !action.argv?.length) return "skip";
|
|
15972
|
-
const existing =
|
|
16726
|
+
const existing = existsSync14(action.target) ? readFileSync12(action.target, "utf8") : null;
|
|
15973
16727
|
if (existing !== null && hasCodexMcpSection(existing, plan.forge_id)) {
|
|
15974
16728
|
return "duplicate";
|
|
15975
16729
|
}
|
|
@@ -15986,13 +16740,13 @@ ${block2}
|
|
|
15986
16740
|
validateIntegration: async (forge_id, home) => {
|
|
15987
16741
|
const file = targetFor2(hostHome(home));
|
|
15988
16742
|
const checks = [];
|
|
15989
|
-
if (!
|
|
16743
|
+
if (!existsSync14(file)) {
|
|
15990
16744
|
checks.push({ id: "config-file", result: "fail", detail: "absent" });
|
|
15991
16745
|
checks.push({ id: "mcp-section", result: "fail", detail: forge_id });
|
|
15992
16746
|
return { ok: false, checks };
|
|
15993
16747
|
}
|
|
15994
16748
|
checks.push({ id: "config-file", result: "pass", detail: file });
|
|
15995
|
-
const content =
|
|
16749
|
+
const content = readFileSync12(file, "utf8");
|
|
15996
16750
|
if (!hasCodexMcpSection(content, forge_id)) {
|
|
15997
16751
|
checks.push({ id: "mcp-section", result: "fail", detail: forge_id });
|
|
15998
16752
|
return { ok: false, checks };
|
|
@@ -16013,12 +16767,12 @@ ${block2}
|
|
|
16013
16767
|
var codexAdapter = createCodexAdapter();
|
|
16014
16768
|
|
|
16015
16769
|
// src/hosts/copilot.ts
|
|
16016
|
-
import
|
|
16017
|
-
import { existsSync as
|
|
16018
|
-
var CONFIG_FILE3 =
|
|
16019
|
-
var CONFIG_REL3 = [".copilot", CONFIG_FILE3,
|
|
16770
|
+
import path23 from "path";
|
|
16771
|
+
import { existsSync as existsSync15 } from "fs";
|
|
16772
|
+
var CONFIG_FILE3 = path23.join(".copilot", "mcp-config.json");
|
|
16773
|
+
var CONFIG_REL3 = [".copilot", CONFIG_FILE3, path23.join(".config", "gh")];
|
|
16020
16774
|
function targetFor3(home) {
|
|
16021
|
-
return
|
|
16775
|
+
return path23.join(home, CONFIG_FILE3);
|
|
16022
16776
|
}
|
|
16023
16777
|
function createCopilotAdapter(probe = defaultProbe) {
|
|
16024
16778
|
return {
|
|
@@ -16042,7 +16796,7 @@ function createCopilotAdapter(probe = defaultProbe) {
|
|
|
16042
16796
|
if (!argv?.length) return null;
|
|
16043
16797
|
const h3 = hostHome(home);
|
|
16044
16798
|
const target = targetFor3(h3);
|
|
16045
|
-
if (!
|
|
16799
|
+
if (!existsSync15(target)) return null;
|
|
16046
16800
|
if (hasMcpEntry(target, forge.forge_id)) {
|
|
16047
16801
|
return {
|
|
16048
16802
|
host: "copilot",
|
|
@@ -16088,11 +16842,11 @@ function createCopilotAdapter(probe = defaultProbe) {
|
|
|
16088
16842
|
var copilotAdapter = createCopilotAdapter();
|
|
16089
16843
|
|
|
16090
16844
|
// src/hosts/cursor.ts
|
|
16091
|
-
import
|
|
16092
|
-
var CONFIG_FILE4 =
|
|
16845
|
+
import path24 from "path";
|
|
16846
|
+
var CONFIG_FILE4 = path24.join(".cursor", "mcp.json");
|
|
16093
16847
|
var CONFIG_REL4 = [CONFIG_FILE4, ".cursor"];
|
|
16094
16848
|
function targetFor4(home) {
|
|
16095
|
-
return
|
|
16849
|
+
return path24.join(home, CONFIG_FILE4);
|
|
16096
16850
|
}
|
|
16097
16851
|
function createCursorAdapter(probe = defaultProbe) {
|
|
16098
16852
|
return {
|
|
@@ -16158,8 +16912,8 @@ function createCursorAdapter(probe = defaultProbe) {
|
|
|
16158
16912
|
var cursorAdapter = createCursorAdapter();
|
|
16159
16913
|
|
|
16160
16914
|
// src/hosts/devin.ts
|
|
16161
|
-
import
|
|
16162
|
-
var CONFIG_REL5 = [".devin",
|
|
16915
|
+
import path25 from "path";
|
|
16916
|
+
var CONFIG_REL5 = [".devin", path25.join(".config", "devin")];
|
|
16163
16917
|
function createDevinAdapter(probe = defaultProbe) {
|
|
16164
16918
|
return {
|
|
16165
16919
|
id: "devin",
|
|
@@ -16210,7 +16964,7 @@ async function detectAll(home) {
|
|
|
16210
16964
|
}
|
|
16211
16965
|
|
|
16212
16966
|
// src/doctor2/context.ts
|
|
16213
|
-
import { readdirSync as
|
|
16967
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync13 } from "fs";
|
|
16214
16968
|
var DEFAULT_MAX_RECEIPTS = 20;
|
|
16215
16969
|
var DEFAULT_MAX_ENTRIES = 50;
|
|
16216
16970
|
function mkCheck(now, check) {
|
|
@@ -16218,14 +16972,14 @@ function mkCheck(now, check) {
|
|
|
16218
16972
|
}
|
|
16219
16973
|
function listDir(dir) {
|
|
16220
16974
|
try {
|
|
16221
|
-
return
|
|
16975
|
+
return readdirSync5(dir).sort();
|
|
16222
16976
|
} catch {
|
|
16223
16977
|
return null;
|
|
16224
16978
|
}
|
|
16225
16979
|
}
|
|
16226
16980
|
function readJson(file) {
|
|
16227
16981
|
try {
|
|
16228
|
-
return { ok: true, value: JSON.parse(
|
|
16982
|
+
return { ok: true, value: JSON.parse(readFileSync13(file, "utf8")) };
|
|
16229
16983
|
} catch (error) {
|
|
16230
16984
|
return { ok: false, error: errMsg(error) };
|
|
16231
16985
|
}
|
|
@@ -16240,13 +16994,13 @@ function errMsg(error) {
|
|
|
16240
16994
|
}
|
|
16241
16995
|
|
|
16242
16996
|
// src/doctor2/domains.ts
|
|
16243
|
-
import { existsSync as
|
|
16244
|
-
import
|
|
16245
|
-
import
|
|
16997
|
+
import { existsSync as existsSync19, readdirSync as readdirSync7, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
16998
|
+
import os4 from "os";
|
|
16999
|
+
import path29 from "path";
|
|
16246
17000
|
|
|
16247
17001
|
// src/envintel/validate.ts
|
|
16248
|
-
import { existsSync as
|
|
16249
|
-
import
|
|
17002
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
17003
|
+
import path27 from "path";
|
|
16250
17004
|
|
|
16251
17005
|
// src/envintel/parse.ts
|
|
16252
17006
|
var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -16332,8 +17086,8 @@ function formatEntry(key, value) {
|
|
|
16332
17086
|
}
|
|
16333
17087
|
|
|
16334
17088
|
// src/envintel/schema.ts
|
|
16335
|
-
import { existsSync as
|
|
16336
|
-
import
|
|
17089
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
17090
|
+
import path26 from "path";
|
|
16337
17091
|
var SECRET_RE = /KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL/;
|
|
16338
17092
|
var KIND_RULES = [
|
|
16339
17093
|
{ kind: "port", re: /(^|_)PORT($|_)/ },
|
|
@@ -16380,10 +17134,10 @@ function validPattern(p) {
|
|
|
16380
17134
|
}
|
|
16381
17135
|
}
|
|
16382
17136
|
function readSchemaFile(repoDir) {
|
|
16383
|
-
const file =
|
|
16384
|
-
if (!
|
|
17137
|
+
const file = path26.join(repoDir, "forge.env.schema.json");
|
|
17138
|
+
if (!existsSync16(file)) return [];
|
|
16385
17139
|
try {
|
|
16386
|
-
const parsed = JSON.parse(
|
|
17140
|
+
const parsed = JSON.parse(readFileSync14(file, "utf8"));
|
|
16387
17141
|
const list = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.vars) ? parsed.vars : parsed && typeof parsed === "object" ? Object.entries(parsed).map(([key, v]) => ({
|
|
16388
17142
|
key,
|
|
16389
17143
|
...v && typeof v === "object" ? v : {}
|
|
@@ -16395,9 +17149,9 @@ function readSchemaFile(repoDir) {
|
|
|
16395
17149
|
}
|
|
16396
17150
|
function deriveSchema(repoDir, envKeys) {
|
|
16397
17151
|
const specs = /* @__PURE__ */ new Map();
|
|
16398
|
-
const exampleFile =
|
|
16399
|
-
if (
|
|
16400
|
-
for (const entry2 of parseEnv(
|
|
17152
|
+
const exampleFile = path26.join(repoDir, ".env.example");
|
|
17153
|
+
if (existsSync16(exampleFile)) {
|
|
17154
|
+
for (const entry2 of parseEnv(readFileSync14(exampleFile, "utf8")).entries) {
|
|
16401
17155
|
if (specs.has(entry2.key)) continue;
|
|
16402
17156
|
const kind = inferKind(entry2.key, entry2.value || void 0);
|
|
16403
17157
|
const secret = kind === "secret";
|
|
@@ -16460,8 +17214,8 @@ function checkValue(spec, value, opts = {}) {
|
|
|
16460
17214
|
case "path": {
|
|
16461
17215
|
if (!value.trim()) return false;
|
|
16462
17216
|
if (!opts.checkPaths) return true;
|
|
16463
|
-
const p =
|
|
16464
|
-
return
|
|
17217
|
+
const p = path27.isAbsolute(value) ? value : path27.resolve(opts.baseDir ?? ".", value);
|
|
17218
|
+
return existsSync17(p);
|
|
16465
17219
|
}
|
|
16466
17220
|
case "bool":
|
|
16467
17221
|
return /^(true|false|0|1|yes|no)$/i.test(value.trim());
|
|
@@ -16512,9 +17266,9 @@ function analyzeKey(key, spec, entries, opts) {
|
|
|
16512
17266
|
}
|
|
16513
17267
|
function analyzeEnv(repoDir, envKeys, opts = {}) {
|
|
16514
17268
|
const schema = deriveSchema(repoDir, envKeys);
|
|
16515
|
-
const envPath =
|
|
17269
|
+
const envPath = path27.join(repoDir, ".env");
|
|
16516
17270
|
const specByKey = new Map(schema.vars.map((v) => [v.key, v]));
|
|
16517
|
-
const envFile =
|
|
17271
|
+
const envFile = existsSync17(envPath) ? parseEnv(readFileSync15(envPath, "utf8")) : null;
|
|
16518
17272
|
const findings = [];
|
|
16519
17273
|
const vars = [];
|
|
16520
17274
|
const byKey = /* @__PURE__ */ new Map();
|
|
@@ -16595,9 +17349,9 @@ function inspectEnv(repoDir, envKeys) {
|
|
|
16595
17349
|
}
|
|
16596
17350
|
|
|
16597
17351
|
// src/maintenance.ts
|
|
16598
|
-
import { existsSync as
|
|
16599
|
-
import { createHash as
|
|
16600
|
-
import
|
|
17352
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync16, readdirSync as readdirSync6 } from "fs";
|
|
17353
|
+
import { createHash as createHash3 } from "crypto";
|
|
17354
|
+
import path28 from "path";
|
|
16601
17355
|
function envLockDigest(repoDir) {
|
|
16602
17356
|
const names = [
|
|
16603
17357
|
".env.example",
|
|
@@ -16607,12 +17361,12 @@ function envLockDigest(repoDir) {
|
|
|
16607
17361
|
"pyproject.toml"
|
|
16608
17362
|
];
|
|
16609
17363
|
const files = [];
|
|
16610
|
-
const hash =
|
|
17364
|
+
const hash = createHash3("sha256");
|
|
16611
17365
|
for (const name of names) {
|
|
16612
|
-
const file =
|
|
16613
|
-
if (!
|
|
17366
|
+
const file = path28.join(repoDir, name);
|
|
17367
|
+
if (!existsSync18(file)) continue;
|
|
16614
17368
|
files.push(name);
|
|
16615
|
-
const content =
|
|
17369
|
+
const content = readFileSync16(file);
|
|
16616
17370
|
hash.update(name).update("\0").update(content).update("\0");
|
|
16617
17371
|
}
|
|
16618
17372
|
return { files, digest: hash.digest("hex") };
|
|
@@ -16624,31 +17378,31 @@ function writeEnvLock(targetDir, repos) {
|
|
|
16624
17378
|
forges: {}
|
|
16625
17379
|
};
|
|
16626
17380
|
for (const repo of repos) {
|
|
16627
|
-
const dir =
|
|
16628
|
-
if (!
|
|
17381
|
+
const dir = path28.join(targetDir, repo.name);
|
|
17382
|
+
if (!existsSync18(dir)) continue;
|
|
16629
17383
|
const { files, digest } = envLockDigest(dir);
|
|
16630
17384
|
const head = loadState(targetDir)?.repos[repo.name]?.head;
|
|
16631
17385
|
lock.forges[repo.name] = { env_files: files, digest, ...head ? { head } : {} };
|
|
16632
17386
|
}
|
|
16633
|
-
const file =
|
|
16634
|
-
|
|
17387
|
+
const file = path28.join(artifactsRoot(targetDir), "env.lock.json");
|
|
17388
|
+
mkdirSync6(path28.dirname(file), { recursive: true });
|
|
16635
17389
|
writeFileAtomic(file, JSON.stringify(lock, null, 2));
|
|
16636
17390
|
return lock;
|
|
16637
17391
|
}
|
|
16638
17392
|
function checkEnvLock(targetDir, repos) {
|
|
16639
|
-
const file =
|
|
16640
|
-
if (!
|
|
17393
|
+
const file = path28.join(artifactsRoot(targetDir), "env.lock.json");
|
|
17394
|
+
if (!existsSync18(file)) return repos.map((r2) => ({ forge: r2.name, state: "untracked" }));
|
|
16641
17395
|
let lock;
|
|
16642
17396
|
try {
|
|
16643
|
-
lock = JSON.parse(
|
|
17397
|
+
lock = JSON.parse(readFileSync16(file, "utf8"));
|
|
16644
17398
|
} catch {
|
|
16645
17399
|
return repos.map((r2) => ({ forge: r2.name, state: "untracked" }));
|
|
16646
17400
|
}
|
|
16647
17401
|
return repos.map((repo) => {
|
|
16648
17402
|
const entry2 = lock.forges?.[repo.name];
|
|
16649
17403
|
if (!entry2) return { forge: repo.name, state: "untracked" };
|
|
16650
|
-
const dir =
|
|
16651
|
-
if (!
|
|
17404
|
+
const dir = path28.join(targetDir, repo.name);
|
|
17405
|
+
if (!existsSync18(dir)) return { forge: repo.name, state: "absent" };
|
|
16652
17406
|
const { digest } = envLockDigest(dir);
|
|
16653
17407
|
return { forge: repo.name, state: digest === entry2.digest ? "match" : "drifted" };
|
|
16654
17408
|
});
|
|
@@ -16666,13 +17420,13 @@ function exportProfile(targetDir, dest) {
|
|
|
16666
17420
|
])
|
|
16667
17421
|
)
|
|
16668
17422
|
};
|
|
16669
|
-
|
|
17423
|
+
mkdirSync6(path28.dirname(path28.resolve(dest)), { recursive: true });
|
|
16670
17424
|
writeFileAtomic(dest, JSON.stringify(profile, null, 2));
|
|
16671
17425
|
return profile;
|
|
16672
17426
|
}
|
|
16673
17427
|
function importProfile(file) {
|
|
16674
17428
|
try {
|
|
16675
|
-
const parsed = JSON.parse(
|
|
17429
|
+
const parsed = JSON.parse(readFileSync16(file, "utf8"));
|
|
16676
17430
|
if (parsed?.schema !== "forge-cli/Profile/v1" || typeof parsed.repos !== "object") {
|
|
16677
17431
|
return { error: "invalid-profile" };
|
|
16678
17432
|
}
|
|
@@ -16750,7 +17504,7 @@ function systemChecks(ctx) {
|
|
|
16750
17504
|
result: "pass",
|
|
16751
17505
|
severity: "info",
|
|
16752
17506
|
diagnosis: "plataforma detectada",
|
|
16753
|
-
evidence: [`platform=${process.platform}`, `arch=${
|
|
17507
|
+
evidence: [`platform=${process.platform}`, `arch=${os4.arch()}`, `release=${os4.release()}`],
|
|
16754
17508
|
reversible: null
|
|
16755
17509
|
})
|
|
16756
17510
|
);
|
|
@@ -16760,23 +17514,23 @@ function systemChecks(ctx) {
|
|
|
16760
17514
|
let readEvidence;
|
|
16761
17515
|
let writeEvidence = "write=n\xE3o sondado (leitura falhou)";
|
|
16762
17516
|
try {
|
|
16763
|
-
|
|
17517
|
+
readdirSync7(dir);
|
|
16764
17518
|
readable2 = true;
|
|
16765
17519
|
readEvidence = "read=ok";
|
|
16766
17520
|
} catch (error) {
|
|
16767
17521
|
readEvidence = `read=falha (${errMsg(error)})`;
|
|
16768
17522
|
}
|
|
16769
17523
|
if (readable2) {
|
|
16770
|
-
const probe =
|
|
17524
|
+
const probe = path29.join(dir, `.forge-doctor2-probe-${process.pid}`);
|
|
16771
17525
|
try {
|
|
16772
|
-
|
|
16773
|
-
|
|
17526
|
+
writeFileSync5(probe, "probe\n", "utf8");
|
|
17527
|
+
rmSync4(probe, { force: true });
|
|
16774
17528
|
writable2 = true;
|
|
16775
17529
|
writeEvidence = "write=ok";
|
|
16776
17530
|
} catch (error) {
|
|
16777
17531
|
writeEvidence = `write=falha (${errMsg(error)})`;
|
|
16778
17532
|
try {
|
|
16779
|
-
|
|
17533
|
+
rmSync4(probe, { force: true });
|
|
16780
17534
|
} catch {
|
|
16781
17535
|
}
|
|
16782
17536
|
}
|
|
@@ -16797,12 +17551,12 @@ function systemChecks(ctx) {
|
|
|
16797
17551
|
);
|
|
16798
17552
|
const required = [
|
|
16799
17553
|
{ label: "home", dir: ctx.home },
|
|
16800
|
-
{ label: "tmpdir", dir:
|
|
17554
|
+
{ label: "tmpdir", dir: os4.tmpdir() }
|
|
16801
17555
|
];
|
|
16802
17556
|
const dirEvidence = [];
|
|
16803
17557
|
let dirFailed = 0;
|
|
16804
17558
|
for (const { label, dir: d } of required) {
|
|
16805
|
-
if (!
|
|
17559
|
+
if (!existsSync19(d)) {
|
|
16806
17560
|
dirFailed += 1;
|
|
16807
17561
|
dirEvidence.push(`${label}=${d}: ausente`);
|
|
16808
17562
|
continue;
|
|
@@ -16860,7 +17614,7 @@ function backupScan(ctx) {
|
|
|
16860
17614
|
over = true;
|
|
16861
17615
|
break;
|
|
16862
17616
|
}
|
|
16863
|
-
const full =
|
|
17617
|
+
const full = path29.join(dir, name);
|
|
16864
17618
|
try {
|
|
16865
17619
|
if (!statSync3(full).isDirectory()) continue;
|
|
16866
17620
|
} catch {
|
|
@@ -16869,16 +17623,16 @@ function backupScan(ctx) {
|
|
|
16869
17623
|
if (restorableIf(full)) restorable.push(`${kind}: ${full}`);
|
|
16870
17624
|
}
|
|
16871
17625
|
};
|
|
16872
|
-
scan(
|
|
17626
|
+
scan(path29.join(artifactsRoot(ctx.root), "backups"), "workspace-backup", dirHasFiles);
|
|
16873
17627
|
scan(
|
|
16874
|
-
|
|
17628
|
+
path29.join(ctx.home, ".forge-cli", "backups"),
|
|
16875
17629
|
"host-backup",
|
|
16876
|
-
(full) =>
|
|
17630
|
+
(full) => existsSync19(path29.join(full, "manifest.json"))
|
|
16877
17631
|
);
|
|
16878
17632
|
for (const repo of ctx.catalog) {
|
|
16879
17633
|
const dir = resolveInside(ctx.root, repo.name);
|
|
16880
17634
|
if (!dir || over) continue;
|
|
16881
|
-
scan(
|
|
17635
|
+
scan(path29.join(dir, ".forge-cli", "backups"), `env-backup(${repo.name})`, dirHasFiles);
|
|
16882
17636
|
}
|
|
16883
17637
|
if (over) ctx.truncated.value = true;
|
|
16884
17638
|
return { restorable, scanned };
|
|
@@ -16886,7 +17640,7 @@ function backupScan(ctx) {
|
|
|
16886
17640
|
function installationChecks(ctx) {
|
|
16887
17641
|
const checks = [];
|
|
16888
17642
|
const forgeDir = artifactsRoot(ctx.root);
|
|
16889
|
-
const forgeDirPresent =
|
|
17643
|
+
const forgeDirPresent = existsSync19(forgeDir);
|
|
16890
17644
|
const stateFile = stateFileFor(ctx.root);
|
|
16891
17645
|
checks.push(
|
|
16892
17646
|
mkCheck(
|
|
@@ -16899,7 +17653,7 @@ function installationChecks(ctx) {
|
|
|
16899
17653
|
diagnosis: "diret\xF3rio de estado .forge-cli presente",
|
|
16900
17654
|
evidence: [
|
|
16901
17655
|
`${forgeDir} presente`,
|
|
16902
|
-
|
|
17656
|
+
existsSync19(stateFile) ? "state.json presente" : "state.json ausente"
|
|
16903
17657
|
],
|
|
16904
17658
|
reversible: null
|
|
16905
17659
|
} : {
|
|
@@ -16914,8 +17668,8 @@ function installationChecks(ctx) {
|
|
|
16914
17668
|
}
|
|
16915
17669
|
)
|
|
16916
17670
|
);
|
|
16917
|
-
const forgeJson =
|
|
16918
|
-
if (!
|
|
17671
|
+
const forgeJson = path29.join(ctx.root, "forge.json");
|
|
17672
|
+
if (!existsSync19(forgeJson)) {
|
|
16919
17673
|
checks.push(
|
|
16920
17674
|
mkCheck(ctx.now, {
|
|
16921
17675
|
id: "installation.forge-json.present",
|
|
@@ -16964,10 +17718,10 @@ function installationChecks(ctx) {
|
|
|
16964
17718
|
);
|
|
16965
17719
|
}
|
|
16966
17720
|
}
|
|
16967
|
-
const lockFile =
|
|
16968
|
-
const lockParsed =
|
|
17721
|
+
const lockFile = path29.join(forgeDir, "env.lock.json");
|
|
17722
|
+
const lockParsed = existsSync19(lockFile) ? readJson(lockFile) : null;
|
|
16969
17723
|
const lockObj = lockParsed?.ok && lockParsed.value !== null && typeof lockParsed.value === "object" ? lockParsed.value : null;
|
|
16970
|
-
if (!forgeDirPresent || !
|
|
17724
|
+
if (!forgeDirPresent || !existsSync19(lockFile)) {
|
|
16971
17725
|
checks.push(
|
|
16972
17726
|
mkCheck(ctx.now, {
|
|
16973
17727
|
id: "installation.lockfile.present",
|
|
@@ -17012,7 +17766,7 @@ function installationChecks(ctx) {
|
|
|
17012
17766
|
);
|
|
17013
17767
|
}
|
|
17014
17768
|
const trackedForges = lockObj ? Object.keys(lockObj.forges ?? {}) : [];
|
|
17015
|
-
if (!
|
|
17769
|
+
if (!existsSync19(lockFile) || !lockObj || !trackedForges.length) {
|
|
17016
17770
|
checks.push(
|
|
17017
17771
|
mkCheck(ctx.now, {
|
|
17018
17772
|
id: "installation.lockfile.drift",
|
|
@@ -17021,7 +17775,7 @@ function installationChecks(ctx) {
|
|
|
17021
17775
|
severity: "info",
|
|
17022
17776
|
diagnosis: "deriva do lockfile n\xE3o verific\xE1vel",
|
|
17023
17777
|
evidence: [
|
|
17024
|
-
!
|
|
17778
|
+
!existsSync19(lockFile) ? "sem lockfile para recomputar (.forge-cli/env.lock.json ausente)" : !lockObj ? "lockfile ileg\xEDvel \u2014 digest n\xE3o pode ser recomputado" : "lockfile n\xE3o cobre nenhuma Forja \u2014 nada para recomputar"
|
|
17025
17779
|
],
|
|
17026
17780
|
reversible: null
|
|
17027
17781
|
})
|
|
@@ -17043,17 +17797,17 @@ function installationChecks(ctx) {
|
|
|
17043
17797
|
})
|
|
17044
17798
|
);
|
|
17045
17799
|
}
|
|
17046
|
-
const ledgerDir =
|
|
17800
|
+
const ledgerDir = path29.join(ctx.home, ".forge", "installations");
|
|
17047
17801
|
const ledgerNames = listDir(ledgerDir);
|
|
17048
17802
|
if (ledgerNames === null) {
|
|
17049
17803
|
checks.push(
|
|
17050
17804
|
mkCheck(ctx.now, {
|
|
17051
17805
|
id: "installation.ledger.readable",
|
|
17052
17806
|
domain: "installation",
|
|
17053
|
-
result:
|
|
17054
|
-
severity:
|
|
17055
|
-
diagnosis:
|
|
17056
|
-
evidence: [`${ledgerDir} ${
|
|
17807
|
+
result: existsSync19(ledgerDir) ? "warn" : "skip",
|
|
17808
|
+
severity: existsSync19(ledgerDir) ? "medium" : "info",
|
|
17809
|
+
diagnosis: existsSync19(ledgerDir) ? "ledger de instala\xE7\xF5es existe mas n\xE3o p\xF4de ser lido" : "sem ledger de instala\xE7\xF5es (~/.forge/installations ausente)",
|
|
17810
|
+
evidence: [`${ledgerDir} ${existsSync19(ledgerDir) ? "ileg\xEDvel" : "ausente"}`],
|
|
17057
17811
|
reversible: null
|
|
17058
17812
|
})
|
|
17059
17813
|
);
|
|
@@ -17181,7 +17935,7 @@ function environmentChecks(ctx) {
|
|
|
17181
17935
|
const targets = [{ label: "<workspace>", dir: ctx.root }];
|
|
17182
17936
|
for (const repo of ctx.catalog) {
|
|
17183
17937
|
const dir = resolveInside(ctx.root, repo.name);
|
|
17184
|
-
if (dir &&
|
|
17938
|
+
if (dir && existsSync19(dir)) targets.push({ label: repo.name, dir, envKeys: repo.envKeys });
|
|
17185
17939
|
}
|
|
17186
17940
|
const inspected = targets.map((t2) => ({
|
|
17187
17941
|
...t2,
|
|
@@ -17393,16 +18147,16 @@ async function hostsChecks(ctx) {
|
|
|
17393
18147
|
}
|
|
17394
18148
|
function operationsChecks(ctx) {
|
|
17395
18149
|
const checks = [];
|
|
17396
|
-
const reportsDir =
|
|
18150
|
+
const reportsDir = path29.join(artifactsRoot(ctx.root), "reports");
|
|
17397
18151
|
const allReports = (listDir(reportsDir) ?? []).filter((n4) => n4.endsWith(".json"));
|
|
17398
18152
|
const capped = allReports.length > ctx.maxReceipts;
|
|
17399
18153
|
if (capped) ctx.truncated.value = true;
|
|
17400
18154
|
const scannedNames = [...allReports].sort().reverse().slice(0, ctx.maxReceipts);
|
|
17401
|
-
const parsedReports = scannedNames.map((n4) => readJson(
|
|
18155
|
+
const parsedReports = scannedNames.map((n4) => readJson(path29.join(reportsDir, n4))).filter((r2) => r2.ok).map((r2) => r2.value);
|
|
17402
18156
|
checks.push(
|
|
17403
18157
|
mkCheck(
|
|
17404
18158
|
ctx.now,
|
|
17405
|
-
!
|
|
18159
|
+
!existsSync19(reportsDir) ? {
|
|
17406
18160
|
id: "operations.receipts.recent",
|
|
17407
18161
|
domain: "operations",
|
|
17408
18162
|
result: "skip",
|
|
@@ -17438,7 +18192,7 @@ function operationsChecks(ctx) {
|
|
|
17438
18192
|
if (entry2.status === "error") failures.set(name, (failures.get(name) ?? 0) + 1);
|
|
17439
18193
|
}
|
|
17440
18194
|
const recurring = [...failures.entries()].filter(([, n4]) => n4 >= 2);
|
|
17441
|
-
const scannedAnything =
|
|
18195
|
+
const scannedAnything = existsSync19(reportsDir) || ctx.state !== null;
|
|
17442
18196
|
if (!scannedAnything) {
|
|
17443
18197
|
checks.push(
|
|
17444
18198
|
mkCheck(ctx.now, {
|
|
@@ -17474,7 +18228,7 @@ function operationsChecks(ctx) {
|
|
|
17474
18228
|
);
|
|
17475
18229
|
const recFiles = boundEntries(ctx, allRecFiles).shown;
|
|
17476
18230
|
const recEvidence = recFiles.map((f) => {
|
|
17477
|
-
const parsed = readJson(
|
|
18231
|
+
const parsed = readJson(path29.join(artifactsRoot(ctx.root), f));
|
|
17478
18232
|
return `recovery file: ${f}${parsed.ok ? "" : " (ileg\xEDvel)"}`;
|
|
17479
18233
|
});
|
|
17480
18234
|
checks.push(
|
|
@@ -17654,10 +18408,10 @@ function defaultCatalog(root) {
|
|
|
17654
18408
|
}
|
|
17655
18409
|
async function runDoctor2(root, opts = {}) {
|
|
17656
18410
|
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
17657
|
-
const resolved =
|
|
18411
|
+
const resolved = path30.resolve(root);
|
|
17658
18412
|
const ctx = {
|
|
17659
18413
|
root: resolved,
|
|
17660
|
-
home: opts.home ??
|
|
18414
|
+
home: opts.home ?? os5.homedir(),
|
|
17661
18415
|
now,
|
|
17662
18416
|
catalog: opts.catalog ?? defaultCatalog(resolved),
|
|
17663
18417
|
adapters: opts.adapters ?? hostAdapters(),
|
|
@@ -17714,9 +18468,9 @@ async function runDoctor2(root, opts = {}) {
|
|
|
17714
18468
|
}
|
|
17715
18469
|
|
|
17716
18470
|
// src/heal/fixes.ts
|
|
17717
|
-
import { existsSync as
|
|
17718
|
-
import
|
|
17719
|
-
var rel = (root, abs) =>
|
|
18471
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync7, readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync4 } from "fs";
|
|
18472
|
+
import path31 from "path";
|
|
18473
|
+
var rel = (root, abs) => path31.relative(root, abs) || ".";
|
|
17720
18474
|
function stampOf(iso) {
|
|
17721
18475
|
const m3 = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/.exec(iso);
|
|
17722
18476
|
return m3 ? `${m3[1]}${m3[2]}${m3[3]}-${m3[4]}${m3[5]}${m3[6]}` : iso.replace(/[^\d]/g, "").slice(0, 14);
|
|
@@ -17731,9 +18485,9 @@ var forgeDirRepair = {
|
|
|
17731
18485
|
plan(ctx) {
|
|
17732
18486
|
const base = artifactsRoot(ctx.root);
|
|
17733
18487
|
return ["", ...FORGE_DIR_SUBDIRS].map((sub) => {
|
|
17734
|
-
const dir = sub ?
|
|
18488
|
+
const dir = sub ? path31.join(base, sub) : base;
|
|
17735
18489
|
return {
|
|
17736
|
-
description: `create directory ${rel(ctx.root, dir)}${
|
|
18490
|
+
description: `create directory ${rel(ctx.root, dir)}${existsSync20(dir) ? " (already present)" : ""}`,
|
|
17737
18491
|
touches: [rel(ctx.root, dir)]
|
|
17738
18492
|
};
|
|
17739
18493
|
});
|
|
@@ -17743,9 +18497,9 @@ var forgeDirRepair = {
|
|
|
17743
18497
|
const base = artifactsRoot(ctx.root);
|
|
17744
18498
|
const created = [];
|
|
17745
18499
|
for (const sub of ["", ...FORGE_DIR_SUBDIRS]) {
|
|
17746
|
-
const dir = sub ?
|
|
17747
|
-
if (
|
|
17748
|
-
|
|
18500
|
+
const dir = sub ? path31.join(base, sub) : base;
|
|
18501
|
+
if (existsSync20(dir)) continue;
|
|
18502
|
+
mkdirSync7(dir, { recursive: true });
|
|
17749
18503
|
created.push(rel(ctx.root, dir));
|
|
17750
18504
|
}
|
|
17751
18505
|
return created.length ? `created ${created.length} directorie(s): ${created.join(", ")}` : "all directories already present";
|
|
@@ -17753,7 +18507,7 @@ var forgeDirRepair = {
|
|
|
17753
18507
|
async verify(ctx) {
|
|
17754
18508
|
const base = artifactsRoot(ctx.root);
|
|
17755
18509
|
return ["", ...FORGE_DIR_SUBDIRS].every((sub) => {
|
|
17756
|
-
const dir = sub ?
|
|
18510
|
+
const dir = sub ? path31.join(base, sub) : base;
|
|
17757
18511
|
try {
|
|
17758
18512
|
return statSync4(dir).isDirectory();
|
|
17759
18513
|
} catch {
|
|
@@ -17764,12 +18518,12 @@ var forgeDirRepair = {
|
|
|
17764
18518
|
};
|
|
17765
18519
|
var EVIDENCE_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
17766
18520
|
function envPathOf(root) {
|
|
17767
|
-
return
|
|
18521
|
+
return path31.join(root, ".env");
|
|
17768
18522
|
}
|
|
17769
18523
|
function presentKeys(root) {
|
|
17770
18524
|
const file = envPathOf(root);
|
|
17771
|
-
if (!
|
|
17772
|
-
return new Set(parseEnv(
|
|
18525
|
+
if (!existsSync20(file)) return /* @__PURE__ */ new Set();
|
|
18526
|
+
return new Set(parseEnv(readFileSync17(file, "utf8")).entries.map((e) => e.key));
|
|
17773
18527
|
}
|
|
17774
18528
|
function missingEnvKeys(root, check) {
|
|
17775
18529
|
const missing = /* @__PURE__ */ new Set();
|
|
@@ -17789,9 +18543,9 @@ function keysToAppend(root, check) {
|
|
|
17789
18543
|
}
|
|
17790
18544
|
function backupEnvFile(ctx, current) {
|
|
17791
18545
|
const stamp = stampOf(ctx.now?.() ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
17792
|
-
const dir =
|
|
17793
|
-
|
|
17794
|
-
writeFileAtomic(
|
|
18546
|
+
const dir = path31.join(artifactsRoot(ctx.root), "backups", `env-${stamp}`);
|
|
18547
|
+
mkdirSync7(dir, { recursive: true });
|
|
18548
|
+
writeFileAtomic(path31.join(dir, ".env"), current);
|
|
17795
18549
|
return dir;
|
|
17796
18550
|
}
|
|
17797
18551
|
var envMissingKeyRepair = {
|
|
@@ -17804,9 +18558,9 @@ var envMissingKeyRepair = {
|
|
|
17804
18558
|
const root = ctx.root;
|
|
17805
18559
|
const toAppend = keysToAppend(root, ctx.check);
|
|
17806
18560
|
const steps = [];
|
|
17807
|
-
if (
|
|
18561
|
+
if (existsSync20(envPathOf(root))) {
|
|
17808
18562
|
steps.push({
|
|
17809
|
-
description: `backup .env to ${rel(root,
|
|
18563
|
+
description: `backup .env to ${rel(root, path31.join(artifactsRoot(root), "backups"))}/env-<ts>/.env`,
|
|
17810
18564
|
touches: [".env", `${rel(root, artifactsRoot(root))}/backups/`]
|
|
17811
18565
|
});
|
|
17812
18566
|
}
|
|
@@ -17823,8 +18577,8 @@ var envMissingKeyRepair = {
|
|
|
17823
18577
|
const root = ctx.root;
|
|
17824
18578
|
if (ctx.dryRun) return "dry-run \u2014 not applied";
|
|
17825
18579
|
const file = envPathOf(root);
|
|
17826
|
-
const exists =
|
|
17827
|
-
const current = exists ?
|
|
18580
|
+
const exists = existsSync20(file);
|
|
18581
|
+
const current = exists ? readFileSync17(file, "utf8") : "";
|
|
17828
18582
|
const env = parseEnv(current);
|
|
17829
18583
|
const present = new Set(env.entries.map((e) => e.key));
|
|
17830
18584
|
const toAppend = keysToAppend(root, ctx.check).filter((k) => !present.has(k));
|
|
@@ -17841,8 +18595,8 @@ var envMissingKeyRepair = {
|
|
|
17841
18595
|
},
|
|
17842
18596
|
async verify(ctx) {
|
|
17843
18597
|
const file = envPathOf(ctx.root);
|
|
17844
|
-
if (!
|
|
17845
|
-
const present = new Set(parseEnv(
|
|
18598
|
+
if (!existsSync20(file)) return false;
|
|
18599
|
+
const present = new Set(parseEnv(readFileSync17(file, "utf8")).entries.map((e) => e.key));
|
|
17846
18600
|
return missingEnvKeys(ctx.root, ctx.check).every((k) => present.has(k));
|
|
17847
18601
|
}
|
|
17848
18602
|
};
|
|
@@ -17858,7 +18612,7 @@ var lockfileDriftRepair = {
|
|
|
17858
18612
|
return [
|
|
17859
18613
|
{
|
|
17860
18614
|
description: `run \`${LOCKFILE_REGEN_COMMAND}\` to regenerate .forge-cli/env.lock.json`,
|
|
17861
|
-
touches: [
|
|
18615
|
+
touches: [path31.join(".forge-cli", "env.lock.json")]
|
|
17862
18616
|
},
|
|
17863
18617
|
{
|
|
17864
18618
|
description: `run \`${LOCKFILE_CHECK_COMMAND}\` to confirm the drift cleared`,
|
|
@@ -17878,14 +18632,14 @@ var lockfileDriftRepair = {
|
|
|
17878
18632
|
var ENV_BACKUP_RE = /^env-\d{8}-\d{6}$/;
|
|
17879
18633
|
var REPO_BACKUP_RE = /^([a-z0-9][a-z0-9._-]*)-\d{10,}$/i;
|
|
17880
18634
|
function listBackups(root) {
|
|
17881
|
-
const base =
|
|
17882
|
-
if (!
|
|
18635
|
+
const base = path31.join(artifactsRoot(root), "backups");
|
|
18636
|
+
if (!existsSync20(base)) return [];
|
|
17883
18637
|
const out = [];
|
|
17884
|
-
for (const entry2 of
|
|
18638
|
+
for (const entry2 of readdirSync8(base, { withFileTypes: true })) {
|
|
17885
18639
|
if (!entry2.isDirectory()) continue;
|
|
17886
|
-
const dir =
|
|
18640
|
+
const dir = path31.join(base, entry2.name);
|
|
17887
18641
|
try {
|
|
17888
|
-
const files =
|
|
18642
|
+
const files = readdirSync8(dir, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).sort();
|
|
17889
18643
|
if (!files.length) continue;
|
|
17890
18644
|
out.push({ name: entry2.name, dir, files, mtimeMs: statSync4(dir).mtimeMs });
|
|
17891
18645
|
} catch {
|
|
@@ -17901,18 +18655,18 @@ function restoreTargetFor(backupDirName, fileName) {
|
|
|
17901
18655
|
return fileName;
|
|
17902
18656
|
}
|
|
17903
18657
|
function pickBackupFile(ctx) {
|
|
17904
|
-
const backupsBase =
|
|
18658
|
+
const backupsBase = path31.join(artifactsRoot(ctx.root), "backups");
|
|
17905
18659
|
if (ctx.backup) {
|
|
17906
|
-
const abs =
|
|
17907
|
-
const inside =
|
|
17908
|
-
if (inside.startsWith("..") ||
|
|
18660
|
+
const abs = path31.resolve(ctx.root, ctx.backup);
|
|
18661
|
+
const inside = path31.relative(backupsBase, abs);
|
|
18662
|
+
if (inside.startsWith("..") || path31.isAbsolute(inside)) {
|
|
17909
18663
|
throw new Error(`selected backup escapes .forge-cli/backups: ${ctx.backup}`);
|
|
17910
18664
|
}
|
|
17911
|
-
if (!
|
|
18665
|
+
if (!existsSync20(abs) || !statSync4(abs).isFile()) {
|
|
17912
18666
|
throw new Error(`selected backup not found: ${ctx.backup}`);
|
|
17913
18667
|
}
|
|
17914
|
-
const dirName = inside.split(
|
|
17915
|
-
const fileName2 =
|
|
18668
|
+
const dirName = inside.split(path31.sep)[0] ?? "";
|
|
18669
|
+
const fileName2 = path31.basename(abs);
|
|
17916
18670
|
return { src: abs, rel: inside, dest: restoreTargetFor(dirName, fileName2) };
|
|
17917
18671
|
}
|
|
17918
18672
|
const candidates = listBackups(ctx.root);
|
|
@@ -17921,7 +18675,7 @@ function pickBackupFile(ctx) {
|
|
|
17921
18675
|
if (!cand) throw new Error("no backups available under .forge-cli/backups");
|
|
17922
18676
|
const fileName = cand.files.includes(".env") ? ".env" : cand.files[0];
|
|
17923
18677
|
return {
|
|
17924
|
-
src:
|
|
18678
|
+
src: path31.join(cand.dir, fileName),
|
|
17925
18679
|
rel: `${cand.name}/${fileName}`,
|
|
17926
18680
|
dest: restoreTargetFor(cand.name, fileName)
|
|
17927
18681
|
};
|
|
@@ -17937,7 +18691,7 @@ var backupRestoreRepair = {
|
|
|
17937
18691
|
if (!candidates.length) {
|
|
17938
18692
|
return [
|
|
17939
18693
|
{
|
|
17940
|
-
description: `no backups found under ${rel(ctx.root,
|
|
18694
|
+
description: `no backups found under ${rel(ctx.root, path31.join(artifactsRoot(ctx.root), "backups"))}/`,
|
|
17941
18695
|
touches: []
|
|
17942
18696
|
}
|
|
17943
18697
|
];
|
|
@@ -17963,20 +18717,20 @@ var backupRestoreRepair = {
|
|
|
17963
18717
|
async apply(ctx) {
|
|
17964
18718
|
if (ctx.dryRun) return "dry-run \u2014 not applied";
|
|
17965
18719
|
const picked = pickBackupFile(ctx);
|
|
17966
|
-
const dest =
|
|
17967
|
-
const relDest =
|
|
17968
|
-
if (relDest.startsWith("..") ||
|
|
18720
|
+
const dest = path31.resolve(ctx.root, picked.dest);
|
|
18721
|
+
const relDest = path31.relative(ctx.root, dest);
|
|
18722
|
+
if (relDest.startsWith("..") || path31.isAbsolute(relDest)) {
|
|
17969
18723
|
throw new Error(`restore target escapes root: ${picked.dest}`);
|
|
17970
18724
|
}
|
|
17971
|
-
|
|
17972
|
-
writeFileAtomic(dest,
|
|
18725
|
+
mkdirSync7(path31.dirname(dest), { recursive: true });
|
|
18726
|
+
writeFileAtomic(dest, readFileSync17(picked.src, "utf8"));
|
|
17973
18727
|
return `restored ${picked.rel} \u2192 ${relDest}`;
|
|
17974
18728
|
},
|
|
17975
18729
|
async verify(ctx) {
|
|
17976
18730
|
try {
|
|
17977
18731
|
const picked = pickBackupFile(ctx);
|
|
17978
|
-
const dest =
|
|
17979
|
-
return
|
|
18732
|
+
const dest = path31.resolve(ctx.root, picked.dest);
|
|
18733
|
+
return existsSync20(dest) && readFileSync17(dest, "utf8") === readFileSync17(picked.src, "utf8");
|
|
17980
18734
|
} catch {
|
|
17981
18735
|
return false;
|
|
17982
18736
|
}
|
|
@@ -18006,11 +18760,11 @@ function classifyFindings(checks) {
|
|
|
18006
18760
|
}
|
|
18007
18761
|
|
|
18008
18762
|
// src/heal/flow.ts
|
|
18009
|
-
import { mkdirSync as
|
|
18010
|
-
import
|
|
18763
|
+
import { mkdirSync as mkdirSync8 } from "fs";
|
|
18764
|
+
import path32 from "path";
|
|
18011
18765
|
function healReceiptPath(root, generatedAt) {
|
|
18012
18766
|
const stamp = generatedAt.replace(/[:.]/g, "-");
|
|
18013
|
-
return
|
|
18767
|
+
return path32.join(artifactsRoot(root), "receipts", `heal-${stamp}.json`);
|
|
18014
18768
|
}
|
|
18015
18769
|
async function planRepairs(checks, opts) {
|
|
18016
18770
|
const classified = classifyFindings(checks);
|
|
@@ -18144,7 +18898,7 @@ async function runHealFlow(checks, opts) {
|
|
|
18144
18898
|
verifiedAll: entries.every((e) => e.verified === true)
|
|
18145
18899
|
};
|
|
18146
18900
|
const file = healReceiptPath(opts.root, generatedAt);
|
|
18147
|
-
|
|
18901
|
+
mkdirSync8(path32.dirname(file), { recursive: true });
|
|
18148
18902
|
writeFileAtomic(file, redactSecrets(JSON.stringify(receipt, null, 2)));
|
|
18149
18903
|
return receipt;
|
|
18150
18904
|
}
|
|
@@ -18227,46 +18981,1056 @@ async function runDoctor2Command(cli) {
|
|
|
18227
18981
|
return runHealCommand({ ...cli, plan: cli.plan });
|
|
18228
18982
|
}
|
|
18229
18983
|
|
|
18230
|
-
// src/
|
|
18231
|
-
import
|
|
18232
|
-
import
|
|
18233
|
-
|
|
18234
|
-
|
|
18235
|
-
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
}
|
|
18243
|
-
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
}
|
|
18257
|
-
|
|
18258
|
-
|
|
18984
|
+
// src/cmd/registry.ts
|
|
18985
|
+
import path33 from "path";
|
|
18986
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync9 } from "fs";
|
|
18987
|
+
|
|
18988
|
+
// src/registry/catalog.ts
|
|
18989
|
+
var REGISTRY_PACKAGES = [
|
|
18990
|
+
{
|
|
18991
|
+
name: "the-forge",
|
|
18992
|
+
dist: "theforge-core",
|
|
18993
|
+
bin: "theforge",
|
|
18994
|
+
minPython: "3.11",
|
|
18995
|
+
origin: "official"
|
|
18996
|
+
},
|
|
18997
|
+
{
|
|
18998
|
+
name: "api-forge",
|
|
18999
|
+
dist: "apiforge-af",
|
|
19000
|
+
bin: "apiforge",
|
|
19001
|
+
minPython: "3.12",
|
|
19002
|
+
mcp: ["{binDir}/apiforge-mcp"],
|
|
19003
|
+
origin: "official"
|
|
19004
|
+
},
|
|
19005
|
+
{
|
|
19006
|
+
name: "platform-forge",
|
|
19007
|
+
dist: "platformforge",
|
|
19008
|
+
bin: "platformforge",
|
|
19009
|
+
minPython: "3.10",
|
|
19010
|
+
mcp: ["{binDir}/platformforge-mcp"],
|
|
19011
|
+
origin: "official"
|
|
19012
|
+
},
|
|
19013
|
+
{
|
|
19014
|
+
name: "spark-forge-aws",
|
|
19015
|
+
dist: "sparkforge-aws",
|
|
19016
|
+
bin: "sparkforge-aws",
|
|
19017
|
+
minPython: "3.10",
|
|
19018
|
+
mcp: ["{bin}", "mcp", "serve"],
|
|
19019
|
+
origin: "official"
|
|
19020
|
+
},
|
|
19021
|
+
{
|
|
19022
|
+
name: "spark-forge-azure",
|
|
19023
|
+
dist: "sparkforge-azure",
|
|
19024
|
+
bin: "sparkforge-azure",
|
|
19025
|
+
minPython: "3.10",
|
|
19026
|
+
mcp: ["{bin}", "mcp", "serve"],
|
|
19027
|
+
origin: "official"
|
|
19028
|
+
},
|
|
19029
|
+
{
|
|
19030
|
+
name: "forge-doctor-data",
|
|
19031
|
+
dist: "forge-doctor-data",
|
|
19032
|
+
bin: "forge-doctor-data",
|
|
19033
|
+
minPython: "3.11",
|
|
19034
|
+
mcp: ["{bin}", "mcp"],
|
|
19035
|
+
origin: "official"
|
|
19036
|
+
},
|
|
19037
|
+
{
|
|
19038
|
+
name: "forge-doctor-api",
|
|
19039
|
+
dist: "forge-doctor-api",
|
|
19040
|
+
bin: "forge-doctor-api",
|
|
19041
|
+
minPython: "3.11",
|
|
19042
|
+
mcp: ["{bin}", "mcp"],
|
|
19043
|
+
origin: "official"
|
|
19044
|
+
}
|
|
19045
|
+
];
|
|
19046
|
+
var BY_NAME = new Map(REGISTRY_PACKAGES.map((e) => [e.name, e]));
|
|
19047
|
+
var BY_DIST = new Map(REGISTRY_PACKAGES.map((e) => [e.dist, e]));
|
|
19048
|
+
function registryEntry(nameOrDist) {
|
|
19049
|
+
return BY_NAME.get(nameOrDist) ?? BY_DIST.get(nameOrDist) ?? null;
|
|
19050
|
+
}
|
|
19051
|
+
function registryNames() {
|
|
19052
|
+
return REGISTRY_PACKAGES.map((e) => e.name);
|
|
19053
|
+
}
|
|
19054
|
+
|
|
19055
|
+
// src/cmd/registry.ts
|
|
19056
|
+
async function probePythons(platform2 = process.platform, probe = commandOutput) {
|
|
19057
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19058
|
+
const out = [];
|
|
19059
|
+
for (const c4 of pythonCandidates(platform2)) {
|
|
19060
|
+
const raw = await probe(c4.exe, [...c4.args, "--version"]);
|
|
19061
|
+
const m3 = raw?.match(/Python\s+(\d+\.\d+\.\d+(?:[a-z]+\d+)?)/);
|
|
19062
|
+
if (!m3) continue;
|
|
19063
|
+
const parsed = parseVersion2(m3[1]);
|
|
19064
|
+
if (!parsed || seen.has(m3[1])) continue;
|
|
19065
|
+
seen.add(m3[1]);
|
|
19066
|
+
out.push({ exe: [c4.exe, ...c4.args], version: m3[1], parsed });
|
|
19067
|
+
}
|
|
19068
|
+
out.sort((a3, b) => compareVersions3(b.parsed, a3.parsed));
|
|
19069
|
+
return out;
|
|
19070
|
+
}
|
|
19071
|
+
function describeResolveError(e) {
|
|
19072
|
+
switch (e.kind) {
|
|
19073
|
+
case "not-found":
|
|
19074
|
+
return `dist ${e.dist} n\xE3o existe no PyPI`;
|
|
19075
|
+
case "version-not-found":
|
|
19076
|
+
return `${e.dist}==${e.requested} n\xE3o existe \u2014 dispon\xEDveis: ${e.available.slice(-5).join(", ")}`;
|
|
19077
|
+
case "python-incompatible":
|
|
19078
|
+
return `${e.dist} ${e.version} exige python ${e.requires} (detectado ${e.have})`;
|
|
19079
|
+
case "specifier-unknown":
|
|
19080
|
+
return `${e.dist} ${e.version}: requires_python fora do subconjunto suportado (${e.requires})`;
|
|
19081
|
+
case "offline-miss":
|
|
19082
|
+
return `${e.dist}: sem cache local e --offline ativo \u2014 rode online uma vez`;
|
|
19083
|
+
case "malformed":
|
|
19084
|
+
return `${e.dist}: metadados fora do formato esperado (${e.detail})`;
|
|
19085
|
+
case "network":
|
|
19086
|
+
return `rede/PyPI: ${e.detail}`;
|
|
19087
|
+
}
|
|
19088
|
+
}
|
|
19089
|
+
function describeInstallError(e) {
|
|
19090
|
+
if (e.stage === "resolve") return describeResolveError(e.error);
|
|
19091
|
+
if (e.stage === "offline-missing-artifact") return `${S.registryOfflineMissing}: ${e.detail}`;
|
|
19092
|
+
if (e.stage === "hash-mismatch") return `${S.registryHashMismatch}: ${e.detail}`;
|
|
19093
|
+
if (e.stage === "already-installed") return e.detail;
|
|
19094
|
+
return `${e.stage}: ${e.detail}`;
|
|
19095
|
+
}
|
|
19096
|
+
function registryCacheDir(home = cliHome()) {
|
|
19097
|
+
return path33.join(home, ".forge-cli", "cache", "pypi");
|
|
19098
|
+
}
|
|
19099
|
+
var REGISTRY_IGNORED_FLAGS = [
|
|
19100
|
+
["dir", "--dir"],
|
|
19101
|
+
// --profile/--scope/--components/--host/--gateway-profile/--native são
|
|
19102
|
+
// honrados via delegação ao instalador nativo da Forja (fase 2).
|
|
19103
|
+
["skipVerify", "--skip-verify"],
|
|
19104
|
+
["skipScripts", "--skip-scripts"],
|
|
19105
|
+
["retryFailed", "--retry-failed"],
|
|
19106
|
+
["shallow", "--shallow"],
|
|
19107
|
+
["fix", "--fix"],
|
|
19108
|
+
["rollback", "--rollback"],
|
|
19109
|
+
["trustCustom", "--trust-custom"],
|
|
19110
|
+
["runAfterSyncFailure", "--run-after-sync-failure"],
|
|
19111
|
+
["onlyChanged", "--only-changed"],
|
|
19112
|
+
["verbose", "--verbose"]
|
|
19113
|
+
];
|
|
19114
|
+
function ignoredRegistryFlags(cli) {
|
|
19115
|
+
const out = [];
|
|
19116
|
+
for (const [key, label] of REGISTRY_IGNORED_FLAGS) {
|
|
19117
|
+
if (cli[key] === void 0 || cli[key] === false) continue;
|
|
19118
|
+
out.push(label);
|
|
19119
|
+
}
|
|
19120
|
+
if (cli.timeoutSet) out.push("--timeout");
|
|
19121
|
+
if (cli.jobsSet) out.push("--jobs");
|
|
19122
|
+
return out;
|
|
19123
|
+
}
|
|
19124
|
+
function registryQuiet(cli) {
|
|
19125
|
+
return cli.jsonReport && cli.reportPath === void 0;
|
|
19126
|
+
}
|
|
19127
|
+
function validateRegistryNativeFlags(cli) {
|
|
19128
|
+
return validateNativeOptions({
|
|
19129
|
+
profile: cli.profile,
|
|
19130
|
+
scope: cli.scope,
|
|
19131
|
+
components: cli.components,
|
|
19132
|
+
host: cli.host,
|
|
19133
|
+
gatewayProfile: cli.gatewayProfile
|
|
19134
|
+
});
|
|
19135
|
+
}
|
|
19136
|
+
async function delegateNative(cli, entry2, receipt, op, run = execaRun) {
|
|
19137
|
+
const wants = cli.native || hasNativeRequest({
|
|
19138
|
+
profile: cli.profile,
|
|
19139
|
+
scope: cli.scope,
|
|
19140
|
+
components: cli.components,
|
|
19141
|
+
host: cli.host,
|
|
19142
|
+
gatewayProfile: cli.gatewayProfile
|
|
19143
|
+
});
|
|
19144
|
+
if (!wants) return void 0;
|
|
19145
|
+
const repo = FORGE_REPOS.find((r2) => r2.name === entry2.name);
|
|
19146
|
+
const built = repo && (op === "install" ? buildNativeInstall(repo, {
|
|
19147
|
+
profile: cli.profile,
|
|
19148
|
+
scope: cli.scope,
|
|
19149
|
+
components: cli.components,
|
|
19150
|
+
host: cli.host,
|
|
19151
|
+
gatewayProfile: cli.gatewayProfile,
|
|
19152
|
+
approve: cli.yes
|
|
19153
|
+
}) : buildNativeOp(repo, "update", cli.yes ?? false));
|
|
19154
|
+
if (!built) {
|
|
19155
|
+
return { argv: [], ran: false, skipped: "sem instalador nativo documentado" };
|
|
19156
|
+
}
|
|
19157
|
+
if (built.argv[0] === "{python}") {
|
|
19158
|
+
return {
|
|
19159
|
+
argv: built.argv,
|
|
19160
|
+
ran: false,
|
|
19161
|
+
skipped: "instalador por script do reposit\xF3rio (scripts/forge_install.py) \u2014 indispon\xEDvel no m\xE9todo registry"
|
|
19162
|
+
};
|
|
19163
|
+
}
|
|
19164
|
+
const argv = [receipt.bin, ...built.argv.slice(1)];
|
|
19165
|
+
const res = await run(argv[0], argv.slice(1));
|
|
19166
|
+
const ignored = "ignored" in built ? built.ignored : [];
|
|
19167
|
+
return {
|
|
19168
|
+
argv,
|
|
19169
|
+
ran: true,
|
|
19170
|
+
dryRun: built.dryRun,
|
|
19171
|
+
exitCode: res.exitCode,
|
|
19172
|
+
...ignored.length ? { ignored } : {}
|
|
19173
|
+
};
|
|
19174
|
+
}
|
|
19175
|
+
function persistReceipt(home, receipt) {
|
|
19176
|
+
writeFileAtomic(receiptPath(home, receipt.forge), `${JSON.stringify(receipt, null, 2)}
|
|
19177
|
+
`);
|
|
19178
|
+
const state = loadForgeState(home);
|
|
19179
|
+
if (Object.hasOwn(state.forges, receipt.forge)) {
|
|
19180
|
+
state.forges[receipt.forge] = receipt;
|
|
19181
|
+
saveForgeState(home, state);
|
|
19182
|
+
}
|
|
19183
|
+
}
|
|
19184
|
+
function writeRegistryReport(reportPath, doc) {
|
|
19185
|
+
const file = path33.resolve(reportPath);
|
|
19186
|
+
mkdirSync9(path33.dirname(file), { recursive: true });
|
|
19187
|
+
writeFileAtomic(file, redactSecrets(JSON.stringify(doc, null, 2)));
|
|
19188
|
+
}
|
|
19189
|
+
async function runRegistryInstall(cli, names, deps = {}) {
|
|
19190
|
+
const home = deps.home ?? cliHome();
|
|
19191
|
+
const quiet = registryQuiet(cli);
|
|
19192
|
+
const reportTo = (doc2) => {
|
|
19193
|
+
if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
|
|
19194
|
+
};
|
|
19195
|
+
const entries = names.map((n4) => ({ name: n4, entry: registryEntry(n4) }));
|
|
19196
|
+
const unknown = entries.filter((e) => !e.entry);
|
|
19197
|
+
for (const e of entries) if (e.entry) e.name = e.entry.name;
|
|
19198
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19199
|
+
const deduped = entries.filter((e) => {
|
|
19200
|
+
const key = e.entry?.name ?? e.name;
|
|
19201
|
+
if (seen.has(key)) return false;
|
|
19202
|
+
seen.add(key);
|
|
19203
|
+
return true;
|
|
19204
|
+
});
|
|
19205
|
+
if (unknown.length) {
|
|
19206
|
+
const msg = `${unknown.map((u4) => u4.name).join(", ")}: sem dist no cat\xE1logo registry \u2014 use o m\xE9todo git`;
|
|
19207
|
+
reportTo({ error: msg });
|
|
19208
|
+
if (quiet) emitJson({ error: msg }, 2);
|
|
19209
|
+
log.error(msg);
|
|
19210
|
+
process.exit(2);
|
|
19211
|
+
}
|
|
19212
|
+
const ignored = ignoredRegistryFlags(cli);
|
|
19213
|
+
const warnings = ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : [];
|
|
19214
|
+
if (warnings.length && !quiet) log.warn(warnings[0]);
|
|
19215
|
+
const nativeErr = validateRegistryNativeFlags(cli);
|
|
19216
|
+
if (nativeErr) {
|
|
19217
|
+
reportTo({ error: nativeErr, warnings });
|
|
19218
|
+
if (quiet) emitJson({ error: nativeErr, warnings }, 1);
|
|
19219
|
+
log.error(nativeErr);
|
|
19220
|
+
process.exit(1);
|
|
19221
|
+
return [];
|
|
19222
|
+
}
|
|
19223
|
+
const pin = cli.ref?.replace(/^v(?=\d)/, "");
|
|
19224
|
+
const pythons = deps.pythons ?? await probePythons();
|
|
19225
|
+
if (!pythons.length) {
|
|
19226
|
+
const msg = "Nenhum interpretador Python detectado \u2014 instale Python \u22653.10";
|
|
19227
|
+
reportTo({ error: msg, warnings });
|
|
19228
|
+
if (quiet) emitJson({ error: msg, warnings }, 1);
|
|
19229
|
+
log.error(msg);
|
|
19230
|
+
process.exit(1);
|
|
19231
|
+
}
|
|
19232
|
+
if (!quiet) log.info(`Python detectado: ${pythons.map((p) => p.version).join(", ")}`);
|
|
19233
|
+
if (cli.plan || cli.check) {
|
|
19234
|
+
const planned = [];
|
|
19235
|
+
for (const { name, entry: entry2 } of deduped) {
|
|
19236
|
+
const resolved = await resolveRelease(entry2.dist, {
|
|
19237
|
+
pin,
|
|
19238
|
+
offline: cli.offline,
|
|
19239
|
+
cacheDir: registryCacheDir(home),
|
|
19240
|
+
fetchImpl: deps.fetchImpl,
|
|
19241
|
+
ephemeral: true
|
|
19242
|
+
// P0-002: plan não persiste nem cache
|
|
19243
|
+
});
|
|
19244
|
+
if ("kind" in resolved) {
|
|
19245
|
+
planned.push({ forge: name, ok: false, detail: describeResolveError(resolved) });
|
|
19246
|
+
continue;
|
|
19247
|
+
}
|
|
19248
|
+
const picked = selectInterpreter(entry2, resolved, pythons);
|
|
19249
|
+
if ("error" in picked) {
|
|
19250
|
+
planned.push({ forge: name, ok: false, detail: describeResolveError(picked.error) });
|
|
19251
|
+
continue;
|
|
19252
|
+
}
|
|
19253
|
+
planned.push({
|
|
19254
|
+
forge: name,
|
|
19255
|
+
ok: true,
|
|
19256
|
+
dist: entry2.dist,
|
|
19257
|
+
version: resolved.version,
|
|
19258
|
+
requiresPython: resolved.requiresPython,
|
|
19259
|
+
python: picked.python.version,
|
|
19260
|
+
venv: forgeVenv(home, name),
|
|
19261
|
+
wheels: wheelCacheDir(registryCacheDir(home), entry2.dist),
|
|
19262
|
+
source: resolved.source,
|
|
19263
|
+
staleCache: resolved.staleCache,
|
|
19264
|
+
yanked: resolved.yanked
|
|
19265
|
+
});
|
|
19266
|
+
}
|
|
19267
|
+
const planDoc = { method: "pypi", plan: true, planned, warnings };
|
|
19268
|
+
reportTo(planDoc);
|
|
19269
|
+
if (quiet) {
|
|
19270
|
+
emitJson(planDoc, planned.every((p) => p.ok) ? 0 : 1);
|
|
19271
|
+
}
|
|
19272
|
+
note(
|
|
19273
|
+
planned.map(
|
|
19274
|
+
(p) => p.ok ? `${p.forge} \u2192 ${p.dist}==${p.version} (python ${p.python}, venv ${p.venv})` : `${p.forge} \u2192 falhou: ${p.detail}`
|
|
19275
|
+
).join("\n"),
|
|
19276
|
+
// venv/receipt/state não são escritos; o cache de metadados do
|
|
19277
|
+
// PyPI pode ser atualizado (mesma regra de `update --check`).
|
|
19278
|
+
"Plano (venv e estado n\xE3o alterados)"
|
|
19279
|
+
);
|
|
19280
|
+
if (planned.some((p) => !p.ok)) process.exit(1);
|
|
19281
|
+
outro(`${icons.ok} plano resolvido \u2014 execute sem --plan para instalar`);
|
|
19282
|
+
return planned.map((p) => ({
|
|
19283
|
+
forge: p.forge,
|
|
19284
|
+
ok: p.ok,
|
|
19285
|
+
detail: p.ok ? "planned" : p.detail ?? "falhou",
|
|
19286
|
+
version: p.version
|
|
19287
|
+
}));
|
|
19288
|
+
}
|
|
19289
|
+
if (!quiet && !cli.yes && process.stdout.isTTY && process.stdin.isTTY) {
|
|
19290
|
+
const lines = [];
|
|
19291
|
+
for (const { name, entry: entry2 } of deduped) {
|
|
19292
|
+
const r2 = await resolveRelease(entry2.dist, {
|
|
19293
|
+
pin,
|
|
19294
|
+
offline: cli.offline,
|
|
19295
|
+
cacheDir: registryCacheDir(home),
|
|
19296
|
+
fetchImpl: deps.fetchImpl
|
|
19297
|
+
});
|
|
19298
|
+
lines.push(
|
|
19299
|
+
"kind" in r2 ? `${name} \u2192 ${entry2.dist} (${describeResolveError(r2)})` : `${name} \u2192 ${r2.dist}==${r2.version} (venv isolada em ${forgeVenv(home, name)})`
|
|
19300
|
+
);
|
|
19301
|
+
}
|
|
19302
|
+
note(lines.join("\n"), "Instala\xE7\xE3o registry-first");
|
|
19303
|
+
const answer = await confirm({ message: S.confirmRun });
|
|
19304
|
+
if (isCancel(answer) || !answer) {
|
|
19305
|
+
if (isCancel(answer)) exitCancelled();
|
|
19306
|
+
log.warn("Nada foi instalado.");
|
|
19307
|
+
process.exit(0);
|
|
19308
|
+
return [];
|
|
19309
|
+
}
|
|
19310
|
+
}
|
|
19311
|
+
const results = [];
|
|
19312
|
+
for (const { name, entry: entry2 } of deduped) {
|
|
19313
|
+
const spin = spinner();
|
|
19314
|
+
if (!quiet) spin.start(`${name}: resolvendo ${entry2.dist}\u2026`);
|
|
19315
|
+
const receipt = await registryInstall(entry2, pin, {
|
|
19316
|
+
home,
|
|
19317
|
+
cacheDir: registryCacheDir(home),
|
|
19318
|
+
offline: cli.offline,
|
|
19319
|
+
pythons,
|
|
19320
|
+
clean: cli.clean,
|
|
19321
|
+
fetchImpl: deps.fetchImpl,
|
|
19322
|
+
run: deps.run
|
|
19323
|
+
});
|
|
19324
|
+
if ("stage" in receipt) {
|
|
19325
|
+
if (!quiet) spin.stop(`${name}: ${theme.err("falhou")}`);
|
|
19326
|
+
results.push({ forge: name, ok: false, detail: describeInstallError(receipt) });
|
|
19327
|
+
continue;
|
|
19328
|
+
}
|
|
19329
|
+
receipt.native = await delegateNative(cli, entry2, receipt, "install", deps.run);
|
|
19330
|
+
if (receipt.native) {
|
|
19331
|
+
persistReceipt(home, receipt);
|
|
19332
|
+
const n4 = receipt.native;
|
|
19333
|
+
if (!quiet) {
|
|
19334
|
+
if (n4.skipped) log.warn(`${name}: ${n4.skipped}`);
|
|
19335
|
+
else if (n4.exitCode !== 0) log.warn(`${name}: instalador nativo saiu ${n4.exitCode}`);
|
|
19336
|
+
else
|
|
19337
|
+
log.info(`${name}: nativo ${n4.dryRun ? "(dry-run)" : "aplicado"} \u2014 ${n4.argv.join(" ")}`);
|
|
19338
|
+
}
|
|
19339
|
+
if (n4.skipped) warnings.push(`${name}: ${n4.skipped}`);
|
|
19340
|
+
if (n4.ran && n4.exitCode !== 0) warnings.push(`${name}: instalador nativo saiu ${n4.exitCode}`);
|
|
19341
|
+
}
|
|
19342
|
+
if (receipt.staleCache) {
|
|
19343
|
+
const w = `${S.registryStaleCache} (${entry2.dist})`;
|
|
19344
|
+
warnings.push(w);
|
|
19345
|
+
if (!quiet) log.warn(w);
|
|
19346
|
+
}
|
|
19347
|
+
if (!quiet) {
|
|
19348
|
+
spin.stop(
|
|
19349
|
+
`${name}: ${theme.ok("instalado")} ${theme.dim(`${receipt.dist}==${receipt.version}`)}`
|
|
19350
|
+
);
|
|
19351
|
+
}
|
|
19352
|
+
results.push({
|
|
19353
|
+
forge: name,
|
|
19354
|
+
ok: true,
|
|
19355
|
+
version: receipt.version,
|
|
19356
|
+
detail: `${receipt.dist}==${receipt.version} em ${receipt.venv}`,
|
|
19357
|
+
...receipt.native ? { native: receipt.native } : {}
|
|
19358
|
+
});
|
|
19359
|
+
}
|
|
19360
|
+
const doc = { method: "pypi", results, warnings };
|
|
19361
|
+
reportTo(doc);
|
|
19362
|
+
if (cli.reportPath !== void 0) {
|
|
19363
|
+
log.info(`${S.reportSaved}: ${theme.dim(path33.resolve(cli.reportPath))}`);
|
|
19364
|
+
}
|
|
19365
|
+
if (quiet) {
|
|
19366
|
+
emitJson(doc, results.every((r2) => r2.ok) ? 0 : 1);
|
|
19367
|
+
}
|
|
19368
|
+
for (const r2 of results) {
|
|
19369
|
+
if (!r2.ok) log.error(`${r2.forge}: ${r2.detail}`);
|
|
19370
|
+
}
|
|
19371
|
+
if (results.some((r2) => !r2.ok)) process.exit(1);
|
|
19372
|
+
const bins = results.map((r2) => {
|
|
19373
|
+
const inst = loadForgeState(home).forges[r2.forge];
|
|
19374
|
+
return inst ? `${r2.forge} \u2192 ${inst.bin}` : "";
|
|
19375
|
+
});
|
|
19376
|
+
note(bins.filter(Boolean).join("\n"), "Para usar, adicione ao PATH ou invoque o caminho");
|
|
19377
|
+
outro(`${icons.ok} instala\xE7\xE3o registry-first conclu\xEDda`);
|
|
19378
|
+
return results;
|
|
19379
|
+
}
|
|
19380
|
+
function runRegistryList(cli, deps = {}) {
|
|
19381
|
+
const home = deps.home ?? cliHome();
|
|
19382
|
+
const state = loadForgeState(home);
|
|
19383
|
+
const wanted = deps.names?.length ? new Set(deps.names.map((n4) => registryEntry(n4)?.name).filter((n4) => Boolean(n4))) : null;
|
|
19384
|
+
if (deps.names?.length) {
|
|
19385
|
+
const bad = deps.names.filter((n4) => !registryEntry(n4));
|
|
19386
|
+
if (bad.length) {
|
|
19387
|
+
const msg = `${bad.join(", ")}: fora do cat\xE1logo registry`;
|
|
19388
|
+
if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, { error: msg });
|
|
19389
|
+
if (registryQuiet(cli)) emitJson({ error: msg }, 2);
|
|
19390
|
+
log.error(msg);
|
|
19391
|
+
process.exit(2);
|
|
19392
|
+
}
|
|
19393
|
+
}
|
|
19394
|
+
const rows = REGISTRY_PACKAGES.filter((e) => !wanted || wanted.has(e.name)).map((e) => {
|
|
19395
|
+
const inst = state.forges[e.name];
|
|
19396
|
+
const binOk = inst ? typeof inst.bin === "string" && existsSync21(inst.bin) : null;
|
|
19397
|
+
return {
|
|
19398
|
+
forge: e.name,
|
|
19399
|
+
dist: e.dist,
|
|
19400
|
+
installed: inst?.version ?? null,
|
|
19401
|
+
method: inst?.method ?? null,
|
|
19402
|
+
verifyOk: inst ? inst.verify?.ok ?? null : null,
|
|
19403
|
+
binPresent: binOk,
|
|
19404
|
+
bin: inst ? binOk ? inst.bin : `${inst.bin ?? "?"} (ausente)` : null,
|
|
19405
|
+
minPython: e.minPython
|
|
19406
|
+
};
|
|
19407
|
+
});
|
|
19408
|
+
if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, { forges: rows });
|
|
19409
|
+
if (registryQuiet(cli)) emitJson({ forges: rows });
|
|
19410
|
+
note(
|
|
19411
|
+
rows.map(
|
|
19412
|
+
(r2) => `${r2.installed ? icons.ok : "\xB7"} ${r2.forge.padEnd(20)} ${r2.dist.padEnd(20)} ${(r2.installed ?? "\u2014") + (r2.installed && r2.verifyOk === false ? ` ${S.registryVerifyFailed}` : "")}`
|
|
19413
|
+
).join("\n"),
|
|
19414
|
+
"Forjas (registry)"
|
|
19415
|
+
);
|
|
19416
|
+
}
|
|
19417
|
+
function resolveRegistryTargets(cli, names, installed) {
|
|
19418
|
+
if (names.length) return names;
|
|
19419
|
+
if (cli.only?.length) return cli.only;
|
|
19420
|
+
if (cli.all) return installed;
|
|
19421
|
+
return [];
|
|
19422
|
+
}
|
|
19423
|
+
async function runRegistryUninstall(cli, names, deps = {}) {
|
|
19424
|
+
const home = deps.home ?? cliHome();
|
|
19425
|
+
const quiet = registryQuiet(cli);
|
|
19426
|
+
const reportTo = (doc2) => {
|
|
19427
|
+
if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
|
|
19428
|
+
};
|
|
19429
|
+
const state = loadForgeState(home).forges;
|
|
19430
|
+
const installed = Object.keys(state);
|
|
19431
|
+
const targets = resolveRegistryTargets(cli, names, installed);
|
|
19432
|
+
if (!targets.length) {
|
|
19433
|
+
reportTo({ error: S.errRegistryUninstallNeedsNames });
|
|
19434
|
+
if (quiet) emitJson({ error: S.errRegistryUninstallNeedsNames }, 2);
|
|
19435
|
+
log.error(S.errRegistryUninstallNeedsNames);
|
|
19436
|
+
process.exit(2);
|
|
19437
|
+
}
|
|
19438
|
+
const ignored = ignoredRegistryFlags(cli);
|
|
19439
|
+
const warnings = ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : [];
|
|
19440
|
+
if (warnings.length && !quiet) log.warn(warnings[0]);
|
|
19441
|
+
const plan = [];
|
|
19442
|
+
const unsafe = [];
|
|
19443
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19444
|
+
for (const raw of targets) {
|
|
19445
|
+
const entry2 = registryEntry(raw);
|
|
19446
|
+
const name = entry2?.name ?? raw;
|
|
19447
|
+
if (!isSafeRepoName(name)) {
|
|
19448
|
+
unsafe.push(raw);
|
|
19449
|
+
continue;
|
|
19450
|
+
}
|
|
19451
|
+
if (seen.has(name)) continue;
|
|
19452
|
+
seen.add(name);
|
|
19453
|
+
const receipt = Object.hasOwn(state, name) ? state[name] : void 0;
|
|
19454
|
+
plan.push({
|
|
19455
|
+
name,
|
|
19456
|
+
dist: entry2?.dist ?? receipt?.dist,
|
|
19457
|
+
known: Boolean(entry2 ?? receipt),
|
|
19458
|
+
installed: installed.includes(name)
|
|
19459
|
+
});
|
|
19460
|
+
}
|
|
19461
|
+
const unknown = [...plan.filter((p) => !p.known).map((p) => p.name), ...unsafe];
|
|
19462
|
+
const errText = [
|
|
19463
|
+
...plan.filter((p) => !p.known).map((p) => `${p.name}: sem dist no cat\xE1logo registry`),
|
|
19464
|
+
...unsafe.map((u4) => `${u4}: nome inseguro no state \u2014 recusado (path traversal)`)
|
|
19465
|
+
].join(" | ");
|
|
19466
|
+
const removals = plan.filter((p) => p.installed);
|
|
19467
|
+
const skipped = plan.filter((p) => p.known && !p.installed).map((p) => p.name);
|
|
19468
|
+
warnings.push(...skipped.map((n4) => `${n4}: n\xE3o estava instalado via registry \u2014 ignorado`));
|
|
19469
|
+
if (!quiet) {
|
|
19470
|
+
for (const u4 of plan.filter((p) => !p.known).map((p) => p.name))
|
|
19471
|
+
log.error(`${u4}: sem dist no cat\xE1logo registry`);
|
|
19472
|
+
for (const u4 of unsafe) log.error(`${u4}: nome inseguro no state \u2014 recusado (path traversal)`);
|
|
19473
|
+
for (const w of warnings.slice(ignored.length ? 1 : 0)) log.warn(w);
|
|
19474
|
+
}
|
|
19475
|
+
if (!removals.length) {
|
|
19476
|
+
const doc2 = {
|
|
19477
|
+
method: "pypi",
|
|
19478
|
+
removed: [],
|
|
19479
|
+
skipped,
|
|
19480
|
+
warnings,
|
|
19481
|
+
...errText ? { error: errText } : {}
|
|
19482
|
+
};
|
|
19483
|
+
reportTo(doc2);
|
|
19484
|
+
if (quiet) emitJson(doc2, unknown.length ? 2 : 0);
|
|
19485
|
+
if (unknown.length) process.exit(2);
|
|
19486
|
+
log.info("Nada a remover.");
|
|
19487
|
+
return;
|
|
19488
|
+
}
|
|
19489
|
+
if (!quiet) {
|
|
19490
|
+
note(
|
|
19491
|
+
removals.map((r2) => `${r2.name} \u2192 ${r2.dist ?? "?"} (venv + wheel cache + receipt em ~/.forge-cli/)`).join("\n"),
|
|
19492
|
+
S.registryUninstallConfirm
|
|
19493
|
+
);
|
|
19494
|
+
}
|
|
19495
|
+
if (!cli.yes) {
|
|
19496
|
+
const needYesDoc = {
|
|
19497
|
+
error: S.registryUninstallNeedsYes,
|
|
19498
|
+
planned: removals.map((r2) => r2.name),
|
|
19499
|
+
skipped,
|
|
19500
|
+
warnings
|
|
19501
|
+
};
|
|
19502
|
+
if (quiet) {
|
|
19503
|
+
emitJson(needYesDoc, 2);
|
|
19504
|
+
}
|
|
19505
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
19506
|
+
reportTo(needYesDoc);
|
|
19507
|
+
log.warn(S.registryUninstallNeedsYes);
|
|
19508
|
+
process.exit(0);
|
|
19509
|
+
return;
|
|
19510
|
+
}
|
|
19511
|
+
const answer = await confirm({ message: S.registryUninstallConfirm });
|
|
19512
|
+
if (isCancel(answer) || !answer) {
|
|
19513
|
+
if (isCancel(answer)) exitCancelled();
|
|
19514
|
+
reportTo(needYesDoc);
|
|
19515
|
+
log.warn(S.registryUninstallNeedsYes);
|
|
19516
|
+
process.exit(0);
|
|
19517
|
+
return;
|
|
19518
|
+
}
|
|
19519
|
+
}
|
|
19520
|
+
const results = [];
|
|
19521
|
+
for (const r2 of removals) {
|
|
19522
|
+
let native;
|
|
19523
|
+
if (cli.native) {
|
|
19524
|
+
const repo = FORGE_REPOS.find((x) => x.name === r2.name);
|
|
19525
|
+
const receipt = Object.hasOwn(state, r2.name) ? state[r2.name] : void 0;
|
|
19526
|
+
const op = repo && buildNativeOp(repo, "uninstall", cli.yes ?? false);
|
|
19527
|
+
if (!op) {
|
|
19528
|
+
native = { argv: [], ran: false, skipped: "sem op nativa de uninstall documentada" };
|
|
19529
|
+
} else if (op.argv[0] === "{python}" || !receipt) {
|
|
19530
|
+
native = {
|
|
19531
|
+
argv: op.argv,
|
|
19532
|
+
ran: false,
|
|
19533
|
+
skipped: "op nativa por script do reposit\xF3rio \u2014 indispon\xEDvel no m\xE9todo registry"
|
|
19534
|
+
};
|
|
19535
|
+
} else {
|
|
19536
|
+
const argv = [receipt.bin, ...op.argv.slice(1)];
|
|
19537
|
+
const res2 = await (deps.run ?? execaRun)(argv[0], argv.slice(1));
|
|
19538
|
+
native = { argv, ran: true, dryRun: op.dryRun, exitCode: res2.exitCode };
|
|
19539
|
+
if (res2.exitCode !== 0) warnings.push(`${r2.name}: uninstall nativo saiu ${res2.exitCode}`);
|
|
19540
|
+
}
|
|
19541
|
+
}
|
|
19542
|
+
const res = registryUninstall({ name: r2.name, dist: r2.dist }, { home });
|
|
19543
|
+
if (res.refused) log.error(`${r2.name}: recusado \u2014 ${res.refused}`);
|
|
19544
|
+
results.push({
|
|
19545
|
+
forge: r2.name,
|
|
19546
|
+
removed: res.removed,
|
|
19547
|
+
cacheRemoved: res.cacheRemoved,
|
|
19548
|
+
...native ? { native } : {}
|
|
19549
|
+
});
|
|
19550
|
+
}
|
|
19551
|
+
const doc = {
|
|
19552
|
+
method: "pypi",
|
|
19553
|
+
results,
|
|
19554
|
+
skipped,
|
|
19555
|
+
warnings,
|
|
19556
|
+
...errText ? { error: errText } : {}
|
|
19557
|
+
};
|
|
19558
|
+
reportTo(doc);
|
|
19559
|
+
if (quiet) emitJson(doc, unknown.length ? 2 : 0);
|
|
19560
|
+
for (const r2 of results) {
|
|
19561
|
+
if (r2.removed) log.success(`${r2.forge}: removido`);
|
|
19562
|
+
}
|
|
19563
|
+
if (unknown.length) process.exit(2);
|
|
19564
|
+
}
|
|
19565
|
+
function entryFor(name, receipt) {
|
|
19566
|
+
return registryEntry(name) ?? ghostEntry(name, receipt);
|
|
19567
|
+
}
|
|
19568
|
+
function lifecycleTargets(cli, names, home) {
|
|
19569
|
+
const state = loadForgeState(home).forges;
|
|
19570
|
+
const targets = resolveRegistryTargets(cli, names, Object.keys(state));
|
|
19571
|
+
const ok = [];
|
|
19572
|
+
const unknown = [];
|
|
19573
|
+
const skipped = [];
|
|
19574
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19575
|
+
for (const raw of targets) {
|
|
19576
|
+
const entry2 = registryEntry(raw);
|
|
19577
|
+
const name = entry2?.name ?? raw;
|
|
19578
|
+
if (!isSafeRepoName(name) || seen.has(name)) {
|
|
19579
|
+
if (!seen.has(name) && !isSafeRepoName(name)) unknown.push(raw);
|
|
19580
|
+
continue;
|
|
19581
|
+
}
|
|
19582
|
+
seen.add(name);
|
|
19583
|
+
const receipt = Object.hasOwn(state, name) ? state[name] : void 0;
|
|
19584
|
+
if (!receipt) {
|
|
19585
|
+
if (entry2) skipped.push(name);
|
|
19586
|
+
else unknown.push(name);
|
|
19587
|
+
continue;
|
|
19588
|
+
}
|
|
19589
|
+
ok.push({ name, receipt });
|
|
19590
|
+
}
|
|
19591
|
+
return { ok, unknown, skipped };
|
|
19592
|
+
}
|
|
19593
|
+
async function runRegistryLifecycle(cli, names, deps) {
|
|
19594
|
+
const home = deps.home ?? cliHome();
|
|
19595
|
+
const quiet = registryQuiet(cli);
|
|
19596
|
+
const reportTo = (doc2) => {
|
|
19597
|
+
if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
|
|
19598
|
+
};
|
|
19599
|
+
const { ok: targets, unknown, skipped } = lifecycleTargets(cli, names, home);
|
|
19600
|
+
if (!targets.length && !skipped.length && !unknown.length) {
|
|
19601
|
+
const msg = "nenhuma Forja instalada via registry \u2014 nada a atualizar";
|
|
19602
|
+
reportTo({ error: msg });
|
|
19603
|
+
if (quiet) emitJson({ error: msg }, 2);
|
|
19604
|
+
log.error(msg);
|
|
19605
|
+
process.exit(2);
|
|
19606
|
+
return;
|
|
19607
|
+
}
|
|
19608
|
+
const ignored = ignoredRegistryFlags(cli);
|
|
19609
|
+
const warnings = [
|
|
19610
|
+
...skipped.map((n4) => `${n4}: n\xE3o estava instalado via registry \u2014 ignorado`),
|
|
19611
|
+
...ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : []
|
|
19612
|
+
];
|
|
19613
|
+
const pinVersion2 = cli.ref?.replace(/^v(?=\d)/, "");
|
|
19614
|
+
const isCheck = cli.plan || cli.check;
|
|
19615
|
+
const items = [];
|
|
19616
|
+
for (const t2 of targets) {
|
|
19617
|
+
const entry2 = entryFor(t2.name, t2.receipt);
|
|
19618
|
+
if (t2.receipt.pinned && !pinVersion2) {
|
|
19619
|
+
items.push({
|
|
19620
|
+
...t2,
|
|
19621
|
+
entry: entry2,
|
|
19622
|
+
action: "update",
|
|
19623
|
+
from: t2.receipt.version,
|
|
19624
|
+
detail: `${S.registryPinnedAt} ${t2.receipt.pinned}`
|
|
19625
|
+
});
|
|
19626
|
+
continue;
|
|
19627
|
+
}
|
|
19628
|
+
const resolved = await resolveRelease(entry2.dist, {
|
|
19629
|
+
pin: pinVersion2,
|
|
19630
|
+
offline: cli.offline,
|
|
19631
|
+
cacheDir: registryCacheDir(home),
|
|
19632
|
+
fetchImpl: deps.fetchImpl,
|
|
19633
|
+
ephemeral: isCheck
|
|
19634
|
+
// P0-002: update --check não persiste nem cache
|
|
19635
|
+
});
|
|
19636
|
+
if ("kind" in resolved) {
|
|
19637
|
+
items.push({
|
|
19638
|
+
...t2,
|
|
19639
|
+
entry: entry2,
|
|
19640
|
+
action: "update",
|
|
19641
|
+
from: t2.receipt.version,
|
|
19642
|
+
detail: describeResolveError(resolved)
|
|
19643
|
+
});
|
|
19644
|
+
continue;
|
|
19645
|
+
}
|
|
19646
|
+
const same = resolved.version === t2.receipt.version;
|
|
19647
|
+
items.push({
|
|
19648
|
+
...t2,
|
|
19649
|
+
entry: entry2,
|
|
19650
|
+
action: "update",
|
|
19651
|
+
from: t2.receipt.version,
|
|
19652
|
+
to: resolved.version,
|
|
19653
|
+
detail: same ? "j\xE1 na mais recente" : void 0
|
|
19654
|
+
});
|
|
19655
|
+
}
|
|
19656
|
+
if (isCheck) {
|
|
19657
|
+
const doc2 = {
|
|
19658
|
+
method: "pypi",
|
|
19659
|
+
plan: true,
|
|
19660
|
+
updates: items.map((i3) => ({
|
|
19661
|
+
forge: i3.name,
|
|
19662
|
+
from: i3.from,
|
|
19663
|
+
to: i3.to ?? null,
|
|
19664
|
+
status: i3.detail ?? "update"
|
|
19665
|
+
})),
|
|
19666
|
+
skipped,
|
|
19667
|
+
warnings,
|
|
19668
|
+
...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
|
|
19669
|
+
};
|
|
19670
|
+
reportTo(doc2);
|
|
19671
|
+
if (quiet) emitJson(doc2, unknown.length ? 2 : 0);
|
|
19672
|
+
note(
|
|
19673
|
+
items.map((i3) => `${i3.name}: ${i3.from} \u2192 ${i3.to ?? "\u2014"}${i3.detail ? ` (${i3.detail})` : ""}`).join("\n"),
|
|
19674
|
+
"Plano de update"
|
|
19675
|
+
);
|
|
19676
|
+
if (unknown.length) process.exit(2);
|
|
19677
|
+
return;
|
|
19678
|
+
}
|
|
19679
|
+
const actionable = items.filter((i3) => i3.to && i3.to !== i3.from);
|
|
19680
|
+
if (!quiet) {
|
|
19681
|
+
for (const i3 of items.filter((i4) => i4.detail)) log.warn(`${i3.name}: ${i3.detail}`);
|
|
19682
|
+
for (const w of warnings) log.warn(w);
|
|
19683
|
+
note(
|
|
19684
|
+
actionable.length ? actionable.map((i3) => `${i3.name}: ${i3.from} \u2192 ${i3.to}`).join("\n") : "nada a atualizar",
|
|
19685
|
+
"Update registry-first"
|
|
19686
|
+
);
|
|
19687
|
+
}
|
|
19688
|
+
if (!actionable.length) {
|
|
19689
|
+
const doc2 = {
|
|
19690
|
+
method: "pypi",
|
|
19691
|
+
updated: [],
|
|
19692
|
+
skipped,
|
|
19693
|
+
warnings,
|
|
19694
|
+
...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
|
|
19695
|
+
};
|
|
19696
|
+
reportTo(doc2);
|
|
19697
|
+
if (quiet) emitJson(doc2, unknown.length ? 2 : skipped.length && !items.length ? 1 : 0);
|
|
19698
|
+
if (!quiet) log.info("Nada a atualizar.");
|
|
19699
|
+
if (unknown.length) process.exit(2);
|
|
19700
|
+
if (skipped.length && !items.length) process.exit(1);
|
|
19701
|
+
return;
|
|
19702
|
+
}
|
|
19703
|
+
if (!quiet && !cli.yes && process.stdout.isTTY && process.stdin.isTTY) {
|
|
19704
|
+
const answer = await confirm({ message: S.confirmRun });
|
|
19705
|
+
if (isCancel(answer) || !answer) {
|
|
19706
|
+
if (isCancel(answer)) exitCancelled();
|
|
19707
|
+
reportTo({ error: S.registryUninstallNeedsYes });
|
|
19708
|
+
log.warn(S.registryUninstallNeedsYes);
|
|
19709
|
+
process.exit(0);
|
|
19710
|
+
return;
|
|
19711
|
+
}
|
|
19712
|
+
}
|
|
19713
|
+
const pythons = deps.pythons ?? await probePythons();
|
|
19714
|
+
const results = [];
|
|
19715
|
+
const state = loadForgeState(home);
|
|
19716
|
+
for (const i3 of actionable) {
|
|
19717
|
+
const spin = spinner();
|
|
19718
|
+
if (!quiet) spin.start(`${i3.name}: ${i3.from} \u2192 ${i3.to}\u2026`);
|
|
19719
|
+
const receipt = await registryInstall(i3.entry, i3.to, {
|
|
19720
|
+
home,
|
|
19721
|
+
cacheDir: registryCacheDir(home),
|
|
19722
|
+
offline: cli.offline,
|
|
19723
|
+
pythons,
|
|
19724
|
+
clean: true,
|
|
19725
|
+
fetchImpl: deps.fetchImpl,
|
|
19726
|
+
run: deps.run
|
|
19727
|
+
});
|
|
19728
|
+
if ("stage" in receipt) {
|
|
19729
|
+
if (!quiet) spin.stop(`${i3.name}: ${theme.err("falhou")} (${describeInstallError(receipt)})`);
|
|
19730
|
+
results.push({
|
|
19731
|
+
forge: i3.name,
|
|
19732
|
+
ok: false,
|
|
19733
|
+
from: i3.from,
|
|
19734
|
+
to: i3.to,
|
|
19735
|
+
detail: describeInstallError(receipt)
|
|
19736
|
+
});
|
|
19737
|
+
continue;
|
|
19738
|
+
}
|
|
19739
|
+
const prev = state.forges[i3.name];
|
|
19740
|
+
receipt.history = [
|
|
19741
|
+
...i3.receipt.history ?? [],
|
|
19742
|
+
{ version: i3.from, installedAt: i3.receipt.installedAt }
|
|
19743
|
+
];
|
|
19744
|
+
receipt.pinned = prev?.pinned;
|
|
19745
|
+
receipt.native = await delegateNative(cli, i3.entry, receipt, "update", deps.run);
|
|
19746
|
+
if (receipt.native?.skipped) warnings.push(`${i3.name}: ${receipt.native.skipped}`);
|
|
19747
|
+
if (receipt.native?.ran && receipt.native.exitCode !== 0)
|
|
19748
|
+
warnings.push(`${i3.name}: instalador nativo saiu ${receipt.native.exitCode}`);
|
|
19749
|
+
state.forges[i3.name] = receipt;
|
|
19750
|
+
if (!quiet) spin.stop(`${i3.name}: ${theme.ok(`${i3.from} \u2192 ${i3.to}`)}`);
|
|
19751
|
+
results.push({ forge: i3.name, ok: true, from: i3.from, to: i3.to, bin: receipt.bin });
|
|
19752
|
+
}
|
|
19753
|
+
saveForgeState(home, state);
|
|
19754
|
+
const doc = {
|
|
19755
|
+
method: "pypi",
|
|
19756
|
+
updated: results,
|
|
19757
|
+
skipped,
|
|
19758
|
+
warnings,
|
|
19759
|
+
...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
|
|
19760
|
+
};
|
|
19761
|
+
reportTo(doc);
|
|
19762
|
+
if (quiet) emitJson(doc, results.every((r2) => r2.ok) ? unknown.length ? 2 : 0 : 1);
|
|
19763
|
+
if (results.some((r2) => !r2.ok)) process.exit(1);
|
|
19764
|
+
if (unknown.length) process.exit(2);
|
|
19765
|
+
}
|
|
19766
|
+
async function runRegistryUpdate(cli, names, deps = {}) {
|
|
19767
|
+
await runRegistryLifecycle(cli, names, deps);
|
|
19768
|
+
}
|
|
19769
|
+
async function runRegistryReinstall(cli, names, deps = {}) {
|
|
19770
|
+
const home = deps.home ?? cliHome();
|
|
19771
|
+
const quiet = registryQuiet(cli);
|
|
19772
|
+
const reportTo = (doc2) => {
|
|
19773
|
+
if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
|
|
19774
|
+
};
|
|
19775
|
+
const { ok: targets, unknown, skipped } = lifecycleTargets(cli, names, home);
|
|
19776
|
+
const ignored = ignoredRegistryFlags(cli);
|
|
19777
|
+
const warnings = [
|
|
19778
|
+
...skipped.map((n4) => `${n4}: n\xE3o instalado via registry \u2014 ignorado`),
|
|
19779
|
+
...ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : []
|
|
19780
|
+
];
|
|
19781
|
+
if (!targets.length) {
|
|
19782
|
+
const msg = S.errRegistryReinstallNeedsNames;
|
|
19783
|
+
reportTo({ error: msg, warnings, ...unknown.length ? { unknown } : {} });
|
|
19784
|
+
if (quiet) emitJson({ error: msg, warnings, unknown }, unknown.length ? 2 : 1);
|
|
19785
|
+
log.error(msg);
|
|
19786
|
+
process.exit(unknown.length ? 2 : 1);
|
|
19787
|
+
return;
|
|
19788
|
+
}
|
|
19789
|
+
if (cli.plan || cli.check) {
|
|
19790
|
+
const doc2 = {
|
|
19791
|
+
method: "pypi",
|
|
19792
|
+
plan: true,
|
|
19793
|
+
reinstalls: targets.map((t2) => ({ forge: t2.name, version: t2.receipt.version })),
|
|
19794
|
+
skipped,
|
|
19795
|
+
warnings
|
|
19796
|
+
};
|
|
19797
|
+
reportTo(doc2);
|
|
19798
|
+
if (quiet) emitJson(doc2, 0);
|
|
19799
|
+
note(
|
|
19800
|
+
targets.map((t2) => `${t2.name}==${t2.receipt.version} (reinstall limpo)`).join("\n"),
|
|
19801
|
+
"Plano de reinstall"
|
|
19802
|
+
);
|
|
19803
|
+
return;
|
|
19804
|
+
}
|
|
19805
|
+
const pythons = deps.pythons ?? await probePythons();
|
|
19806
|
+
const results = [];
|
|
19807
|
+
const state = loadForgeState(home);
|
|
19808
|
+
for (const t2 of targets) {
|
|
19809
|
+
const spin = spinner();
|
|
19810
|
+
if (!quiet) spin.start(`${t2.name}: reinstall ${t2.receipt.version}\u2026`);
|
|
19811
|
+
const receipt = await registryInstall(entryFor(t2.name, t2.receipt), t2.receipt.version, {
|
|
19812
|
+
home,
|
|
19813
|
+
cacheDir: registryCacheDir(home),
|
|
19814
|
+
offline: cli.offline,
|
|
19815
|
+
pythons,
|
|
19816
|
+
clean: true,
|
|
19817
|
+
fetchImpl: deps.fetchImpl,
|
|
19818
|
+
run: deps.run
|
|
19819
|
+
});
|
|
19820
|
+
if ("stage" in receipt) {
|
|
19821
|
+
if (!quiet) spin.stop(`${t2.name}: ${theme.err("falhou")} (${describeInstallError(receipt)})`);
|
|
19822
|
+
results.push({ forge: t2.name, ok: false, detail: describeInstallError(receipt) });
|
|
19823
|
+
continue;
|
|
19824
|
+
}
|
|
19825
|
+
receipt.pinned = t2.receipt.pinned;
|
|
19826
|
+
receipt.history = t2.receipt.history;
|
|
19827
|
+
receipt.native = await delegateNative(
|
|
19828
|
+
cli,
|
|
19829
|
+
entryFor(t2.name, t2.receipt),
|
|
19830
|
+
receipt,
|
|
19831
|
+
"update",
|
|
19832
|
+
deps.run
|
|
19833
|
+
);
|
|
19834
|
+
if (receipt.native?.skipped) warnings.push(`${t2.name}: ${receipt.native.skipped}`);
|
|
19835
|
+
if (receipt.native?.ran && receipt.native.exitCode !== 0)
|
|
19836
|
+
warnings.push(`${t2.name}: instalador nativo saiu ${receipt.native.exitCode}`);
|
|
19837
|
+
state.forges[t2.name] = receipt;
|
|
19838
|
+
if (!quiet) spin.stop(`${t2.name}: ${theme.ok("reinstalado")} (${receipt.version})`);
|
|
19839
|
+
results.push({ forge: t2.name, ok: true, version: receipt.version, bin: receipt.bin });
|
|
19840
|
+
}
|
|
19841
|
+
saveForgeState(home, state);
|
|
19842
|
+
const doc = {
|
|
19843
|
+
method: "pypi",
|
|
19844
|
+
reinstalled: results,
|
|
19845
|
+
skipped,
|
|
19846
|
+
warnings,
|
|
19847
|
+
...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
|
|
19848
|
+
};
|
|
19849
|
+
reportTo(doc);
|
|
19850
|
+
if (quiet) emitJson(doc, results.every((r2) => r2.ok) ? unknown.length ? 2 : 0 : 1);
|
|
19851
|
+
if (results.some((r2) => !r2.ok)) process.exit(1);
|
|
19852
|
+
if (unknown.length) process.exit(2);
|
|
19853
|
+
}
|
|
19854
|
+
async function runRegistryRollback(cli, names, deps = {}) {
|
|
19855
|
+
const home = deps.home ?? cliHome();
|
|
19856
|
+
const quiet = registryQuiet(cli);
|
|
19857
|
+
const reportTo = (doc2) => {
|
|
19858
|
+
if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
|
|
19859
|
+
};
|
|
19860
|
+
const { ok: targets, unknown, skipped } = lifecycleTargets(cli, names, home);
|
|
19861
|
+
const ignored = ignoredRegistryFlags(cli);
|
|
19862
|
+
const warnings = [
|
|
19863
|
+
...skipped.map((n4) => `${n4}: n\xE3o instalado via registry \u2014 ignorado`),
|
|
19864
|
+
...ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : []
|
|
19865
|
+
];
|
|
19866
|
+
const plan = targets.map((t2) => {
|
|
19867
|
+
const prev = t2.receipt.history?.at(-1);
|
|
19868
|
+
return {
|
|
19869
|
+
name: t2.name,
|
|
19870
|
+
receipt: t2.receipt,
|
|
19871
|
+
to: prev?.version,
|
|
19872
|
+
detail: prev ? void 0 : S.rollbackNoPrevious
|
|
19873
|
+
};
|
|
19874
|
+
});
|
|
19875
|
+
const actionable = plan.filter((p) => p.to);
|
|
19876
|
+
if (cli.plan || cli.check) {
|
|
19877
|
+
const doc2 = {
|
|
19878
|
+
method: "pypi",
|
|
19879
|
+
plan: true,
|
|
19880
|
+
rollbacks: plan.map((p) => ({
|
|
19881
|
+
forge: p.name,
|
|
19882
|
+
from: p.receipt.version,
|
|
19883
|
+
to: p.to ?? null,
|
|
19884
|
+
status: p.detail ?? "rollback"
|
|
19885
|
+
})),
|
|
19886
|
+
skipped,
|
|
19887
|
+
warnings
|
|
19888
|
+
};
|
|
19889
|
+
reportTo(doc2);
|
|
19890
|
+
if (quiet) emitJson(doc2, 0);
|
|
19891
|
+
note(
|
|
19892
|
+
plan.map(
|
|
19893
|
+
(p) => `${p.name}: ${p.receipt.version} \u2192 ${p.to ?? "\u2014"}${p.detail ? ` (${p.detail})` : ""}`
|
|
19894
|
+
).join("\n"),
|
|
19895
|
+
"Plano de rollback"
|
|
19896
|
+
);
|
|
19897
|
+
return;
|
|
19898
|
+
}
|
|
19899
|
+
if (!actionable.length) {
|
|
19900
|
+
const msg = targets.length ? `${S.rollbackNoPrevious} \u2014 ${targets.map((t2) => t2.name).join(", ")}` : S.errRegistryRollbackNeedsNames;
|
|
19901
|
+
reportTo({ error: msg, warnings, ...unknown.length ? { unknown } : {} });
|
|
19902
|
+
if (quiet) emitJson({ error: msg, warnings, unknown }, 1);
|
|
19903
|
+
log.error(msg);
|
|
19904
|
+
process.exit(1);
|
|
19905
|
+
return;
|
|
19906
|
+
}
|
|
19907
|
+
const pythons = deps.pythons ?? await probePythons();
|
|
19908
|
+
const results = [];
|
|
19909
|
+
const state = loadForgeState(home);
|
|
19910
|
+
for (const p of actionable) {
|
|
19911
|
+
const spin = spinner();
|
|
19912
|
+
if (!quiet) spin.start(`${p.name}: ${p.receipt.version} \u2192 ${p.to}\u2026`);
|
|
19913
|
+
const receipt = await registryInstall(entryFor(p.name, p.receipt), p.to, {
|
|
19914
|
+
home,
|
|
19915
|
+
cacheDir: registryCacheDir(home),
|
|
19916
|
+
offline: cli.offline,
|
|
19917
|
+
pythons,
|
|
19918
|
+
clean: true,
|
|
19919
|
+
fetchImpl: deps.fetchImpl,
|
|
19920
|
+
run: deps.run
|
|
19921
|
+
});
|
|
19922
|
+
if ("stage" in receipt) {
|
|
19923
|
+
if (!quiet) spin.stop(`${p.name}: ${theme.err("falhou")} (${describeInstallError(receipt)})`);
|
|
19924
|
+
results.push({ forge: p.name, ok: false, detail: describeInstallError(receipt) });
|
|
19925
|
+
continue;
|
|
19926
|
+
}
|
|
19927
|
+
const rest = (p.receipt.history ?? []).slice(0, -1);
|
|
19928
|
+
receipt.history = [...rest, { version: p.receipt.version, installedAt: p.receipt.installedAt }];
|
|
19929
|
+
receipt.pinned = p.receipt.pinned;
|
|
19930
|
+
state.forges[p.name] = receipt;
|
|
19931
|
+
if (!quiet) spin.stop(`${p.name}: ${theme.ok(`${p.receipt.version} \u2192 ${p.to}`)}`);
|
|
19932
|
+
results.push({ forge: p.name, ok: true, from: p.receipt.version, to: p.to, bin: receipt.bin });
|
|
19933
|
+
}
|
|
19934
|
+
saveForgeState(home, state);
|
|
19935
|
+
const doc = {
|
|
19936
|
+
method: "pypi",
|
|
19937
|
+
rolledBack: results,
|
|
19938
|
+
skipped,
|
|
19939
|
+
warnings,
|
|
19940
|
+
...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
|
|
19941
|
+
};
|
|
19942
|
+
reportTo(doc);
|
|
19943
|
+
if (quiet) emitJson(doc, results.every((r2) => r2.ok) ? unknown.length ? 2 : 0 : 1);
|
|
19944
|
+
if (results.some((r2) => !r2.ok)) process.exit(1);
|
|
19945
|
+
if (unknown.length) process.exit(2);
|
|
19946
|
+
}
|
|
19947
|
+
async function runRegistryPin(cli, names, pin, deps = {}) {
|
|
19948
|
+
const home = deps.home ?? cliHome();
|
|
19949
|
+
const quiet = registryQuiet(cli);
|
|
19950
|
+
const reportTo = (doc2) => {
|
|
19951
|
+
if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
|
|
19952
|
+
};
|
|
19953
|
+
const { ok: targets, unknown, skipped } = lifecycleTargets(cli, names, home);
|
|
19954
|
+
const ignored = ignoredRegistryFlags(cli);
|
|
19955
|
+
const warnings = [
|
|
19956
|
+
...skipped.map((n4) => `${n4}: n\xE3o instalado via registry \u2014 ignorado`),
|
|
19957
|
+
...ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : []
|
|
19958
|
+
];
|
|
19959
|
+
if (!targets.length) {
|
|
19960
|
+
const msg = pin ? S.errRegistryPinNeedsNames : S.errRegistryUnpinNeedsNames;
|
|
19961
|
+
reportTo({ error: msg, warnings, unknown });
|
|
19962
|
+
if (quiet) emitJson({ error: msg, warnings, unknown }, unknown.length ? 2 : 1);
|
|
19963
|
+
log.error(msg);
|
|
19964
|
+
process.exit(unknown.length ? 2 : 1);
|
|
19965
|
+
return;
|
|
19966
|
+
}
|
|
19967
|
+
const state = loadForgeState(home);
|
|
19968
|
+
const done = [];
|
|
19969
|
+
for (const t2 of targets) {
|
|
19970
|
+
if (pin) {
|
|
19971
|
+
state.forges[t2.name].pinned = t2.receipt.version;
|
|
19972
|
+
done.push({ forge: t2.name, pinned: t2.receipt.version });
|
|
19973
|
+
} else {
|
|
19974
|
+
delete state.forges[t2.name].pinned;
|
|
19975
|
+
done.push({ forge: t2.name, pinned: null });
|
|
19976
|
+
}
|
|
19977
|
+
}
|
|
19978
|
+
saveForgeState(home, state);
|
|
19979
|
+
const doc = {
|
|
19980
|
+
method: "pypi",
|
|
19981
|
+
[pin ? "pinned" : "unpinned"]: done,
|
|
19982
|
+
skipped,
|
|
19983
|
+
warnings,
|
|
19984
|
+
...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
|
|
19985
|
+
};
|
|
19986
|
+
reportTo(doc);
|
|
19987
|
+
if (quiet) emitJson(doc, unknown.length ? 2 : 0);
|
|
19988
|
+
for (const d of done) {
|
|
19989
|
+
log.success(pin ? `${d.forge}: ${S.pinDone} ${d.pinned}` : `${d.forge}: ${S.unpinDone}`);
|
|
19990
|
+
}
|
|
19991
|
+
if (unknown.length) process.exit(2);
|
|
19992
|
+
}
|
|
19993
|
+
|
|
19994
|
+
// src/lifecycle.ts
|
|
19995
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync10, readdirSync as readdirSync9, rmSync as rmSync5 } from "fs";
|
|
19996
|
+
import path34 from "path";
|
|
19997
|
+
function configFilesToBackup(repoDir) {
|
|
19998
|
+
const out = /* @__PURE__ */ new Set();
|
|
19999
|
+
if (existsSync22(path34.join(repoDir, ".env"))) out.add(".env");
|
|
20000
|
+
for (const file of readdirSync9(repoDir)) {
|
|
20001
|
+
if (file === ".env.example" || !file.includes(".example.")) continue;
|
|
20002
|
+
const real = file.replace(".example.", ".");
|
|
20003
|
+
if (existsSync22(path34.join(repoDir, real))) out.add(real);
|
|
20004
|
+
}
|
|
20005
|
+
return [...out];
|
|
20006
|
+
}
|
|
20007
|
+
async function planUninstall(targetDir, repo) {
|
|
20008
|
+
const dir = resolveInside(targetDir, repo.name);
|
|
20009
|
+
if (!dir)
|
|
20010
|
+
return { kind: "refused", dir: path34.join(targetDir, repo.name), reason: "invalid-name" };
|
|
20011
|
+
if (isLinkOrJunction(dir)) return { kind: "refused", dir, reason: "symlink" };
|
|
20012
|
+
if (!existsSync22(dir)) return { kind: "absent", dir };
|
|
20013
|
+
if (!hasGitRepo(dir)) return { kind: "refused", dir, reason: "non-git" };
|
|
20014
|
+
const remote = await commandOutput("git", ["-C", dir, "remote", "get-url", "origin"]);
|
|
20015
|
+
if (remote && remote !== "ok" && !sameRemote(remote, repo.url)) {
|
|
20016
|
+
return { kind: "refused", dir, reason: "remote-mismatch" };
|
|
20017
|
+
}
|
|
20018
|
+
if (await isGitDirty(dir)) return { kind: "refused", dir, reason: "dirty" };
|
|
20019
|
+
return { kind: "ready", dir, backups: configFilesToBackup(dir) };
|
|
20020
|
+
}
|
|
20021
|
+
function executeUninstall(targetDir, repo, plan) {
|
|
20022
|
+
const expected = resolveInside(targetDir, repo.name);
|
|
18259
20023
|
if (!expected || expected !== plan.dir) throw new Error("plano de remo\xE7\xE3o inconsistente");
|
|
18260
20024
|
let backupDir = null;
|
|
18261
20025
|
if (plan.backups.length) {
|
|
18262
|
-
backupDir =
|
|
20026
|
+
backupDir = path34.join(artifactsRoot(targetDir), "backups", `${repo.name}-${Date.now()}`);
|
|
18263
20027
|
for (const file of plan.backups) {
|
|
18264
|
-
const dest =
|
|
18265
|
-
|
|
18266
|
-
copyFileSync3(
|
|
20028
|
+
const dest = path34.join(backupDir, file);
|
|
20029
|
+
mkdirSync10(path34.dirname(dest), { recursive: true });
|
|
20030
|
+
copyFileSync3(path34.join(plan.dir, file), dest);
|
|
18267
20031
|
}
|
|
18268
20032
|
}
|
|
18269
|
-
|
|
20033
|
+
rmSync5(plan.dir, { recursive: true, force: true });
|
|
18270
20034
|
forgetRepo(targetDir, repo.name);
|
|
18271
20035
|
return backupDir;
|
|
18272
20036
|
}
|
|
@@ -18286,13 +20050,13 @@ async function rollbackRepo(targetDir, repo) {
|
|
|
18286
20050
|
}
|
|
18287
20051
|
|
|
18288
20052
|
// src/self.ts
|
|
18289
|
-
import
|
|
20053
|
+
import path35 from "path";
|
|
18290
20054
|
function norm(p) {
|
|
18291
|
-
return
|
|
20055
|
+
return path35.resolve(p).replace(/\\/g, "/").toLowerCase();
|
|
18292
20056
|
}
|
|
18293
20057
|
function detectInstallMethod(scriptPath, home) {
|
|
18294
20058
|
const script = norm(scriptPath);
|
|
18295
|
-
if (script.startsWith(`${norm(
|
|
20059
|
+
if (script.startsWith(`${norm(path35.join(home, ".forge-cli", "bin"))}/`)) return "shim";
|
|
18296
20060
|
if (script.includes("/node_modules/")) return "npm-global";
|
|
18297
20061
|
return "source";
|
|
18298
20062
|
}
|
|
@@ -18474,14 +20238,14 @@ async function runUninstallCommand() {
|
|
|
18474
20238
|
}
|
|
18475
20239
|
|
|
18476
20240
|
// src/config.ts
|
|
18477
|
-
import { existsSync as
|
|
18478
|
-
import
|
|
20241
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
|
|
20242
|
+
import path36 from "path";
|
|
18479
20243
|
function loadConfig(targetDir) {
|
|
18480
|
-
const file =
|
|
18481
|
-
if (!
|
|
20244
|
+
const file = path36.join(targetDir, "forge-cli.config.json");
|
|
20245
|
+
if (!existsSync23(file)) return null;
|
|
18482
20246
|
let parsed;
|
|
18483
20247
|
try {
|
|
18484
|
-
parsed = JSON.parse(
|
|
20248
|
+
parsed = JSON.parse(readFileSync18(file, "utf8"));
|
|
18485
20249
|
} catch {
|
|
18486
20250
|
return { error: "invalid-json" };
|
|
18487
20251
|
}
|
|
@@ -18524,12 +20288,12 @@ function loadConfig(targetDir) {
|
|
|
18524
20288
|
}
|
|
18525
20289
|
|
|
18526
20290
|
// src/plan.ts
|
|
18527
|
-
import { existsSync as
|
|
18528
|
-
import
|
|
20291
|
+
import { existsSync as existsSync24, readdirSync as readdirSync10 } from "fs";
|
|
20292
|
+
import path37 from "path";
|
|
18529
20293
|
function planLines(repo, targetDir) {
|
|
18530
|
-
const repoDir =
|
|
20294
|
+
const repoDir = path37.join(targetDir, repo.name);
|
|
18531
20295
|
const lines = [];
|
|
18532
|
-
if (!
|
|
20296
|
+
if (!existsSync24(repoDir)) {
|
|
18533
20297
|
lines.push(`${S.planSync}: git clone (${S.planDetectAfter})`);
|
|
18534
20298
|
for (const step of [
|
|
18535
20299
|
S.phaseEnv,
|
|
@@ -18552,7 +20316,7 @@ function planLines(repo, targetDir) {
|
|
|
18552
20316
|
lines.push(
|
|
18553
20317
|
`${S.phaseEnv}: ${envAction === "exists" ? S.planEnvKeep : envAction === "copied" ? S.planEnvCopy : S.planEnvStub}`
|
|
18554
20318
|
);
|
|
18555
|
-
const examples =
|
|
20319
|
+
const examples = readdirSync10(repoDir).filter(
|
|
18556
20320
|
(f) => f !== ".env.example" && f.includes(".example.")
|
|
18557
20321
|
);
|
|
18558
20322
|
lines.push(
|
|
@@ -18568,11 +20332,11 @@ function planLines(repo, targetDir) {
|
|
|
18568
20332
|
return lines;
|
|
18569
20333
|
}
|
|
18570
20334
|
async function executionSummary(repo, targetDir, native, platform2 = process.platform) {
|
|
18571
|
-
const repoDir =
|
|
20335
|
+
const repoDir = path37.join(targetDir, repo.name);
|
|
18572
20336
|
const trust = repo.origin === "custom" ? "custom" : "builtin";
|
|
18573
20337
|
const commands = [];
|
|
18574
20338
|
let revision;
|
|
18575
|
-
if (!
|
|
20339
|
+
if (!existsSync24(repoDir) || !hasGitRepo(repoDir)) {
|
|
18576
20340
|
commands.push(`git clone -- ${repo.url}`, S.planScriptsAfterClone);
|
|
18577
20341
|
} else {
|
|
18578
20342
|
revision = (await currentHead(repoDir))?.slice(0, 7);
|
|
@@ -18802,8 +20566,8 @@ ${planLines(repo, targetDir).map((line) => ` ${theme.dim(line)}`).join("\n")}`
|
|
|
18802
20566
|
}
|
|
18803
20567
|
|
|
18804
20568
|
// src/cmd/env.ts
|
|
18805
|
-
import { existsSync as
|
|
18806
|
-
import
|
|
20569
|
+
import { existsSync as existsSync26, readdirSync as readdirSync12 } from "fs";
|
|
20570
|
+
import path39 from "path";
|
|
18807
20571
|
|
|
18808
20572
|
// src/envintel/doctor.ts
|
|
18809
20573
|
function envDoctor(repoDir, envKeys) {
|
|
@@ -18852,8 +20616,8 @@ function envDoctor(repoDir, envKeys) {
|
|
|
18852
20616
|
|
|
18853
20617
|
// src/envintel/migrate.ts
|
|
18854
20618
|
import { randomBytes } from "crypto";
|
|
18855
|
-
import { existsSync as
|
|
18856
|
-
import
|
|
20619
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync11, readdirSync as readdirSync11, readFileSync as readFileSync19 } from "fs";
|
|
20620
|
+
import path38 from "path";
|
|
18857
20621
|
function timestamp2(d = /* @__PURE__ */ new Date()) {
|
|
18858
20622
|
const p = (n4) => String(n4).padStart(2, "0");
|
|
18859
20623
|
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
@@ -18867,13 +20631,13 @@ function addValue(spec, exampleEmpty) {
|
|
|
18867
20631
|
}
|
|
18868
20632
|
function planMigration(repoDir, targetSchema) {
|
|
18869
20633
|
const schema = targetSchema ?? deriveSchema(repoDir);
|
|
18870
|
-
const envPath =
|
|
18871
|
-
const env =
|
|
20634
|
+
const envPath = path38.join(repoDir, ".env");
|
|
20635
|
+
const env = existsSync25(envPath) ? parseEnv(readFileSync19(envPath, "utf8")) : null;
|
|
18872
20636
|
const present = new Set(env?.entries.map((e) => e.key) ?? []);
|
|
18873
|
-
const examplePath =
|
|
20637
|
+
const examplePath = path38.join(repoDir, ".env.example");
|
|
18874
20638
|
const exampleEmpty = /* @__PURE__ */ new Set();
|
|
18875
|
-
if (
|
|
18876
|
-
for (const e of parseEnv(
|
|
20639
|
+
if (existsSync25(examplePath)) {
|
|
20640
|
+
for (const e of parseEnv(readFileSync19(examplePath, "utf8")).entries) {
|
|
18877
20641
|
if (e.value === "") exampleEmpty.add(e.key);
|
|
18878
20642
|
}
|
|
18879
20643
|
}
|
|
@@ -18901,19 +20665,19 @@ function planMigration(repoDir, targetSchema) {
|
|
|
18901
20665
|
return { actions, removesRequireApproval: true };
|
|
18902
20666
|
}
|
|
18903
20667
|
function backupDirFor2(repoDir, ts) {
|
|
18904
|
-
return
|
|
20668
|
+
return path38.join(artifactsRoot(repoDir), "backups", `env-${ts}`);
|
|
18905
20669
|
}
|
|
18906
20670
|
function backupEnv(repoDir, content) {
|
|
18907
20671
|
const dir = backupDirFor2(repoDir, timestamp2());
|
|
18908
|
-
|
|
18909
|
-
const file =
|
|
20672
|
+
mkdirSync11(dir, { recursive: true });
|
|
20673
|
+
const file = path38.join(dir, ".env");
|
|
18910
20674
|
writeFileAtomic(file, content);
|
|
18911
20675
|
return dir;
|
|
18912
20676
|
}
|
|
18913
20677
|
function applyMigration(repoDir, plan, opts = {}) {
|
|
18914
|
-
const envPath =
|
|
18915
|
-
const exists =
|
|
18916
|
-
const current = exists ?
|
|
20678
|
+
const envPath = path38.join(repoDir, ".env");
|
|
20679
|
+
const exists = existsSync25(envPath);
|
|
20680
|
+
const current = exists ? readFileSync19(envPath, "utf8") : "";
|
|
18917
20681
|
const env = parseEnv(current);
|
|
18918
20682
|
const present = new Set(env.entries.map((e) => e.key));
|
|
18919
20683
|
const removes = plan.actions.filter((a3) => a3.kind === "remove-requested" && present.has(a3.key));
|
|
@@ -18943,16 +20707,16 @@ function applyMigration(repoDir, plan, opts = {}) {
|
|
|
18943
20707
|
};
|
|
18944
20708
|
}
|
|
18945
20709
|
function latestEnvBackup(repoDir) {
|
|
18946
|
-
const root =
|
|
18947
|
-
if (!
|
|
18948
|
-
const candidates =
|
|
20710
|
+
const root = path38.join(artifactsRoot(repoDir), "backups");
|
|
20711
|
+
if (!existsSync25(root)) return null;
|
|
20712
|
+
const candidates = readdirSync11(root, { withFileTypes: true }).filter((d) => d.isDirectory() && /^env-\d{8}-\d{6}$/.test(d.name)).map((d) => d.name).sort();
|
|
18949
20713
|
const last = candidates.at(-1);
|
|
18950
|
-
return last ?
|
|
20714
|
+
return last ? path38.join(root, last) : null;
|
|
18951
20715
|
}
|
|
18952
20716
|
function rollbackEnv(repoDir, backupDir) {
|
|
18953
|
-
const src =
|
|
18954
|
-
if (!
|
|
18955
|
-
writeFileAtomic(
|
|
20717
|
+
const src = path38.join(backupDir, ".env");
|
|
20718
|
+
if (!existsSync25(src)) return false;
|
|
20719
|
+
writeFileAtomic(path38.join(repoDir, ".env"), readFileSync19(src, "utf8"));
|
|
18956
20720
|
return true;
|
|
18957
20721
|
}
|
|
18958
20722
|
|
|
@@ -18960,9 +20724,9 @@ function rollbackEnv(repoDir, backupDir) {
|
|
|
18960
20724
|
var VERBS = /* @__PURE__ */ new Set(["inspect", "plan", "init", "validate", "migrate", "doctor"]);
|
|
18961
20725
|
function envCandidates(dir) {
|
|
18962
20726
|
try {
|
|
18963
|
-
return
|
|
20727
|
+
return readdirSync12(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).filter((name) => {
|
|
18964
20728
|
const r2 = resolveInside(dir, name);
|
|
18965
|
-
return r2 !== null && (
|
|
20729
|
+
return r2 !== null && (existsSync26(path39.join(r2, ".env")) || existsSync26(path39.join(r2, ".env.example")));
|
|
18966
20730
|
}).sort();
|
|
18967
20731
|
} catch {
|
|
18968
20732
|
return [];
|
|
@@ -19009,7 +20773,7 @@ async function runEnvCommand(cli, args) {
|
|
|
19009
20773
|
envKeys = catalogFor(targetDir).find((r2) => r2.name === name)?.envKeys;
|
|
19010
20774
|
} else {
|
|
19011
20775
|
const cwd = process.cwd();
|
|
19012
|
-
if (
|
|
20776
|
+
if (existsSync26(path39.join(cwd, ".env")) || existsSync26(path39.join(cwd, ".env.example"))) {
|
|
19013
20777
|
repoDir = cwd;
|
|
19014
20778
|
}
|
|
19015
20779
|
}
|
|
@@ -19019,7 +20783,7 @@ async function runEnvCommand(cli, args) {
|
|
|
19019
20783
|
if (found.length) note(found.join("\n"), S.envTitle);
|
|
19020
20784
|
return 2;
|
|
19021
20785
|
}
|
|
19022
|
-
const repo = name ??
|
|
20786
|
+
const repo = name ?? path39.basename(repoDir);
|
|
19023
20787
|
if (verb === "inspect") {
|
|
19024
20788
|
const res2 = inspectEnv(repoDir, envKeys);
|
|
19025
20789
|
if (json) {
|
|
@@ -19061,13 +20825,13 @@ async function runEnvCommand(cli, args) {
|
|
|
19061
20825
|
note(lines.join("\n"), `${S.envDoctorTitle} \u2014 ${repo}`);
|
|
19062
20826
|
return res2.checks.some((c4) => c4.result === "fail") ? 1 : 0;
|
|
19063
20827
|
}
|
|
19064
|
-
if (!
|
|
20828
|
+
if (!existsSync26(repoDir)) {
|
|
19065
20829
|
log.error(`${repo}: ${S.statusNotInstalled}`);
|
|
19066
20830
|
return 2;
|
|
19067
20831
|
}
|
|
19068
20832
|
const schema = deriveSchema(repoDir, envKeys);
|
|
19069
20833
|
const secretKeys = new Set(schema.vars.filter((v) => v.secret).map((v) => v.key));
|
|
19070
|
-
const envPath =
|
|
20834
|
+
const envPath = path39.join(repoDir, ".env");
|
|
19071
20835
|
if (verb === "plan") {
|
|
19072
20836
|
const plan2 = planMigration(repoDir, schema);
|
|
19073
20837
|
const shown = maskedActions(plan2, secretKeys);
|
|
@@ -19090,7 +20854,7 @@ async function runEnvCommand(cli, args) {
|
|
|
19090
20854
|
return 0;
|
|
19091
20855
|
}
|
|
19092
20856
|
if (verb === "init") {
|
|
19093
|
-
if (
|
|
20857
|
+
if (existsSync26(envPath)) {
|
|
19094
20858
|
if (json) console.log(JSON.stringify({ command: "env init", repo, created: false }));
|
|
19095
20859
|
else log.info(S.envInitExists);
|
|
19096
20860
|
return 0;
|
|
@@ -19163,6 +20927,165 @@ async function runEnvCommand(cli, args) {
|
|
|
19163
20927
|
return 0;
|
|
19164
20928
|
}
|
|
19165
20929
|
|
|
20930
|
+
// src/cmd/hosts.ts
|
|
20931
|
+
import path40 from "path";
|
|
20932
|
+
import { existsSync as existsSync27, readdirSync as readdirSync13 } from "fs";
|
|
20933
|
+
|
|
20934
|
+
// src/mcp/handshake.ts
|
|
20935
|
+
import { spawn as spawn2 } from "child_process";
|
|
20936
|
+
import { createInterface as createInterface2 } from "readline";
|
|
20937
|
+
var PROTOCOL_VERSION = "2024-11-05";
|
|
20938
|
+
var STEP_MS = 8e3;
|
|
20939
|
+
var MAX_LINE = 1 << 20;
|
|
20940
|
+
var MAX_LINES = 64;
|
|
20941
|
+
var err = (code, detail) => ({
|
|
20942
|
+
ok: false,
|
|
20943
|
+
levels: [],
|
|
20944
|
+
tools: null,
|
|
20945
|
+
serverInfo: null,
|
|
20946
|
+
protocolVersion: null,
|
|
20947
|
+
error: { code, detail: detail.slice(0, 300) }
|
|
20948
|
+
});
|
|
20949
|
+
function probeMcpServer(argv) {
|
|
20950
|
+
return new Promise((resolve) => {
|
|
20951
|
+
if (!argv.length || !argv[0]) {
|
|
20952
|
+
resolve(err("argv-empty", "comando MCP vazio"));
|
|
20953
|
+
return;
|
|
20954
|
+
}
|
|
20955
|
+
let child;
|
|
20956
|
+
try {
|
|
20957
|
+
child = spawn2(argv[0], argv.slice(1), { stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
20958
|
+
} catch (e) {
|
|
20959
|
+
resolve(err("spawn-failed", e instanceof Error ? e.message : String(e)));
|
|
20960
|
+
return;
|
|
20961
|
+
}
|
|
20962
|
+
const levels = [];
|
|
20963
|
+
let tools = null;
|
|
20964
|
+
let serverInfo = null;
|
|
20965
|
+
let protocolVersion = null;
|
|
20966
|
+
let stdoutGarbage = false;
|
|
20967
|
+
let done = false;
|
|
20968
|
+
const finish = (r2) => {
|
|
20969
|
+
if (done) return;
|
|
20970
|
+
done = true;
|
|
20971
|
+
clearTimeout(timer);
|
|
20972
|
+
child.kill("SIGTERM");
|
|
20973
|
+
resolve(r2);
|
|
20974
|
+
};
|
|
20975
|
+
const timer = setTimeout(() => {
|
|
20976
|
+
finish({
|
|
20977
|
+
...err("timeout", `sem resposta em ${STEP_MS}ms (n\xEDveis: ${levels.join(",") || "nenhum"})`),
|
|
20978
|
+
levels
|
|
20979
|
+
});
|
|
20980
|
+
}, STEP_MS);
|
|
20981
|
+
const pending = /* @__PURE__ */ new Map();
|
|
20982
|
+
const rl = createInterface2({ input: child.stdout, crlfDelay: Infinity });
|
|
20983
|
+
let lines = 0;
|
|
20984
|
+
rl.on("line", (line) => {
|
|
20985
|
+
if (line.length > MAX_LINE || ++lines > MAX_LINES) {
|
|
20986
|
+
stdoutGarbage = true;
|
|
20987
|
+
finish({
|
|
20988
|
+
...err("stdout-oversized", "sa\xEDda stdout excedeu limites \u2014 n\xE3o \xE9 um MCP stdio limpo"),
|
|
20989
|
+
levels
|
|
20990
|
+
});
|
|
20991
|
+
return;
|
|
20992
|
+
}
|
|
20993
|
+
let msg;
|
|
20994
|
+
try {
|
|
20995
|
+
msg = JSON.parse(line);
|
|
20996
|
+
} catch {
|
|
20997
|
+
stdoutGarbage = true;
|
|
20998
|
+
finish({
|
|
20999
|
+
...err("stdout-non-json", "server escreveu n\xE3o-JSON no stdout (logs indevidos?)"),
|
|
21000
|
+
levels
|
|
21001
|
+
});
|
|
21002
|
+
return;
|
|
21003
|
+
}
|
|
21004
|
+
const id = msg.id;
|
|
21005
|
+
if (typeof id === "number" && pending.has(id)) {
|
|
21006
|
+
pending.get(id)(msg);
|
|
21007
|
+
pending.delete(id);
|
|
21008
|
+
}
|
|
21009
|
+
});
|
|
21010
|
+
child.on("error", (e) => finish({ ...err("spawn-failed", e.message), levels }));
|
|
21011
|
+
child.on("exit", (code) => {
|
|
21012
|
+
if (!done)
|
|
21013
|
+
finish({ ...err("server-exit", `exit ${code} antes do handshake completar`), levels });
|
|
21014
|
+
});
|
|
21015
|
+
const stderr = [];
|
|
21016
|
+
child.stderr?.on("data", (d) => {
|
|
21017
|
+
if (stderr.join("").length < 4096) stderr.push(d.toString("utf8"));
|
|
21018
|
+
});
|
|
21019
|
+
const request = (method, params, id) => new Promise((ok, bad) => {
|
|
21020
|
+
pending.set(id, ok);
|
|
21021
|
+
const stepTimer = setTimeout(() => {
|
|
21022
|
+
pending.delete(id);
|
|
21023
|
+
bad(new Error(`${method}: timeout por etapa`));
|
|
21024
|
+
}, STEP_MS);
|
|
21025
|
+
setTimeout(() => clearTimeout(stepTimer), STEP_MS + 50);
|
|
21026
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
21027
|
+
`);
|
|
21028
|
+
});
|
|
21029
|
+
const notify = (method) => child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method })}
|
|
21030
|
+
`);
|
|
21031
|
+
void (async () => {
|
|
21032
|
+
try {
|
|
21033
|
+
const init = await request(
|
|
21034
|
+
"initialize",
|
|
21035
|
+
{
|
|
21036
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
21037
|
+
capabilities: {},
|
|
21038
|
+
clientInfo: { name: "forge-cli", version: "0.1.0" }
|
|
21039
|
+
},
|
|
21040
|
+
1
|
|
21041
|
+
);
|
|
21042
|
+
const res = init.result ?? {};
|
|
21043
|
+
if (init.error || typeof res !== "object" || !res.protocolVersion) {
|
|
21044
|
+
finish(
|
|
21045
|
+
err(
|
|
21046
|
+
"initialize-invalid",
|
|
21047
|
+
`resposta initialize inv\xE1lida: ${JSON.stringify(init).slice(0, 200)}`
|
|
21048
|
+
)
|
|
21049
|
+
);
|
|
21050
|
+
return;
|
|
21051
|
+
}
|
|
21052
|
+
protocolVersion = String(res.protocolVersion);
|
|
21053
|
+
const si = res.serverInfo;
|
|
21054
|
+
serverInfo = si ? { name: String(si.name ?? ""), version: String(si.version ?? "") } : null;
|
|
21055
|
+
levels.push("MCP_INITIALIZED");
|
|
21056
|
+
notify("notifications/initialized");
|
|
21057
|
+
const list = await request("tools/list", {}, 2);
|
|
21058
|
+
const tl = (list.result ?? {}).tools;
|
|
21059
|
+
if (list.error || !Array.isArray(tl)) {
|
|
21060
|
+
finish({
|
|
21061
|
+
...err("tools-list-invalid", "tools/list ausente ou malformado"),
|
|
21062
|
+
levels,
|
|
21063
|
+
protocolVersion,
|
|
21064
|
+
serverInfo
|
|
21065
|
+
});
|
|
21066
|
+
return;
|
|
21067
|
+
}
|
|
21068
|
+
for (const t2 of tl) {
|
|
21069
|
+
if (typeof t2.name !== "string") {
|
|
21070
|
+
finish({
|
|
21071
|
+
...err("tool-schema-invalid", "tool sem name:string"),
|
|
21072
|
+
levels,
|
|
21073
|
+
protocolVersion,
|
|
21074
|
+
serverInfo
|
|
21075
|
+
});
|
|
21076
|
+
return;
|
|
21077
|
+
}
|
|
21078
|
+
}
|
|
21079
|
+
tools = tl.length;
|
|
21080
|
+
levels.push("TOOLS_DISCOVERED");
|
|
21081
|
+
finish({ ok: !stdoutGarbage, levels, tools, serverInfo, protocolVersion, error: null });
|
|
21082
|
+
} catch (e) {
|
|
21083
|
+
finish({ ...err("handshake-failed", e instanceof Error ? e.message : String(e)), levels });
|
|
21084
|
+
}
|
|
21085
|
+
})();
|
|
21086
|
+
});
|
|
21087
|
+
}
|
|
21088
|
+
|
|
19166
21089
|
// src/cmd/hosts.ts
|
|
19167
21090
|
var STATE_LABEL = {
|
|
19168
21091
|
absent: S.hostAbsent,
|
|
@@ -19189,9 +21112,26 @@ function planLines3(plan) {
|
|
|
19189
21112
|
if (plan.copyable_command) lines.push(`${S.hostCopyCommand} ${plan.copyable_command}`);
|
|
19190
21113
|
return lines.join("\n");
|
|
19191
21114
|
}
|
|
19192
|
-
function forgeDescriptor(targetDir, name) {
|
|
21115
|
+
function forgeDescriptor(targetDir, name, home = cliHome()) {
|
|
19193
21116
|
const dir = resolveInside(targetDir, name);
|
|
19194
21117
|
const manifest = dir ? readForgeManifest(dir) : null;
|
|
21118
|
+
if (manifest?.mcpCommand?.length) {
|
|
21119
|
+
return { forge_id: manifest.forgeId ?? name, mcp_command: manifest.mcpCommand };
|
|
21120
|
+
}
|
|
21121
|
+
const receipt = loadForgeState(home).forges[name];
|
|
21122
|
+
const entry2 = registryEntry(name);
|
|
21123
|
+
if (receipt?.bin && entry2?.mcp?.length) {
|
|
21124
|
+
const binDir = path40.dirname(receipt.bin);
|
|
21125
|
+
const mcp_command = entry2.mcp.map((tok) => {
|
|
21126
|
+
if (tok === "{bin}") return receipt.bin;
|
|
21127
|
+
if (tok.startsWith("{binDir}/")) {
|
|
21128
|
+
const script = tok.slice("{binDir}/".length);
|
|
21129
|
+
return path40.join(binDir, process.platform === "win32" ? `${script}.exe` : script);
|
|
21130
|
+
}
|
|
21131
|
+
return tok;
|
|
21132
|
+
});
|
|
21133
|
+
return { forge_id: name, mcp_command };
|
|
21134
|
+
}
|
|
19195
21135
|
return { forge_id: manifest?.forgeId ?? name, mcp_command: manifest?.mcpCommand };
|
|
19196
21136
|
}
|
|
19197
21137
|
async function runHostsCommand(cli, args, home) {
|
|
@@ -19214,7 +21154,7 @@ async function runHostsCommand(cli, args, home) {
|
|
|
19214
21154
|
return 2;
|
|
19215
21155
|
}
|
|
19216
21156
|
const targetDir = resolveTargetDir(cli.dir);
|
|
19217
|
-
const forge = forgeDescriptor(targetDir, forgeName);
|
|
21157
|
+
const forge = forgeDescriptor(targetDir, forgeName, home);
|
|
19218
21158
|
if (verb === "plan" || verb === "register") {
|
|
19219
21159
|
const plan = await adapter.planIntegration(forge, targetDir, hostHome(home));
|
|
19220
21160
|
if (!plan) {
|
|
@@ -19287,11 +21227,79 @@ async function runHostsCommand(cli, args, home) {
|
|
|
19287
21227
|
log.error(`${S.errUnknownCommand}: ${args.join(" ")}`);
|
|
19288
21228
|
return 2;
|
|
19289
21229
|
}
|
|
21230
|
+
async function runMcpCommand(cli, args, home = cliHome()) {
|
|
21231
|
+
const json = cli.jsonReport;
|
|
21232
|
+
const verify = cli.verify;
|
|
21233
|
+
const names = args.filter((a3) => !a3.startsWith("-"));
|
|
21234
|
+
const targetDir = resolveTargetDir(cli.dir);
|
|
21235
|
+
const candidates = names.length > 0 ? names : [
|
|
21236
|
+
.../* @__PURE__ */ new Set([
|
|
21237
|
+
...Object.keys(loadForgeState(home).forges),
|
|
21238
|
+
...existsSync27(targetDir) ? readdirSync13(targetDir, { withFileTypes: true }).filter((d) => d.isDirectory() && isSafeRepoName(d.name)).map((d) => d.name) : []
|
|
21239
|
+
])
|
|
21240
|
+
].sort();
|
|
21241
|
+
if (candidates.length === 0) {
|
|
21242
|
+
if (json) process.stdout.write(`${JSON.stringify({ mcp: [] })}
|
|
21243
|
+
`);
|
|
21244
|
+
else log.warn("nenhuma Forja instalada/encontrada para MCP");
|
|
21245
|
+
return 1;
|
|
21246
|
+
}
|
|
21247
|
+
const results = [];
|
|
21248
|
+
for (const name of candidates) {
|
|
21249
|
+
if (!isSafeRepoName(name)) {
|
|
21250
|
+
results.push({ forge: name, ok: false, detail: "nome inseguro" });
|
|
21251
|
+
continue;
|
|
21252
|
+
}
|
|
21253
|
+
const forge = forgeDescriptor(targetDir, name, home);
|
|
21254
|
+
const argv = forge.mcp_command;
|
|
21255
|
+
if (!argv?.length) {
|
|
21256
|
+
results.push({ forge: name, ok: false, detail: "sem mcp_command" });
|
|
21257
|
+
continue;
|
|
21258
|
+
}
|
|
21259
|
+
const binPresent = path40.isAbsolute(argv[0]) ? existsSync27(argv[0]) : true;
|
|
21260
|
+
const item = {
|
|
21261
|
+
forge: name,
|
|
21262
|
+
forge_id: forge.forge_id,
|
|
21263
|
+
mcp_command: argv,
|
|
21264
|
+
binPresent,
|
|
21265
|
+
ok: binPresent
|
|
21266
|
+
};
|
|
21267
|
+
if (!binPresent) item.detail = "bin\xE1rio MCP ausente na venv";
|
|
21268
|
+
if (verify && binPresent && path40.isAbsolute(argv[0])) {
|
|
21269
|
+
const probe = await probeMcpServer(argv);
|
|
21270
|
+
item.probe = {
|
|
21271
|
+
ok: probe.ok,
|
|
21272
|
+
levels: probe.levels,
|
|
21273
|
+
tools: probe.tools,
|
|
21274
|
+
server: probe.serverInfo,
|
|
21275
|
+
protocol: probe.protocolVersion,
|
|
21276
|
+
error: probe.error
|
|
21277
|
+
};
|
|
21278
|
+
if (!probe.ok) {
|
|
21279
|
+
item.ok = false;
|
|
21280
|
+
item.detail = probe.error ? `${probe.error.code}: ${probe.error.detail}` : "probe falhou";
|
|
21281
|
+
}
|
|
21282
|
+
}
|
|
21283
|
+
results.push(item);
|
|
21284
|
+
}
|
|
21285
|
+
if (json) {
|
|
21286
|
+
process.stdout.write(`${JSON.stringify({ mcp: results }, null, 2)}
|
|
21287
|
+
`);
|
|
21288
|
+
} else {
|
|
21289
|
+
for (const r2 of results) {
|
|
21290
|
+
const rec = r2;
|
|
21291
|
+
if (rec.ok) log.info(`${icons.ok} ${rec.forge}: ${rec.mcp_command?.join(" ")}`);
|
|
21292
|
+
else log.warn(`${rec.forge}: ${rec.detail ?? "indispon\xEDvel"}`);
|
|
21293
|
+
}
|
|
21294
|
+
}
|
|
21295
|
+
const okCount = results.filter((r2) => r2.ok).length;
|
|
21296
|
+
return okCount === results.length ? 0 : okCount > 0 ? 0 : 1;
|
|
21297
|
+
}
|
|
19290
21298
|
|
|
19291
21299
|
// src/discovery.ts
|
|
19292
|
-
import { existsSync as
|
|
19293
|
-
import
|
|
19294
|
-
import
|
|
21300
|
+
import { existsSync as existsSync28, readdirSync as readdirSync14, readFileSync as readFileSync20, statSync as statSync5 } from "fs";
|
|
21301
|
+
import os6 from "os";
|
|
21302
|
+
import path41 from "path";
|
|
19295
21303
|
var DEFAULT_LIMITS = {
|
|
19296
21304
|
maxDepth: 3,
|
|
19297
21305
|
maxEntriesPerDir: 500,
|
|
@@ -19384,7 +21392,7 @@ function readBoundedText(file) {
|
|
|
19384
21392
|
try {
|
|
19385
21393
|
const st = statSync5(file);
|
|
19386
21394
|
if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
|
|
19387
|
-
const text2 =
|
|
21395
|
+
const text2 = readFileSync20(file, "utf8");
|
|
19388
21396
|
if (text2.includes(String.fromCharCode(0))) return null;
|
|
19389
21397
|
return text2;
|
|
19390
21398
|
} catch {
|
|
@@ -19440,7 +21448,7 @@ function walkWorkspace(root, limits, budgetLeft) {
|
|
|
19440
21448
|
const { dir, rel: rel2, depth } = current;
|
|
19441
21449
|
let entries;
|
|
19442
21450
|
try {
|
|
19443
|
-
entries =
|
|
21451
|
+
entries = readdirSync14(dir, { withFileTypes: true }).sort(
|
|
19444
21452
|
(a3, b) => a3.name < b.name ? -1 : a3.name > b.name ? 1 : 0
|
|
19445
21453
|
);
|
|
19446
21454
|
} catch {
|
|
@@ -19456,7 +21464,7 @@ function walkWorkspace(root, limits, budgetLeft) {
|
|
|
19456
21464
|
return { markers, envFiles, topDirs, signals: signals2, truncated };
|
|
19457
21465
|
}
|
|
19458
21466
|
const lower = entry2.name.toLowerCase();
|
|
19459
|
-
const full =
|
|
21467
|
+
const full = path41.join(dir, entry2.name);
|
|
19460
21468
|
const relPath = rel2 ? `${rel2}/${entry2.name}` : entry2.name;
|
|
19461
21469
|
if (entry2.isDirectory()) {
|
|
19462
21470
|
if (skip.has(lower) || isLinkOrJunction(full)) continue;
|
|
@@ -19579,10 +21587,10 @@ function registryState(entry2) {
|
|
|
19579
21587
|
function discoverForge(name, dir, state) {
|
|
19580
21588
|
const sources = [];
|
|
19581
21589
|
const git = !!dir && hasGitRepo(dir);
|
|
19582
|
-
const manifest = dir &&
|
|
21590
|
+
const manifest = dir && existsSync28(dir) ? readForgeManifest(dir) : null;
|
|
19583
21591
|
const entry2 = state?.repos[name];
|
|
19584
21592
|
if (entry2 && entry2.phase !== "not-installed") {
|
|
19585
|
-
const dirExists = !!dir &&
|
|
21593
|
+
const dirExists = !!dir && existsSync28(dir);
|
|
19586
21594
|
sources.push({
|
|
19587
21595
|
origin: "manifest",
|
|
19588
21596
|
// registro diz instalada mas o checkout sumiu → evidência falhando
|
|
@@ -19625,7 +21633,7 @@ async function discoverEnvironment(targetDir, opts) {
|
|
|
19625
21633
|
const start = now();
|
|
19626
21634
|
const limits = { ...DEFAULT_LIMITS, ...opts?.limits };
|
|
19627
21635
|
const budgetLeft = () => limits.timeoutMs - (now() - start);
|
|
19628
|
-
const root =
|
|
21636
|
+
const root = path41.resolve(targetDir);
|
|
19629
21637
|
const walk = walkWorkspace(root, limits, budgetLeft);
|
|
19630
21638
|
const tools = await probeTools(process.platform, budgetLeft);
|
|
19631
21639
|
const hosts = await probeHosts(budgetLeft);
|
|
@@ -19642,7 +21650,7 @@ async function discoverEnvironment(targetDir, opts) {
|
|
|
19642
21650
|
const dir = resolveInside(root, dirName);
|
|
19643
21651
|
if (!dir) continue;
|
|
19644
21652
|
const git = hasGitRepo(dir);
|
|
19645
|
-
const manifest =
|
|
21653
|
+
const manifest = existsSync28(path41.join(dir, "forge.json"));
|
|
19646
21654
|
if (!git && !manifest) continue;
|
|
19647
21655
|
presence.push({ name: dirName, path: dirName, git, manifest });
|
|
19648
21656
|
if (!catalogNames.has(dirName) && manifest) {
|
|
@@ -19659,8 +21667,8 @@ async function discoverEnvironment(targetDir, opts) {
|
|
|
19659
21667
|
generated_at: new Date(now()).toISOString(),
|
|
19660
21668
|
os: {
|
|
19661
21669
|
platform: process.platform,
|
|
19662
|
-
release:
|
|
19663
|
-
arch:
|
|
21670
|
+
release: os6.release(),
|
|
21671
|
+
arch: os6.arch(),
|
|
19664
21672
|
shell: process.env.SHELL ?? process.env.ComSpec ?? null
|
|
19665
21673
|
},
|
|
19666
21674
|
tools,
|
|
@@ -20289,11 +22297,11 @@ async function answerRepair(targetDir, repos, opts = {}) {
|
|
|
20289
22297
|
}
|
|
20290
22298
|
|
|
20291
22299
|
// src/intel/context.ts
|
|
20292
|
-
import { existsSync as
|
|
20293
|
-
import
|
|
22300
|
+
import { existsSync as existsSync29, readdirSync as readdirSync15, readFileSync as readFileSync21, statSync as statSync6 } from "fs";
|
|
22301
|
+
import path42 from "path";
|
|
20294
22302
|
|
|
20295
22303
|
// src/planner.ts
|
|
20296
|
-
import { createHash as
|
|
22304
|
+
import { createHash as createHash4 } from "crypto";
|
|
20297
22305
|
var PLAN_TTL_MS = 15 * 60 * 1e3;
|
|
20298
22306
|
var SELECTABLE_STEPS = [
|
|
20299
22307
|
"clone",
|
|
@@ -20319,7 +22327,7 @@ function canonicalJson(doc) {
|
|
|
20319
22327
|
return JSON.stringify(canonicalize(doc));
|
|
20320
22328
|
}
|
|
20321
22329
|
function planHash(plan) {
|
|
20322
|
-
return
|
|
22330
|
+
return createHash4("sha256").update(canonicalJson(plan), "utf8").digest("hex");
|
|
20323
22331
|
}
|
|
20324
22332
|
function buildPlan(input) {
|
|
20325
22333
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
@@ -20388,10 +22396,10 @@ function safeScalar(value) {
|
|
|
20388
22396
|
return redactSecrets(value) === value ? value : null;
|
|
20389
22397
|
}
|
|
20390
22398
|
function safeConfig(targetDir) {
|
|
20391
|
-
const file =
|
|
22399
|
+
const file = path42.join(targetDir, "forge-cli.config.json");
|
|
20392
22400
|
let raw;
|
|
20393
22401
|
try {
|
|
20394
|
-
raw = JSON.parse(
|
|
22402
|
+
raw = JSON.parse(readFileSync21(file, "utf8"));
|
|
20395
22403
|
} catch {
|
|
20396
22404
|
return {};
|
|
20397
22405
|
}
|
|
@@ -20405,10 +22413,10 @@ function safeConfig(targetDir) {
|
|
|
20405
22413
|
return out;
|
|
20406
22414
|
}
|
|
20407
22415
|
function readManifestObject(repoDir) {
|
|
20408
|
-
const file =
|
|
22416
|
+
const file = path42.join(repoDir, "forge.json");
|
|
20409
22417
|
try {
|
|
20410
|
-
if (!
|
|
20411
|
-
const raw = JSON.parse(
|
|
22418
|
+
if (!existsSync29(file) || statSync6(file).size > MAX_MANIFEST_BYTES) return null;
|
|
22419
|
+
const raw = JSON.parse(readFileSync21(file, "utf8"));
|
|
20412
22420
|
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
20413
22421
|
} catch {
|
|
20414
22422
|
return null;
|
|
@@ -20441,10 +22449,10 @@ function manifestCapabilities(repoDir, repoName, out) {
|
|
|
20441
22449
|
}
|
|
20442
22450
|
}
|
|
20443
22451
|
function activePlans(targetDir, nowIso) {
|
|
20444
|
-
const dir =
|
|
22452
|
+
const dir = path42.join(artifactsRoot(targetDir), "plans");
|
|
20445
22453
|
let names;
|
|
20446
22454
|
try {
|
|
20447
|
-
names =
|
|
22455
|
+
names = readdirSync15(dir).filter((n4) => n4.endsWith(".json")).sort();
|
|
20448
22456
|
} catch {
|
|
20449
22457
|
return [];
|
|
20450
22458
|
}
|
|
@@ -20452,7 +22460,7 @@ function activePlans(targetDir, nowIso) {
|
|
|
20452
22460
|
const out = [];
|
|
20453
22461
|
for (const name of names.slice(0, MAX_PLAN_FILES)) {
|
|
20454
22462
|
try {
|
|
20455
|
-
const doc = JSON.parse(
|
|
22463
|
+
const doc = JSON.parse(readFileSync21(path42.join(dir, name), "utf8"));
|
|
20456
22464
|
if (!doc || typeof doc !== "object" || Array.isArray(doc)) continue;
|
|
20457
22465
|
const plan = doc;
|
|
20458
22466
|
if (typeof plan.plan_id !== "string" || !plan.plan_id) continue;
|
|
@@ -20523,7 +22531,7 @@ async function buildForgeContext(targetDir, repos, opts = {}) {
|
|
|
20523
22531
|
const byName = new Map(report.forges.map((f) => [f.name, f]));
|
|
20524
22532
|
const capabilities = {};
|
|
20525
22533
|
const forjas = [...repos].sort((a3, b) => cmp2(a3.name, b.name)).map((repo) => {
|
|
20526
|
-
const dir =
|
|
22534
|
+
const dir = path42.join(targetDir, repo.name);
|
|
20527
22535
|
manifestCapabilities(dir, repo.name, capabilities);
|
|
20528
22536
|
const discovered = byName.get(repo.name);
|
|
20529
22537
|
return {
|
|
@@ -20577,8 +22585,8 @@ async function buildForgeContext(targetDir, repos, opts = {}) {
|
|
|
20577
22585
|
}
|
|
20578
22586
|
|
|
20579
22587
|
// src/intel/predict.ts
|
|
20580
|
-
import { existsSync as
|
|
20581
|
-
import
|
|
22588
|
+
import { existsSync as existsSync30, readFileSync as readFileSync22, statSync as statSync7 } from "fs";
|
|
22589
|
+
import path43 from "path";
|
|
20582
22590
|
var MAX_MANIFEST_BYTES2 = 64 * 1024;
|
|
20583
22591
|
var SEMVERISH = /^v?\d+\.\d+\.\d+/;
|
|
20584
22592
|
var ev2 = (source, fact) => ({
|
|
@@ -20588,10 +22596,10 @@ var ev2 = (source, fact) => ({
|
|
|
20588
22596
|
var cmp3 = (a3, b) => a3 < b ? -1 : a3 > b ? 1 : 0;
|
|
20589
22597
|
var mk2 = (id, kind, subject, certainty, version, evidence, explanation) => ({ id, kind, subject, certainty, version, evidence, explanation });
|
|
20590
22598
|
function readManifest(repoDir) {
|
|
20591
|
-
const file =
|
|
22599
|
+
const file = path43.join(repoDir, "forge.json");
|
|
20592
22600
|
try {
|
|
20593
|
-
if (!
|
|
20594
|
-
const raw = JSON.parse(
|
|
22601
|
+
if (!existsSync30(file) || statSync7(file).size > MAX_MANIFEST_BYTES2) return null;
|
|
22602
|
+
const raw = JSON.parse(readFileSync22(file, "utf8"));
|
|
20595
22603
|
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
20596
22604
|
} catch {
|
|
20597
22605
|
return null;
|
|
@@ -20640,7 +22648,7 @@ function declaredRefs(targetDir, repoName, m3) {
|
|
|
20640
22648
|
}
|
|
20641
22649
|
try {
|
|
20642
22650
|
const lock = JSON.parse(
|
|
20643
|
-
|
|
22651
|
+
readFileSync22(path43.join(artifactsRoot(targetDir), "env.lock.json"), "utf8")
|
|
20644
22652
|
);
|
|
20645
22653
|
const head = lock?.forges?.[repoName]?.head;
|
|
20646
22654
|
if (typeof head === "string" && SEMVERISH.test(head)) {
|
|
@@ -20907,7 +22915,7 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
|
|
|
20907
22915
|
const allClaims = [];
|
|
20908
22916
|
for (const repo of sorted) {
|
|
20909
22917
|
const dir = resolveInside(targetDir, repo.name);
|
|
20910
|
-
if (!dir || !
|
|
22918
|
+
if (!dir || !existsSync30(dir)) continue;
|
|
20911
22919
|
const m3 = readManifest(dir);
|
|
20912
22920
|
manifests.set(repo.name, m3);
|
|
20913
22921
|
if (m3) out.push(...schemaChange(repo.name, m3));
|
|
@@ -20919,7 +22927,7 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
|
|
|
20919
22927
|
out.push(...envVarFindings("<workspace>", targetDir));
|
|
20920
22928
|
for (const repo of sorted) {
|
|
20921
22929
|
const dir = resolveInside(targetDir, repo.name);
|
|
20922
|
-
if (!dir || !
|
|
22930
|
+
if (!dir || !existsSync30(dir)) continue;
|
|
20923
22931
|
out.push(...envVarFindings(repo.name, dir, repo.envKeys));
|
|
20924
22932
|
}
|
|
20925
22933
|
out.push(...installConflict(allClaims));
|
|
@@ -20927,13 +22935,13 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
|
|
|
20927
22935
|
}
|
|
20928
22936
|
|
|
20929
22937
|
// src/ai/bridge.ts
|
|
20930
|
-
import { mkdirSync as
|
|
20931
|
-
import
|
|
22938
|
+
import { mkdirSync as mkdirSync13 } from "fs";
|
|
22939
|
+
import path45 from "path";
|
|
20932
22940
|
|
|
20933
22941
|
// src/ai/cache.ts
|
|
20934
|
-
import { createHash as
|
|
20935
|
-
import { existsSync as
|
|
20936
|
-
import
|
|
22942
|
+
import { createHash as createHash5 } from "crypto";
|
|
22943
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync12, readFileSync as readFileSync23, readdirSync as readdirSync16, rmSync as rmSync6 } from "fs";
|
|
22944
|
+
import path44 from "path";
|
|
20937
22945
|
|
|
20938
22946
|
// src/ai/types.ts
|
|
20939
22947
|
var AI_PROPOSAL_SCHEMA = "forge/AiProposal/v1";
|
|
@@ -21016,7 +23024,7 @@ function validateProposal(proposal, catalog) {
|
|
|
21016
23024
|
var AI_CACHE_MAX_ENTRIES = 32;
|
|
21017
23025
|
var KEY_RE = /^[a-f0-9]{64}$/;
|
|
21018
23026
|
function aiCacheDir(rootDir) {
|
|
21019
|
-
return
|
|
23027
|
+
return path44.join(artifactsRoot(rootDir), "ai", "cache");
|
|
21020
23028
|
}
|
|
21021
23029
|
function canonical(value) {
|
|
21022
23030
|
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
@@ -21027,18 +23035,18 @@ function canonical(value) {
|
|
|
21027
23035
|
return JSON.stringify(value) ?? "null";
|
|
21028
23036
|
}
|
|
21029
23037
|
function aiCacheKey(payload) {
|
|
21030
|
-
return
|
|
23038
|
+
return createHash5("sha256").update(canonical(payload)).digest("hex");
|
|
21031
23039
|
}
|
|
21032
23040
|
function entryFile(rootDir, key) {
|
|
21033
23041
|
if (!KEY_RE.test(key)) return null;
|
|
21034
|
-
return
|
|
23042
|
+
return path44.join(aiCacheDir(rootDir), `${key}.json`);
|
|
21035
23043
|
}
|
|
21036
23044
|
function aiCacheGet(rootDir, key, opts) {
|
|
21037
23045
|
if (opts.ttlMs <= 0) return null;
|
|
21038
23046
|
const file = entryFile(rootDir, key);
|
|
21039
|
-
if (!file || !
|
|
23047
|
+
if (!file || !existsSync31(file)) return null;
|
|
21040
23048
|
try {
|
|
21041
|
-
const entry2 = JSON.parse(
|
|
23049
|
+
const entry2 = JSON.parse(readFileSync23(file, "utf8"));
|
|
21042
23050
|
const now = opts.now ?? Date.now();
|
|
21043
23051
|
if (typeof entry2?.at !== "number" || now - entry2.at > opts.ttlMs || now < entry2.at) {
|
|
21044
23052
|
return null;
|
|
@@ -21052,7 +23060,7 @@ function aiCacheSet(rootDir, key, proposal, opts = {}) {
|
|
|
21052
23060
|
const file = entryFile(rootDir, key);
|
|
21053
23061
|
if (!file) return;
|
|
21054
23062
|
const dir = aiCacheDir(rootDir);
|
|
21055
|
-
|
|
23063
|
+
mkdirSync12(dir, { recursive: true });
|
|
21056
23064
|
const entry2 = { at: opts.now ?? Date.now(), proposal };
|
|
21057
23065
|
writeFileAtomic(file, JSON.stringify(entry2));
|
|
21058
23066
|
evictOldest(dir, opts.maxEntries ?? AI_CACHE_MAX_ENTRIES);
|
|
@@ -21061,19 +23069,19 @@ function evictOldest(dir, maxEntries) {
|
|
|
21061
23069
|
if (maxEntries < 1) return;
|
|
21062
23070
|
let files;
|
|
21063
23071
|
try {
|
|
21064
|
-
files =
|
|
23072
|
+
files = readdirSync16(dir).filter((f) => KEY_RE.test(path44.basename(f, ".json")));
|
|
21065
23073
|
} catch {
|
|
21066
23074
|
return;
|
|
21067
23075
|
}
|
|
21068
23076
|
if (files.length <= maxEntries) return;
|
|
21069
|
-
const aged = files.map((file) => ({ file, at: entryAge(
|
|
23077
|
+
const aged = files.map((file) => ({ file, at: entryAge(path44.join(dir, file)) })).sort((a3, b) => a3.at - b.at || a3.file.localeCompare(b.file));
|
|
21070
23078
|
for (const { file } of aged.slice(0, aged.length - maxEntries)) {
|
|
21071
|
-
|
|
23079
|
+
rmSync6(path44.join(dir, file), { force: true });
|
|
21072
23080
|
}
|
|
21073
23081
|
}
|
|
21074
23082
|
function entryAge(file) {
|
|
21075
23083
|
try {
|
|
21076
|
-
const entry2 = JSON.parse(
|
|
23084
|
+
const entry2 = JSON.parse(readFileSync23(file, "utf8"));
|
|
21077
23085
|
return typeof entry2?.at === "number" ? entry2.at : 0;
|
|
21078
23086
|
} catch {
|
|
21079
23087
|
return 0;
|
|
@@ -21125,18 +23133,18 @@ function sanitizeForAi(input) {
|
|
|
21125
23133
|
}
|
|
21126
23134
|
function assertNoSecrets(input) {
|
|
21127
23135
|
const hits = /* @__PURE__ */ new Set();
|
|
21128
|
-
const visit = (value,
|
|
23136
|
+
const visit = (value, path49) => {
|
|
21129
23137
|
if (typeof value === "string") {
|
|
21130
|
-
if (redactSecrets(value) !== value) hits.add(
|
|
23138
|
+
if (redactSecrets(value) !== value) hits.add(path49 || "<root>");
|
|
21131
23139
|
return;
|
|
21132
23140
|
}
|
|
21133
23141
|
if (Array.isArray(value)) {
|
|
21134
|
-
value.forEach((item, index) => visit(item, `${
|
|
23142
|
+
value.forEach((item, index) => visit(item, `${path49}[${index}]`));
|
|
21135
23143
|
return;
|
|
21136
23144
|
}
|
|
21137
23145
|
if (value && typeof value === "object") {
|
|
21138
23146
|
for (const [key, child] of Object.entries(value)) {
|
|
21139
|
-
const childPath =
|
|
23147
|
+
const childPath = path49 ? `${path49}.${key}` : key;
|
|
21140
23148
|
if (isDeniedKey(key)) {
|
|
21141
23149
|
hits.add(childPath);
|
|
21142
23150
|
continue;
|
|
@@ -21193,16 +23201,16 @@ async function runAiBridge(req, cfg, deps) {
|
|
|
21193
23201
|
if (Buffer.byteLength(body, "utf8") > cfg.maxContextBytes) {
|
|
21194
23202
|
return { ok: false, reason: "bridge-error", detail: "context-too-large" };
|
|
21195
23203
|
}
|
|
21196
|
-
const aiDir =
|
|
21197
|
-
|
|
21198
|
-
const contextFile =
|
|
23204
|
+
const aiDir = path45.join(artifactsRoot(rootDir), "ai");
|
|
23205
|
+
mkdirSync13(aiDir, { recursive: true });
|
|
23206
|
+
const contextFile = path45.join(aiDir, `context-${digest}.json`);
|
|
21199
23207
|
writeFileAtomic(contextFile, body);
|
|
21200
23208
|
const argv = command.map((part) => part === "{context}" ? contextFile : part);
|
|
21201
23209
|
if (!command.includes("{context}")) argv.push(contextFile);
|
|
21202
|
-
const
|
|
23210
|
+
const spawn3 = deps.spawnFn ?? execaSpawn;
|
|
21203
23211
|
let res;
|
|
21204
23212
|
try {
|
|
21205
|
-
res = await
|
|
23213
|
+
res = await spawn3(argv, { timeoutMs: cfg.timeoutMs, cwd: rootDir });
|
|
21206
23214
|
} catch (error) {
|
|
21207
23215
|
return { ok: false, reason: "bridge-error", detail: cap(errorDetail(error)) };
|
|
21208
23216
|
}
|
|
@@ -21236,18 +23244,18 @@ async function runAiBridge(req, cfg, deps) {
|
|
|
21236
23244
|
}
|
|
21237
23245
|
|
|
21238
23246
|
// src/portability/bundle.ts
|
|
21239
|
-
import { existsSync as
|
|
21240
|
-
import
|
|
23247
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync14, readFileSync as readFileSync24 } from "fs";
|
|
23248
|
+
import path46 from "path";
|
|
21241
23249
|
|
|
21242
23250
|
// src/portability/manifest.ts
|
|
21243
|
-
import { createHash as
|
|
23251
|
+
import { createHash as createHash6 } from "crypto";
|
|
21244
23252
|
var INTEGRITY_MANIFEST_SCHEMA = "forge/IntegrityManifest/v1";
|
|
21245
23253
|
var SAFE_ENTRY_PATH = /^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/;
|
|
21246
23254
|
function isSafeEntryPath(p) {
|
|
21247
23255
|
return typeof p === "string" && SAFE_ENTRY_PATH.test(p) && !p.split("/").includes("..");
|
|
21248
23256
|
}
|
|
21249
23257
|
function sha256(data) {
|
|
21250
|
-
return
|
|
23258
|
+
return createHash6("sha256").update(data).digest("hex");
|
|
21251
23259
|
}
|
|
21252
23260
|
function entryDigest(entries) {
|
|
21253
23261
|
return sha256(canonicalJson({ entries }));
|
|
@@ -21324,19 +23332,19 @@ var SecretKeysRefusedError = class extends Error {
|
|
|
21324
23332
|
function writeJson(dest, doc) {
|
|
21325
23333
|
const content = JSON.stringify(doc, null, 2);
|
|
21326
23334
|
writeFileAtomic(dest, content);
|
|
21327
|
-
return { path:
|
|
23335
|
+
return { path: path46.basename(dest), content };
|
|
21328
23336
|
}
|
|
21329
23337
|
function pinVersion(repoDir) {
|
|
21330
23338
|
for (const file of ["forge.json", "package.json"]) {
|
|
21331
23339
|
try {
|
|
21332
|
-
const parsed = JSON.parse(
|
|
23340
|
+
const parsed = JSON.parse(readFileSync24(path46.join(repoDir, file), "utf8"));
|
|
21333
23341
|
const v = parsed?.version;
|
|
21334
23342
|
if (typeof v === "string" && v) return v;
|
|
21335
23343
|
} catch {
|
|
21336
23344
|
}
|
|
21337
23345
|
}
|
|
21338
23346
|
try {
|
|
21339
|
-
const pyproject =
|
|
23347
|
+
const pyproject = readFileSync24(path46.join(repoDir, "pyproject.toml"), "utf8");
|
|
21340
23348
|
const m3 = /^version\s*=\s*"([^"]+)"/m.exec(pyproject);
|
|
21341
23349
|
if (m3) return m3[1];
|
|
21342
23350
|
} catch {
|
|
@@ -21347,8 +23355,8 @@ function collectPins(targetDir, repos) {
|
|
|
21347
23355
|
const state = loadState(targetDir);
|
|
21348
23356
|
const pins = /* @__PURE__ */ new Map();
|
|
21349
23357
|
for (const repo of repos) {
|
|
21350
|
-
const repoDir =
|
|
21351
|
-
const version =
|
|
23358
|
+
const repoDir = path46.join(targetDir, repo.name);
|
|
23359
|
+
const version = existsSync32(repoDir) ? pinVersion(repoDir) : void 0;
|
|
21352
23360
|
const head = state?.repos[repo.name]?.head;
|
|
21353
23361
|
const ref = state?.pins?.[repo.name] ?? head;
|
|
21354
23362
|
pins.set(repo.name, {
|
|
@@ -21374,15 +23382,15 @@ async function exportBundle(targetDir, repos, dest, opts) {
|
|
|
21374
23382
|
}
|
|
21375
23383
|
const schemaByRepo = /* @__PURE__ */ new Map();
|
|
21376
23384
|
for (const repo of repos) {
|
|
21377
|
-
const repoDir =
|
|
21378
|
-
const schema =
|
|
23385
|
+
const repoDir = path46.join(targetDir, repo.name);
|
|
23386
|
+
const schema = existsSync32(repoDir) ? deriveSchema(repoDir, repo.envKeys) : { vars: [] };
|
|
21379
23387
|
schemaByRepo.set(repo.name, schema);
|
|
21380
23388
|
for (const v of schema.vars) {
|
|
21381
23389
|
if (v.secret && whitelist.includes(v.key)) refused.add(v.key);
|
|
21382
23390
|
}
|
|
21383
23391
|
}
|
|
21384
23392
|
if (refused.size) throw new SecretKeysRefusedError([...refused]);
|
|
21385
|
-
|
|
23393
|
+
mkdirSync14(dest, { recursive: true });
|
|
21386
23394
|
const inputs = [];
|
|
21387
23395
|
const produced = [
|
|
21388
23396
|
BUNDLE_FILES.envelope,
|
|
@@ -21395,30 +23403,30 @@ async function exportBundle(targetDir, repos, dest, opts) {
|
|
|
21395
23403
|
BUNDLE_FILES.integrity
|
|
21396
23404
|
].sort();
|
|
21397
23405
|
inputs.push(
|
|
21398
|
-
writeJson(
|
|
23406
|
+
writeJson(path46.join(dest, BUNDLE_FILES.envelope), {
|
|
21399
23407
|
schema: BUNDLE_SCHEMA,
|
|
21400
23408
|
generatedAt: now,
|
|
21401
23409
|
files: produced
|
|
21402
23410
|
})
|
|
21403
23411
|
);
|
|
21404
|
-
const profile = { ...exportProfile(targetDir,
|
|
23412
|
+
const profile = { ...exportProfile(targetDir, path46.join(dest, BUNDLE_FILES.profile)) };
|
|
21405
23413
|
const normalizedProfile = { ...profile, exported_at: now };
|
|
21406
|
-
inputs.push(writeJson(
|
|
23414
|
+
inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.profile), normalizedProfile));
|
|
21407
23415
|
const lock = { ...writeEnvLock(targetDir, repos), generated_at: now };
|
|
21408
|
-
inputs.push(writeJson(
|
|
21409
|
-
inputs.push(writeJson(
|
|
23416
|
+
inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.lock), lock));
|
|
23417
|
+
inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.lockCheck), checkEnvLock(targetDir, repos)));
|
|
21410
23418
|
const envKeys = {};
|
|
21411
23419
|
for (const repo of repos) {
|
|
21412
23420
|
const keys = [...new Set((schemaByRepo.get(repo.name)?.vars ?? []).map((v) => v.key))].sort();
|
|
21413
23421
|
envKeys[repo.name] = keys;
|
|
21414
23422
|
}
|
|
21415
|
-
inputs.push(writeJson(
|
|
23423
|
+
inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.envKeys), envKeys));
|
|
21416
23424
|
if (whitelist.length) {
|
|
21417
23425
|
const values = {};
|
|
21418
23426
|
for (const repo of repos) {
|
|
21419
|
-
const envFile =
|
|
21420
|
-
if (!
|
|
21421
|
-
const entries = parseEnv(
|
|
23427
|
+
const envFile = path46.join(targetDir, repo.name, ".env");
|
|
23428
|
+
if (!existsSync32(envFile)) continue;
|
|
23429
|
+
const entries = parseEnv(readFileSync24(envFile, "utf8")).entries;
|
|
21422
23430
|
const found = {};
|
|
21423
23431
|
for (const key of whitelist) {
|
|
21424
23432
|
const entry2 = entries.find((e) => e.key === key);
|
|
@@ -21426,16 +23434,16 @@ async function exportBundle(targetDir, repos, dest, opts) {
|
|
|
21426
23434
|
}
|
|
21427
23435
|
if (Object.keys(found).length) values[repo.name] = found;
|
|
21428
23436
|
}
|
|
21429
|
-
inputs.push(writeJson(
|
|
23437
|
+
inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.envValues), values));
|
|
21430
23438
|
}
|
|
21431
|
-
inputs.push(writeJson(
|
|
23439
|
+
inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.pins), collectPins(targetDir, repos)));
|
|
21432
23440
|
const manifest = buildIntegrityManifest(inputs, { now });
|
|
21433
|
-
inputs.push(writeJson(
|
|
23441
|
+
inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.integrity), manifest));
|
|
21434
23442
|
return { ok: true, dir: dest, files: produced, manifest };
|
|
21435
23443
|
}
|
|
21436
23444
|
function readJsonFile(file) {
|
|
21437
23445
|
try {
|
|
21438
|
-
return JSON.parse(
|
|
23446
|
+
return JSON.parse(readFileSync24(file, "utf8"));
|
|
21439
23447
|
} catch {
|
|
21440
23448
|
return null;
|
|
21441
23449
|
}
|
|
@@ -21484,40 +23492,40 @@ function readPins(file) {
|
|
|
21484
23492
|
function importBundle(src, opts) {
|
|
21485
23493
|
const read = (p) => {
|
|
21486
23494
|
try {
|
|
21487
|
-
return
|
|
23495
|
+
return readFileSync24(path46.join(src, p));
|
|
21488
23496
|
} catch {
|
|
21489
23497
|
return null;
|
|
21490
23498
|
}
|
|
21491
23499
|
};
|
|
21492
|
-
const manifestRaw = readJsonFile(
|
|
23500
|
+
const manifestRaw = readJsonFile(path46.join(src, BUNDLE_FILES.integrity));
|
|
21493
23501
|
const manifest = manifestRaw && typeof manifestRaw === "object" && manifestRaw.schema === "forge/IntegrityManifest/v1" ? manifestRaw : null;
|
|
21494
23502
|
const verify = opts?.verifyIntegrity === false ? { ok: true } : verifyIntegrityManifest(manifest, read);
|
|
21495
|
-
const profile = importProfile(
|
|
23503
|
+
const profile = importProfile(path46.join(src, BUNDLE_FILES.profile));
|
|
21496
23504
|
return {
|
|
21497
23505
|
dir: src,
|
|
21498
23506
|
verify,
|
|
21499
23507
|
manifest,
|
|
21500
23508
|
profile: "error" in profile ? null : profile,
|
|
21501
|
-
lockfile: readLockfile(
|
|
21502
|
-
lockCheck: readLockCheck(
|
|
21503
|
-
envKeys: readStringMap(
|
|
21504
|
-
envValues: readValueMap(
|
|
21505
|
-
pins: readPins(
|
|
23509
|
+
lockfile: readLockfile(path46.join(src, BUNDLE_FILES.lock)),
|
|
23510
|
+
lockCheck: readLockCheck(path46.join(src, BUNDLE_FILES.lockCheck)),
|
|
23511
|
+
envKeys: readStringMap(path46.join(src, BUNDLE_FILES.envKeys)),
|
|
23512
|
+
envValues: readValueMap(path46.join(src, BUNDLE_FILES.envValues)),
|
|
23513
|
+
pins: readPins(path46.join(src, BUNDLE_FILES.pins))
|
|
21506
23514
|
};
|
|
21507
23515
|
}
|
|
21508
23516
|
|
|
21509
23517
|
// src/intel/learning.ts
|
|
21510
|
-
import { existsSync as
|
|
21511
|
-
import
|
|
23518
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync15, readFileSync as readFileSync25 } from "fs";
|
|
23519
|
+
import path47 from "path";
|
|
21512
23520
|
var LEARNING_SCHEMA = "forge-cli/LearningStore/v1";
|
|
21513
23521
|
function learningFile(targetDir) {
|
|
21514
|
-
return
|
|
23522
|
+
return path47.join(artifactsRoot(targetDir), "learning.json");
|
|
21515
23523
|
}
|
|
21516
23524
|
function readStore(targetDir) {
|
|
21517
23525
|
const file = learningFile(targetDir);
|
|
21518
|
-
if (!
|
|
23526
|
+
if (!existsSync33(file)) return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
|
|
21519
23527
|
try {
|
|
21520
|
-
const parsed = JSON.parse(
|
|
23528
|
+
const parsed = JSON.parse(readFileSync25(file, "utf8"));
|
|
21521
23529
|
if (parsed?.schema !== LEARNING_SCHEMA || !Array.isArray(parsed.observations)) {
|
|
21522
23530
|
return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
|
|
21523
23531
|
}
|
|
@@ -21534,7 +23542,7 @@ function readStore(targetDir) {
|
|
|
21534
23542
|
}
|
|
21535
23543
|
function writeStore(targetDir, store) {
|
|
21536
23544
|
const file = learningFile(targetDir);
|
|
21537
|
-
|
|
23545
|
+
mkdirSync15(path47.dirname(file), { recursive: true });
|
|
21538
23546
|
writeFileAtomic(file, JSON.stringify(store, null, 2));
|
|
21539
23547
|
}
|
|
21540
23548
|
function learningStatus(targetDir) {
|
|
@@ -21689,12 +23697,12 @@ async function runBundleCommand(cli) {
|
|
|
21689
23697
|
if (cli.jsonReport) emitJson(result);
|
|
21690
23698
|
log.success(`${S.bundleExported}: ${dest}`);
|
|
21691
23699
|
return 0;
|
|
21692
|
-
} catch (
|
|
21693
|
-
if (
|
|
21694
|
-
log.error(`${S.bundleSecretRefused}: ${
|
|
23700
|
+
} catch (err2) {
|
|
23701
|
+
if (err2 instanceof SecretKeysRefusedError) {
|
|
23702
|
+
log.error(`${S.bundleSecretRefused}: ${err2.keys.join(", ")}`);
|
|
21695
23703
|
return 1;
|
|
21696
23704
|
}
|
|
21697
|
-
throw
|
|
23705
|
+
throw err2;
|
|
21698
23706
|
}
|
|
21699
23707
|
}
|
|
21700
23708
|
if (verb === "import" || verb === "verify") {
|
|
@@ -21705,8 +23713,8 @@ async function runBundleCommand(cli) {
|
|
|
21705
23713
|
let result;
|
|
21706
23714
|
try {
|
|
21707
23715
|
result = importBundle(dest);
|
|
21708
|
-
} catch (
|
|
21709
|
-
log.error(`${S.bundleInvalid}: ${
|
|
23716
|
+
} catch (err2) {
|
|
23717
|
+
log.error(`${S.bundleInvalid}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
21710
23718
|
return 1;
|
|
21711
23719
|
}
|
|
21712
23720
|
if (cli.jsonReport) emitJson(result);
|
|
@@ -21755,8 +23763,8 @@ async function runLearningCommand(cli) {
|
|
|
21755
23763
|
}
|
|
21756
23764
|
|
|
21757
23765
|
// src/wizard.ts
|
|
21758
|
-
import { existsSync as
|
|
21759
|
-
import
|
|
23766
|
+
import { existsSync as existsSync34 } from "fs";
|
|
23767
|
+
import path48 from "path";
|
|
21760
23768
|
var STEP_LABELS = [
|
|
21761
23769
|
S.wizardStepDetect,
|
|
21762
23770
|
S.wizardStepRecommend,
|
|
@@ -21795,8 +23803,8 @@ function renderOperationPlan(plan) {
|
|
|
21795
23803
|
return lines.join("\n");
|
|
21796
23804
|
}
|
|
21797
23805
|
function planActionsFor(repo, targetDir, steps) {
|
|
21798
|
-
const repoDir =
|
|
21799
|
-
const cloned =
|
|
23806
|
+
const repoDir = path48.join(targetDir, repo.name);
|
|
23807
|
+
const cloned = existsSync34(repoDir) && hasGitRepo(repoDir);
|
|
21800
23808
|
const actions = [
|
|
21801
23809
|
{
|
|
21802
23810
|
kind: "clone",
|
|
@@ -21816,7 +23824,7 @@ function planActionsFor(repo, targetDir, steps) {
|
|
|
21816
23824
|
actions.push({
|
|
21817
23825
|
kind: "env",
|
|
21818
23826
|
label: "provisionar .env (nunca sobrescreve)",
|
|
21819
|
-
paths: [
|
|
23827
|
+
paths: [path48.join(repo.name, ".env")],
|
|
21820
23828
|
reversible: false
|
|
21821
23829
|
});
|
|
21822
23830
|
if (steps.includes("skills")) {
|
|
@@ -21917,7 +23925,7 @@ async function runWizard(cli) {
|
|
|
21917
23925
|
const planForges = repos.map((repo) => ({
|
|
21918
23926
|
forge_id: repo.name,
|
|
21919
23927
|
repo: repo.name,
|
|
21920
|
-
target_dir:
|
|
23928
|
+
target_dir: path48.join(targetDir, repo.name),
|
|
21921
23929
|
actions: filterSteps(planActionsFor(repo, targetDir, steps), steps)
|
|
21922
23930
|
}));
|
|
21923
23931
|
const plan = buildPlan({
|
|
@@ -21965,12 +23973,16 @@ var JSON_QUERY_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
21965
23973
|
"recommend",
|
|
21966
23974
|
"env",
|
|
21967
23975
|
"hosts",
|
|
23976
|
+
"mcp",
|
|
21968
23977
|
"lock",
|
|
21969
23978
|
"profile",
|
|
21970
23979
|
"recover",
|
|
21971
23980
|
"ask",
|
|
21972
23981
|
"predict",
|
|
21973
23982
|
"context",
|
|
23983
|
+
"install",
|
|
23984
|
+
"uninstall",
|
|
23985
|
+
"registry",
|
|
21974
23986
|
"ai",
|
|
21975
23987
|
"bundle",
|
|
21976
23988
|
"learning"
|
|
@@ -21997,7 +24009,7 @@ async function main() {
|
|
|
21997
24009
|
}
|
|
21998
24010
|
if (shouldShowMenu(cli, Boolean(process.stdin.isTTY && process.stdout.isTTY))) {
|
|
21999
24011
|
const next = await applyMenu(cli, {
|
|
22000
|
-
ask: () => askMenu(),
|
|
24012
|
+
ask: () => askMenu({}, menuItemsFor(cli)),
|
|
22001
24013
|
workspace: askWorkspace,
|
|
22002
24014
|
resolveDir: resolveTargetDir,
|
|
22003
24015
|
pickRepos: async (dir) => {
|
|
@@ -22008,6 +24020,33 @@ async function main() {
|
|
|
22008
24020
|
status: new Map(infos.map((i3) => [i3.name, i3]))
|
|
22009
24021
|
});
|
|
22010
24022
|
return chosen.map((r2) => r2.name);
|
|
24023
|
+
},
|
|
24024
|
+
pickRegistryCatalog: async () => {
|
|
24025
|
+
const picked = await multiselect({
|
|
24026
|
+
message: "Forjas para instalar do PyPI (registry-first)",
|
|
24027
|
+
options: REGISTRY_PACKAGES.map((e) => ({
|
|
24028
|
+
value: e.name,
|
|
24029
|
+
label: e.name,
|
|
24030
|
+
hint: `${e.dist} \xB7 python >=${e.minPython}`
|
|
24031
|
+
})),
|
|
24032
|
+
required: true
|
|
24033
|
+
});
|
|
24034
|
+
if (isCancel(picked)) exitCancelled();
|
|
24035
|
+
return picked;
|
|
24036
|
+
},
|
|
24037
|
+
pickRegistryInstalled: async () => {
|
|
24038
|
+
const installed = Object.keys(loadForgeState(cliHome()).forges);
|
|
24039
|
+
if (!installed.length) {
|
|
24040
|
+
log.warn("Nenhuma Forja instalada via registry ainda \u2014 instale primeiro.");
|
|
24041
|
+
return [];
|
|
24042
|
+
}
|
|
24043
|
+
const picked = await multiselect({
|
|
24044
|
+
message: "Forjas instaladas (registry)",
|
|
24045
|
+
options: installed.map((n4) => ({ value: n4, label: n4 })),
|
|
24046
|
+
required: true
|
|
24047
|
+
});
|
|
24048
|
+
if (isCancel(picked)) exitCancelled();
|
|
24049
|
+
return picked;
|
|
22011
24050
|
}
|
|
22012
24051
|
});
|
|
22013
24052
|
if (!next) {
|
|
@@ -22023,6 +24062,54 @@ async function main() {
|
|
|
22023
24062
|
console.log(usage());
|
|
22024
24063
|
process.exit(2);
|
|
22025
24064
|
}
|
|
24065
|
+
const REGISTRY_COMMANDS = /* @__PURE__ */ new Set([
|
|
24066
|
+
"install",
|
|
24067
|
+
"uninstall",
|
|
24068
|
+
"update",
|
|
24069
|
+
"reinstall",
|
|
24070
|
+
"rollback",
|
|
24071
|
+
"pin",
|
|
24072
|
+
"unpin"
|
|
24073
|
+
]);
|
|
24074
|
+
if (cli.method === "registry" && !REGISTRY_COMMANDS.has(resolved.command)) {
|
|
24075
|
+
log.error(S.errMethodUnsupported);
|
|
24076
|
+
process.exit(2);
|
|
24077
|
+
}
|
|
24078
|
+
if (cli.method === "registry" && resolved.command === "update") {
|
|
24079
|
+
await runRegistryUpdate(cli, resolved.names);
|
|
24080
|
+
return;
|
|
24081
|
+
}
|
|
24082
|
+
if (cli.method === "registry" && resolved.command === "reinstall") {
|
|
24083
|
+
await runRegistryReinstall(cli, resolved.names);
|
|
24084
|
+
return;
|
|
24085
|
+
}
|
|
24086
|
+
if (cli.method === "registry" && resolved.command === "rollback") {
|
|
24087
|
+
await runRegistryRollback(cli, resolved.names);
|
|
24088
|
+
return;
|
|
24089
|
+
}
|
|
24090
|
+
if (cli.method === "registry" && resolved.command === "pin") {
|
|
24091
|
+
await runRegistryPin(cli, resolved.names, true);
|
|
24092
|
+
return;
|
|
24093
|
+
}
|
|
24094
|
+
if (cli.method === "registry" && resolved.command === "unpin") {
|
|
24095
|
+
await runRegistryPin(cli, resolved.names, false);
|
|
24096
|
+
return;
|
|
24097
|
+
}
|
|
24098
|
+
const METHOD_COMMANDS = /* @__PURE__ */ new Set([
|
|
24099
|
+
"run",
|
|
24100
|
+
"install",
|
|
24101
|
+
"clone",
|
|
24102
|
+
"uninstall",
|
|
24103
|
+
"update",
|
|
24104
|
+
"reinstall",
|
|
24105
|
+
"pin",
|
|
24106
|
+
"unpin",
|
|
24107
|
+
"rollback",
|
|
24108
|
+
"wizard"
|
|
24109
|
+
]);
|
|
24110
|
+
if (cli.method === "git" && !METHOD_COMMANDS.has(resolved.command)) {
|
|
24111
|
+
log.warn(`--method git n\xE3o tem efeito em "${resolved.command}" \u2014 ignorado`);
|
|
24112
|
+
}
|
|
22026
24113
|
if (resolved.command === "doctor") {
|
|
22027
24114
|
process.exitCode = await runDoctor2Command(cli);
|
|
22028
24115
|
return;
|
|
@@ -22047,6 +24134,10 @@ async function main() {
|
|
|
22047
24134
|
process.exitCode = await runHostsCommand(cli, resolved.names);
|
|
22048
24135
|
return;
|
|
22049
24136
|
}
|
|
24137
|
+
if (resolved.command === "mcp") {
|
|
24138
|
+
process.exitCode = await runMcpCommand(cli, resolved.names);
|
|
24139
|
+
return;
|
|
24140
|
+
}
|
|
22050
24141
|
if (resolved.command === "wizard") {
|
|
22051
24142
|
if (isNonInteractive(cli)) {
|
|
22052
24143
|
log.error(S.errCloneNeedsNames);
|
|
@@ -22087,6 +24178,10 @@ async function main() {
|
|
|
22087
24178
|
process.exitCode = await runBundleCommand(cli);
|
|
22088
24179
|
return;
|
|
22089
24180
|
}
|
|
24181
|
+
if (resolved.command === "registry") {
|
|
24182
|
+
runRegistryList(cli, { names: resolved.names });
|
|
24183
|
+
return;
|
|
24184
|
+
}
|
|
22090
24185
|
if (resolved.command === "learning") {
|
|
22091
24186
|
process.exitCode = await runLearningCommand(cli);
|
|
22092
24187
|
return;
|
|
@@ -22151,6 +24246,10 @@ async function main() {
|
|
|
22151
24246
|
await runUninstallCommand();
|
|
22152
24247
|
return;
|
|
22153
24248
|
}
|
|
24249
|
+
if (resolved.command === "uninstall" && cli.method === "registry") {
|
|
24250
|
+
await runRegistryUninstall(cli, resolved.names);
|
|
24251
|
+
return;
|
|
24252
|
+
}
|
|
22154
24253
|
if (resolved.command === "uninstall" && !resolved.names.length && !isNonInteractive(cli)) {
|
|
22155
24254
|
log.warn(S.warnLegacyUninstall);
|
|
22156
24255
|
await runUninstallCommand();
|
|
@@ -22176,6 +24275,19 @@ async function main() {
|
|
|
22176
24275
|
log.error(S.errReinstallNeedsNames);
|
|
22177
24276
|
process.exit(2);
|
|
22178
24277
|
}
|
|
24278
|
+
if (resolved.command === "install" && cli.method === "registry") {
|
|
24279
|
+
const names = resolved.names.length ? resolved.names : cli.all ? registryNames() : cli.only ?? [];
|
|
24280
|
+
if (!names.length) {
|
|
24281
|
+
log.error(S.errRegistryNeedsNames);
|
|
24282
|
+
process.exit(2);
|
|
24283
|
+
}
|
|
24284
|
+
if (cli.ref && new Set(names.map((n4) => registryEntry(n4)?.name ?? n4)).size !== 1) {
|
|
24285
|
+
log.error(S.errRefNeedsOne);
|
|
24286
|
+
process.exit(2);
|
|
24287
|
+
}
|
|
24288
|
+
await runRegistryInstall(cli, names);
|
|
24289
|
+
return;
|
|
24290
|
+
}
|
|
22179
24291
|
if (resolved.names.length) cli = { ...cli, only: [...cli.only ?? [], ...resolved.names] };
|
|
22180
24292
|
const cloneOnly = resolved.command === "clone";
|
|
22181
24293
|
if (cloneOnly && !isNonInteractive(cli)) {
|