pulso-doctor 1.0.0 → 1.1.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 +17 -0
- package/README.md +13 -5
- package/dist/{chunk-FQDXA3BM.js → chunk-B4E4SOCQ.js} +228 -11
- package/dist/cli.js +12 -3
- package/dist/index.d.ts +24 -1
- package/dist/index.js +5 -3
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
# pulso-doctor
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Quando há um token gitlab configurado, verifica se o token está válido, expirado, ou sem as permissões necessárias. Traz um feedback de como proceder, sem mostrar o valor do token verificado.
|
|
8
|
+
|
|
9
|
+
## 1.1.0 - 11/11/2026
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Adiciona teste real de validação do token do registry Pulso contra o GitLab via requisição HTTP autenticada.
|
|
14
|
+
- Detecta e reporta tokens expirados, revogados ou sem permissão de acesso ao registry, com mensagens de erro claras e orientações de correção indicando a variável de ambiente ou arquivo correspondente.
|
|
15
|
+
- Adiciona opção `--skip-token-check` na CLI e opção `skipTokenVerification` na API programática (além do suporte à variável `PULSO_DOCTOR_SKIP_TOKEN_CHECK=1`).
|
|
16
|
+
- Suporta chaves de autenticação em nível de projeto e em nível de instância GitLab.
|
|
17
|
+
- Bloqueia a execução do auto-fix para instalar dependências quando o token de autenticação do registry estiver inválido ou expirado.
|
|
18
|
+
|
|
3
19
|
## 1.0.0 - 05/11/2026
|
|
20
|
+
|
|
4
21
|
### Major Changes
|
|
5
22
|
|
|
6
23
|
- 963a91d: Disponibiliza o diagnóstico público do ambiente necessário para consumir os componentes Pulso Web e App, com saída orientada, API reutilizável e auto-fix opcional, sem depender do registry privado para sua execução.
|
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ Outras opções:
|
|
|
45
45
|
```bash
|
|
46
46
|
npx pulso-doctor --cwd ./apps/site
|
|
47
47
|
npx pulso-doctor --json
|
|
48
|
+
npx pulso-doctor --skip-token-check
|
|
48
49
|
npx pulso-doctor --log-file ./diagnosticos/pulso.jsonl
|
|
49
50
|
```
|
|
50
51
|
|
|
@@ -61,6 +62,7 @@ desativar as cores explicitamente, use `NO_COLOR=1 npx pulso-doctor`.
|
|
|
61
62
|
- presença e sintaxe do `package.json`;
|
|
62
63
|
- configuração do registry `@pulso` e sua autenticação no `.npmrc`, no
|
|
63
64
|
`.yarnrc` do Yarn Classic ou no `.yarnrc.yml` do Yarn Berry;
|
|
65
|
+
- validação real do token contra o registry GitLab (testa se o token é válido, se expirou ou se foi revogado);
|
|
64
66
|
- registry `@rd-core-svg` para projetos App;
|
|
65
67
|
- proteção do `.npmrc` no `.gitignore` ou uso de variável de ambiente nas
|
|
66
68
|
configurações do Yarn;
|
|
@@ -73,7 +75,11 @@ Quando existe `node_modules`, a versão instalada é a fonte principal. Sem uma
|
|
|
73
75
|
instalação local, o Doctor avalia o range declarado e informa que a versão
|
|
74
76
|
resolvida ainda precisa ser confirmada.
|
|
75
77
|
|
|
76
|
-
O Doctor
|
|
78
|
+
O Doctor valida o token fazendo uma consulta HTTP ao endpoint de pacotes do GitLab
|
|
79
|
+
(sem expor o token nos logs ou saída). Em caso de falha de conexão (offline),
|
|
80
|
+
registra um alerta em vez de reprovar o diagnóstico. Caso queira desativar a
|
|
81
|
+
verificação de rede do token, utilize a opção `--skip-token-check` ou a variável de
|
|
82
|
+
ambiente `PULSO_DOCTOR_SKIP_TOKEN_CHECK=1`. O Doctor não altera configurações e não
|
|
77
83
|
instala dependências durante o diagnóstico normal. Alterações só são executadas
|
|
78
84
|
depois da confirmação interativa ou com `--fix`.
|
|
79
85
|
|
|
@@ -185,7 +191,9 @@ mesmo diagnóstico e o fluxo de correção.
|
|
|
185
191
|
|
|
186
192
|
## Privacidade
|
|
187
193
|
|
|
188
|
-
O pacote não possui analytics ou telemetria.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
194
|
+
O pacote não possui analytics ou telemetria. A única chamada de rede realizada
|
|
195
|
+
pelo diagnóstico é a validação do token diretamente contra o registry privado do
|
|
196
|
+
GitLab configurado no projeto (podendo ser desativada com `--skip-token-check`).
|
|
197
|
+
O valor do token nunca é gravado nos logs ou exibido na saída do terminal. Quando
|
|
198
|
+
autorizado, o auto-fix pode editar os arquivos de configuração citados acima e
|
|
199
|
+
executar o gerenciador de pacotes do projeto.
|
|
@@ -5,7 +5,9 @@ import { intersects, minVersion, satisfies, subset, valid } from "semver";
|
|
|
5
5
|
import { parse as parseYaml } from "yaml";
|
|
6
6
|
var PULSO_REGISTRY = "https://gitlab.com/api/v4/projects/75913903/packages/npm/";
|
|
7
7
|
var PULSO_AUTH_KEY = "//gitlab.com/api/v4/projects/75913903/packages/npm/:_authToken";
|
|
8
|
+
var INSTANCE_AUTH_KEY = "//gitlab.com/api/v4/packages/npm/:_authToken";
|
|
8
9
|
var RD_CORE_SVG_REGISTRY = "https://gitlab.com/api/v4/projects/44326237/packages/npm/";
|
|
10
|
+
var RD_CORE_SVG_AUTH_KEY = "//gitlab.com/api/v4/projects/44326237/packages/npm/:_authToken";
|
|
9
11
|
var SHARED_DEPENDENCIES = [
|
|
10
12
|
{ name: "@pulso/design-tokens" },
|
|
11
13
|
{ name: "@pulso/icons" }
|
|
@@ -134,6 +136,10 @@ function normalizeRegistry(value) {
|
|
|
134
136
|
if (!value) return void 0;
|
|
135
137
|
return `${value.replace(/\/+$/, "")}/`;
|
|
136
138
|
}
|
|
139
|
+
function stripQuotes(value) {
|
|
140
|
+
if (!value) return void 0;
|
|
141
|
+
return value.trim().replace(/(?:^["'])|(?:["']$)/g, "");
|
|
142
|
+
}
|
|
137
143
|
function npmrcSnippet(platforms) {
|
|
138
144
|
const appRegistry = platforms.includes("app") ? `@rd-core-svg:registry=${RD_CORE_SVG_REGISTRY}
|
|
139
145
|
|
|
@@ -218,7 +224,87 @@ async function readRegistryConfiguration(cwd) {
|
|
|
218
224
|
return null;
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
|
-
async function
|
|
227
|
+
async function verifyRegistryToken(options) {
|
|
228
|
+
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
229
|
+
if (typeof fetchFn !== "function") {
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
status: "network-error",
|
|
233
|
+
detail: "fetch n\xE3o dispon\xEDvel no ambiente de execu\xE7\xE3o."
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const normalizedRegistry = options.registry.replace(/\/+$/, "");
|
|
237
|
+
const packageName = options.packageName ?? (normalizedRegistry.includes("44326237") ? "@rd-core-svg/svg" : "@pulso/design-tokens");
|
|
238
|
+
const targetUrl = `${normalizedRegistry}/${packageName}`;
|
|
239
|
+
const controller = new AbortController();
|
|
240
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
241
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
242
|
+
try {
|
|
243
|
+
const response = await fetchFn(targetUrl, {
|
|
244
|
+
method: "GET",
|
|
245
|
+
headers: {
|
|
246
|
+
Authorization: `Bearer ${options.token.trim()}`,
|
|
247
|
+
"User-Agent": "pulso-doctor",
|
|
248
|
+
Accept: "application/json"
|
|
249
|
+
},
|
|
250
|
+
signal: controller.signal
|
|
251
|
+
});
|
|
252
|
+
if (response.ok) {
|
|
253
|
+
return {
|
|
254
|
+
ok: true,
|
|
255
|
+
status: "valid",
|
|
256
|
+
httpStatus: response.status
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
let detail;
|
|
260
|
+
try {
|
|
261
|
+
const data = await response.json();
|
|
262
|
+
detail = data.error_description || data.message || data.error;
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
const isExpired = Boolean(detail) && (detail.toLowerCase().includes("expired") || detail.toLowerCase().includes("expirado"));
|
|
266
|
+
if (response.status === 401) {
|
|
267
|
+
return {
|
|
268
|
+
ok: false,
|
|
269
|
+
status: isExpired ? "expired" : "invalid",
|
|
270
|
+
httpStatus: response.status,
|
|
271
|
+
detail
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (response.status === 403) {
|
|
275
|
+
return {
|
|
276
|
+
ok: false,
|
|
277
|
+
status: "forbidden",
|
|
278
|
+
httpStatus: response.status,
|
|
279
|
+
detail
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (response.status === 404) {
|
|
283
|
+
return {
|
|
284
|
+
ok: false,
|
|
285
|
+
status: "not-found",
|
|
286
|
+
httpStatus: response.status,
|
|
287
|
+
detail
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
ok: false,
|
|
292
|
+
status: "invalid",
|
|
293
|
+
httpStatus: response.status,
|
|
294
|
+
detail: detail ?? `HTTP ${response.status}`
|
|
295
|
+
};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const isTimeout = error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
298
|
+
return {
|
|
299
|
+
ok: false,
|
|
300
|
+
status: "network-error",
|
|
301
|
+
detail: isTimeout ? "tempo limite esgotado ao conectar ao GitLab (timeout)" : error instanceof Error ? error.message : "falha de conex\xE3o com o registry"
|
|
302
|
+
};
|
|
303
|
+
} finally {
|
|
304
|
+
clearTimeout(timeoutId);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function validateRegistryConfiguration(cwd, platforms, environment, options = {}) {
|
|
222
308
|
const configuration = await readRegistryConfiguration(cwd);
|
|
223
309
|
if (!configuration) {
|
|
224
310
|
const usesYarnBerry = await fileExists(join(cwd, ".yarnrc.yml"));
|
|
@@ -262,7 +348,9 @@ async function validateRegistryConfiguration(cwd, platforms, environment) {
|
|
|
262
348
|
resolution: source === ".yarnrc.yml" ? `Defina npmScopes.pulso.npmRegistryServer=${PULSO_REGISTRY} no .yarnrc.yml.` : source === ".yarnrc" ? `Defina "@pulso:registry" "${PULSO_REGISTRY}" no .yarnrc.` : `Defina @pulso:registry=${PULSO_REGISTRY} no .npmrc.`
|
|
263
349
|
})
|
|
264
350
|
);
|
|
265
|
-
const authValue = entries.get(PULSO_AUTH_KEY);
|
|
351
|
+
const authValue = entries.get(PULSO_AUTH_KEY) ?? entries.get(INSTANCE_AUTH_KEY);
|
|
352
|
+
let pulsoToken;
|
|
353
|
+
let pulsoVariableName;
|
|
266
354
|
if (!authValue) {
|
|
267
355
|
results.push(
|
|
268
356
|
check({
|
|
@@ -276,21 +364,105 @@ async function validateRegistryConfiguration(cwd, platforms, environment) {
|
|
|
276
364
|
})
|
|
277
365
|
);
|
|
278
366
|
} else {
|
|
279
|
-
const
|
|
280
|
-
const
|
|
281
|
-
|
|
367
|
+
const rawAuth = stripQuotes(authValue) ?? "";
|
|
368
|
+
const variableMatch = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(rawAuth) ?? /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(rawAuth);
|
|
369
|
+
pulsoVariableName = variableMatch?.[1];
|
|
370
|
+
pulsoToken = pulsoVariableName ? stripQuotes(environment[pulsoVariableName]) : rawAuth;
|
|
371
|
+
const available = Boolean(pulsoToken);
|
|
282
372
|
results.push(
|
|
283
373
|
check({
|
|
284
374
|
id: "registry.pulso-auth",
|
|
285
375
|
category: "registry",
|
|
286
376
|
status: available ? "pass" : "error",
|
|
287
377
|
title: "Autentica\xE7\xE3o do registry Pulso",
|
|
288
|
-
message: available ? `Autentica\xE7\xE3o configurada em ${source} via ${
|
|
289
|
-
actual:
|
|
290
|
-
resolution: available ? void 0 : `Exporte ${
|
|
378
|
+
message: available ? `Autentica\xE7\xE3o configurada em ${source} via ${pulsoVariableName ? `vari\xE1vel ${pulsoVariableName}` : "valor local"}.` : `A vari\xE1vel ${pulsoVariableName} referenciada em ${source} n\xE3o est\xE1 dispon\xEDvel.`,
|
|
379
|
+
actual: pulsoVariableName ? `vari\xE1vel ${pulsoVariableName}` : "valor configurado",
|
|
380
|
+
resolution: available ? void 0 : `Exporte ${pulsoVariableName} antes de instalar pacotes Pulso.`
|
|
291
381
|
})
|
|
292
382
|
);
|
|
293
383
|
}
|
|
384
|
+
const skipEnv = environment.PULSO_DOCTOR_SKIP_TOKEN_CHECK === "1" || environment.PULSO_DOCTOR_SKIP_TOKEN_CHECK === "true";
|
|
385
|
+
const shouldVerifyTokens = !options.skipTokenVerification && !skipEnv;
|
|
386
|
+
if (pulsoToken && shouldVerifyTokens) {
|
|
387
|
+
const verifier = options.tokenVerifier ?? verifyRegistryToken;
|
|
388
|
+
const verification = await verifier({
|
|
389
|
+
registry: pulsoRegistry ?? PULSO_REGISTRY,
|
|
390
|
+
token: pulsoToken,
|
|
391
|
+
fetchFn: options.fetchFn
|
|
392
|
+
});
|
|
393
|
+
if (verification.status === "valid") {
|
|
394
|
+
results.push(
|
|
395
|
+
check({
|
|
396
|
+
id: "registry.pulso-token",
|
|
397
|
+
category: "registry",
|
|
398
|
+
status: "pass",
|
|
399
|
+
title: "Valida\xE7\xE3o do token do registry Pulso",
|
|
400
|
+
message: "Token de autentica\xE7\xE3o validado com sucesso no GitLab.",
|
|
401
|
+
actual: "token v\xE1lido (HTTP 200)"
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
} else if (verification.status === "expired") {
|
|
405
|
+
results.push(
|
|
406
|
+
check({
|
|
407
|
+
id: "registry.pulso-token",
|
|
408
|
+
category: "registry",
|
|
409
|
+
status: "error",
|
|
410
|
+
title: "Valida\xE7\xE3o do token do registry Pulso",
|
|
411
|
+
message: `O token do registry Pulso est\xE1 expirado no GitLab (${verification.detail ?? "Token is expired"}).`,
|
|
412
|
+
actual: "token expirado (HTTP 401)",
|
|
413
|
+
resolution: pulsoVariableName ? `Gere um novo token no GitLab com escopo de leitura do registry e atualize a vari\xE1vel ${pulsoVariableName} (se j\xE1 atualizou no ~/.zshrc ou ~/.bashrc, execute "source ~/.zshrc" ou abra um novo terminal).` : `Gere um novo token no GitLab com escopo de leitura do registry e atualize a configura\xE7\xE3o em ${source}.`
|
|
414
|
+
})
|
|
415
|
+
);
|
|
416
|
+
} else if (verification.status === "forbidden") {
|
|
417
|
+
results.push(
|
|
418
|
+
check({
|
|
419
|
+
id: "registry.pulso-token",
|
|
420
|
+
category: "registry",
|
|
421
|
+
status: "error",
|
|
422
|
+
title: "Valida\xE7\xE3o do token do registry Pulso",
|
|
423
|
+
message: `O token n\xE3o possui permiss\xE3o para acessar o registry privado do Pulso no GitLab${verification.detail ? ` (${verification.detail})` : ""}.`,
|
|
424
|
+
actual: "permiss\xE3o negada (HTTP 403)",
|
|
425
|
+
resolution: "Verifique se o token possui as permiss\xF5es necess\xE1rias (escopo read_package_registry / read_api) no projeto Pulso."
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
} else if (verification.status === "not-found") {
|
|
429
|
+
results.push(
|
|
430
|
+
check({
|
|
431
|
+
id: "registry.pulso-token",
|
|
432
|
+
category: "registry",
|
|
433
|
+
status: "error",
|
|
434
|
+
title: "Valida\xE7\xE3o do token do registry Pulso",
|
|
435
|
+
message: "O registry privado do Pulso n\xE3o foi encontrado ou n\xE3o \xE9 acess\xEDvel com este token (HTTP 404).",
|
|
436
|
+
actual: "n\xE3o encontrado (HTTP 404)",
|
|
437
|
+
resolution: "Verifique se a URL do registry est\xE1 correta e se o token tem acesso ao projeto no GitLab."
|
|
438
|
+
})
|
|
439
|
+
);
|
|
440
|
+
} else if (verification.status === "network-error") {
|
|
441
|
+
results.push(
|
|
442
|
+
check({
|
|
443
|
+
id: "registry.pulso-token",
|
|
444
|
+
category: "registry",
|
|
445
|
+
status: "warning",
|
|
446
|
+
title: "Valida\xE7\xE3o do token do registry Pulso",
|
|
447
|
+
message: `N\xE3o foi poss\xEDvel validar o token junto ao GitLab: ${verification.detail ?? "falha de conex\xE3o"}.`,
|
|
448
|
+
actual: "falha de conex\xE3o",
|
|
449
|
+
resolution: "Verifique sua conex\xE3o com a internet ou acesso ao GitLab e tente novamente."
|
|
450
|
+
})
|
|
451
|
+
);
|
|
452
|
+
} else {
|
|
453
|
+
results.push(
|
|
454
|
+
check({
|
|
455
|
+
id: "registry.pulso-token",
|
|
456
|
+
category: "registry",
|
|
457
|
+
status: "error",
|
|
458
|
+
title: "Valida\xE7\xE3o do token do registry Pulso",
|
|
459
|
+
message: `O token do registry Pulso n\xE3o foi aceito pelo GitLab${verification.detail ? ` (${verification.detail})` : ""}.`,
|
|
460
|
+
actual: `n\xE3o autorizado (HTTP ${verification.httpStatus ?? 401})`,
|
|
461
|
+
resolution: pulsoVariableName ? `Gere um novo token no GitLab com escopo de leitura do registry e atualize a vari\xE1vel ${pulsoVariableName}.` : `Gere um novo token no GitLab com escopo de leitura do registry e atualize a configura\xE7\xE3o em ${source}.`
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
294
466
|
if (platforms.includes("app")) {
|
|
295
467
|
const svgRegistry = normalizeRegistry(entries.get("@rd-core-svg:registry"));
|
|
296
468
|
results.push(
|
|
@@ -312,13 +484,52 @@ async function validateRegistryConfiguration(cwd, platforms, environment) {
|
|
|
312
484
|
resolution: source === ".yarnrc.yml" ? `Defina npmScopes.rd-core-svg.npmRegistryServer=${RD_CORE_SVG_REGISTRY} no .yarnrc.yml.` : source === ".yarnrc" ? `Defina "@rd-core-svg:registry" "${RD_CORE_SVG_REGISTRY}" no .yarnrc.` : `Defina @rd-core-svg:registry=${RD_CORE_SVG_REGISTRY} no .npmrc.`
|
|
313
485
|
})
|
|
314
486
|
);
|
|
487
|
+
const svgAuthValue = entries.get(RD_CORE_SVG_AUTH_KEY);
|
|
488
|
+
if (svgAuthValue && shouldVerifyTokens) {
|
|
489
|
+
const rawSvgAuth = stripQuotes(svgAuthValue) ?? "";
|
|
490
|
+
const svgVarMatch = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(rawSvgAuth) ?? /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(rawSvgAuth);
|
|
491
|
+
const svgVarName = svgVarMatch?.[1];
|
|
492
|
+
const svgToken = svgVarName ? stripQuotes(environment[svgVarName]) : rawSvgAuth;
|
|
493
|
+
if (svgToken) {
|
|
494
|
+
const verifier = options.tokenVerifier ?? verifyRegistryToken;
|
|
495
|
+
const svgVerification = await verifier({
|
|
496
|
+
registry: svgRegistry ?? RD_CORE_SVG_REGISTRY,
|
|
497
|
+
token: svgToken,
|
|
498
|
+
fetchFn: options.fetchFn
|
|
499
|
+
});
|
|
500
|
+
if (svgVerification.status === "valid") {
|
|
501
|
+
results.push(
|
|
502
|
+
check({
|
|
503
|
+
id: "registry.rd-core-svg-token",
|
|
504
|
+
category: "registry",
|
|
505
|
+
status: "pass",
|
|
506
|
+
title: "Valida\xE7\xE3o do token do registry @rd-core-svg",
|
|
507
|
+
message: "Token de autentica\xE7\xE3o do registry @rd-core-svg validado com sucesso no GitLab.",
|
|
508
|
+
actual: "token v\xE1lido (HTTP 200)"
|
|
509
|
+
})
|
|
510
|
+
);
|
|
511
|
+
} else if (svgVerification.status === "expired" || svgVerification.status === "invalid") {
|
|
512
|
+
results.push(
|
|
513
|
+
check({
|
|
514
|
+
id: "registry.rd-core-svg-token",
|
|
515
|
+
category: "registry",
|
|
516
|
+
status: "error",
|
|
517
|
+
title: "Valida\xE7\xE3o do token do registry @rd-core-svg",
|
|
518
|
+
message: `O token do registry @rd-core-svg \xE9 inv\xE1lido ou est\xE1 expirado no GitLab${svgVerification.detail ? ` (${svgVerification.detail})` : ""}.`,
|
|
519
|
+
actual: `n\xE3o autorizado (HTTP ${svgVerification.httpStatus ?? 401})`,
|
|
520
|
+
resolution: svgVarName ? `Gere um novo token no GitLab e atualize a vari\xE1vel ${svgVarName}.` : `Gere um novo token no GitLab e atualize a configura\xE7\xE3o em ${source}.`
|
|
521
|
+
})
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
315
526
|
}
|
|
316
527
|
return { checks: results, configuration };
|
|
317
528
|
}
|
|
318
529
|
async function validateRegistryProtection(cwd, configuration) {
|
|
319
530
|
if (configuration?.source === ".yarnrc.yml" || configuration?.source === ".yarnrc") {
|
|
320
531
|
const source = configuration.source;
|
|
321
|
-
const authentication = configuration.entries.get(PULSO_AUTH_KEY);
|
|
532
|
+
const authentication = configuration.entries.get(PULSO_AUTH_KEY) ?? configuration.entries.get(INSTANCE_AUTH_KEY);
|
|
322
533
|
const usesEnvironment = /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(
|
|
323
534
|
authentication ?? ""
|
|
324
535
|
);
|
|
@@ -523,7 +734,12 @@ async function runDoctor(options = {}) {
|
|
|
523
734
|
const registry = await validateRegistryConfiguration(
|
|
524
735
|
cwd,
|
|
525
736
|
platforms,
|
|
526
|
-
environment
|
|
737
|
+
environment,
|
|
738
|
+
{
|
|
739
|
+
tokenVerifier: options.tokenVerifier,
|
|
740
|
+
skipTokenVerification: options.skipTokenVerification,
|
|
741
|
+
fetchFn: options.fetchFn
|
|
742
|
+
}
|
|
527
743
|
);
|
|
528
744
|
checks.push(...registry.checks);
|
|
529
745
|
checks.push(await validateRegistryProtection(cwd, registry.configuration));
|
|
@@ -989,7 +1205,7 @@ async function applyDoctorFixes(report, options = {}) {
|
|
|
989
1205
|
(item) => item.id === "registry.configuration" || isMissingAuthentication(item)
|
|
990
1206
|
);
|
|
991
1207
|
const authenticationError = report.checks.find(
|
|
992
|
-
(item) => item.id === "registry.pulso-auth" && item.status === "error"
|
|
1208
|
+
(item) => (item.id === "registry.pulso-auth" || item.id === "registry.pulso-token") && item.status === "error"
|
|
993
1209
|
);
|
|
994
1210
|
const credentialAvailable = Boolean(
|
|
995
1211
|
(options.environment ?? process.env).CI_JOB_TOKEN
|
|
@@ -1098,6 +1314,7 @@ function formatDoctorFixReport(report, options = {}) {
|
|
|
1098
1314
|
}
|
|
1099
1315
|
|
|
1100
1316
|
export {
|
|
1317
|
+
verifyRegistryToken,
|
|
1101
1318
|
runDoctor,
|
|
1102
1319
|
formatDoctorReport,
|
|
1103
1320
|
hasAutomaticFixes,
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
formatDoctorReport,
|
|
6
6
|
hasAutomaticFixes,
|
|
7
7
|
runDoctor
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-B4E4SOCQ.js";
|
|
9
9
|
|
|
10
10
|
// src/cli-core.ts
|
|
11
11
|
import { createInterface } from "readline/promises";
|
|
@@ -19,6 +19,7 @@ Op\xE7\xF5es:
|
|
|
19
19
|
--fix aplica corre\xE7\xF5es autom\xE1ticas seguras
|
|
20
20
|
--json imprime o relat\xF3rio estruturado em JSON
|
|
21
21
|
--log-file <arquivo> caminho do log JSON Lines
|
|
22
|
+
--skip-token-check n\xE3o realiza a valida\xE7\xE3o de rede do token
|
|
22
23
|
-h, --help exibe esta ajuda`;
|
|
23
24
|
function terminalSupportsColor() {
|
|
24
25
|
if (process.env.NO_COLOR !== void 0 || process.env.FORCE_COLOR === "0") {
|
|
@@ -47,12 +48,19 @@ function readValue(argv, index, flag) {
|
|
|
47
48
|
return value;
|
|
48
49
|
}
|
|
49
50
|
function parseCliArguments(argv) {
|
|
50
|
-
const parsed = {
|
|
51
|
+
const parsed = {
|
|
52
|
+
platform: "auto",
|
|
53
|
+
json: false,
|
|
54
|
+
fix: false,
|
|
55
|
+
help: false,
|
|
56
|
+
skipTokenCheck: false
|
|
57
|
+
};
|
|
51
58
|
for (let index = 0; index < argv.length; index += 1) {
|
|
52
59
|
const argument = argv[index];
|
|
53
60
|
if (argument === "-h" || argument === "--help") parsed.help = true;
|
|
54
61
|
else if (argument === "--json") parsed.json = true;
|
|
55
62
|
else if (argument === "--fix") parsed.fix = true;
|
|
63
|
+
else if (argument === "--skip-token-check") parsed.skipTokenCheck = true;
|
|
56
64
|
else if (argument === "--cwd") {
|
|
57
65
|
parsed.cwd = readValue(argv, index, argument);
|
|
58
66
|
index += 1;
|
|
@@ -92,7 +100,8 @@ async function runCli(argv = process.argv.slice(2), io = {
|
|
|
92
100
|
cwd: options.cwd,
|
|
93
101
|
platform: options.platform,
|
|
94
102
|
logFile: options.logFile,
|
|
95
|
-
writeLog: options.fix ? false : void 0
|
|
103
|
+
writeLog: options.fix ? false : void 0,
|
|
104
|
+
skipTokenVerification: options.skipTokenCheck
|
|
96
105
|
});
|
|
97
106
|
io.stdout(
|
|
98
107
|
options.json ? JSON.stringify(report, null, 2) : formatDoctorReport(report, { color: io.color ?? false })
|
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,19 @@ interface DoctorFixOptions {
|
|
|
72
72
|
commandRunner?: DoctorCommandRunner;
|
|
73
73
|
environment?: Readonly<Record<string, string | undefined>>;
|
|
74
74
|
}
|
|
75
|
+
type RegistryTokenVerificationStatus = 'valid' | 'expired' | 'invalid' | 'forbidden' | 'not-found' | 'network-error';
|
|
76
|
+
interface RegistryTokenVerificationResult {
|
|
77
|
+
ok: boolean;
|
|
78
|
+
status: RegistryTokenVerificationStatus;
|
|
79
|
+
httpStatus?: number;
|
|
80
|
+
detail?: string;
|
|
81
|
+
}
|
|
82
|
+
type RegistryTokenVerifier = (options: {
|
|
83
|
+
registry: string;
|
|
84
|
+
token: string;
|
|
85
|
+
fetchFn?: typeof fetch;
|
|
86
|
+
timeoutMs?: number;
|
|
87
|
+
}) => Promise<RegistryTokenVerificationResult>;
|
|
75
88
|
interface RunDoctorOptions {
|
|
76
89
|
cwd?: string;
|
|
77
90
|
platform?: DoctorPlatformOption;
|
|
@@ -80,8 +93,18 @@ interface RunDoctorOptions {
|
|
|
80
93
|
now?: Date;
|
|
81
94
|
nodeVersion?: string;
|
|
82
95
|
environment?: Readonly<Record<string, string | undefined>>;
|
|
96
|
+
tokenVerifier?: RegistryTokenVerifier;
|
|
97
|
+
fetchFn?: typeof fetch;
|
|
98
|
+
skipTokenVerification?: boolean;
|
|
83
99
|
}
|
|
84
100
|
|
|
101
|
+
declare function verifyRegistryToken(options: {
|
|
102
|
+
registry: string;
|
|
103
|
+
token: string;
|
|
104
|
+
packageName?: string;
|
|
105
|
+
fetchFn?: typeof fetch;
|
|
106
|
+
timeoutMs?: number;
|
|
107
|
+
}): Promise<RegistryTokenVerificationResult>;
|
|
85
108
|
declare function runDoctor(options?: RunDoctorOptions): Promise<DoctorReport>;
|
|
86
109
|
declare function formatDoctorReport(report: DoctorReport, options?: DoctorFormatOptions): string;
|
|
87
110
|
|
|
@@ -89,4 +112,4 @@ declare function hasAutomaticFixes(report: DoctorReport): boolean;
|
|
|
89
112
|
declare function applyDoctorFixes(report: DoctorReport, options?: DoctorFixOptions): Promise<DoctorFixReport>;
|
|
90
113
|
declare function formatDoctorFixReport(report: DoctorFixReport, options?: DoctorFormatOptions): string;
|
|
91
114
|
|
|
92
|
-
export { type DoctorCheckCategory, type DoctorCheckResult, type DoctorCheckStatus, type DoctorCommandRunner, type DoctorDependencyVersion, type DoctorFixAction, type DoctorFixOptions, type DoctorFixReport, type DoctorFixStatus, type DoctorFixSummary, type DoctorFormatOptions, type DoctorLogResult, type DoctorPlatform, type DoctorPlatformOption, type DoctorProject, type DoctorReport, type DoctorSummary, type RunDoctorOptions, applyDoctorFixes, formatDoctorFixReport, formatDoctorReport, hasAutomaticFixes, runDoctor };
|
|
115
|
+
export { type DoctorCheckCategory, type DoctorCheckResult, type DoctorCheckStatus, type DoctorCommandRunner, type DoctorDependencyVersion, type DoctorFixAction, type DoctorFixOptions, type DoctorFixReport, type DoctorFixStatus, type DoctorFixSummary, type DoctorFormatOptions, type DoctorLogResult, type DoctorPlatform, type DoctorPlatformOption, type DoctorProject, type DoctorReport, type DoctorSummary, type RegistryTokenVerificationResult, type RegistryTokenVerificationStatus, type RegistryTokenVerifier, type RunDoctorOptions, applyDoctorFixes, formatDoctorFixReport, formatDoctorReport, hasAutomaticFixes, runDoctor, verifyRegistryToken };
|
package/dist/index.js
CHANGED
|
@@ -3,12 +3,14 @@ import {
|
|
|
3
3
|
formatDoctorFixReport,
|
|
4
4
|
formatDoctorReport,
|
|
5
5
|
hasAutomaticFixes,
|
|
6
|
-
runDoctor
|
|
7
|
-
|
|
6
|
+
runDoctor,
|
|
7
|
+
verifyRegistryToken
|
|
8
|
+
} from "./chunk-B4E4SOCQ.js";
|
|
8
9
|
export {
|
|
9
10
|
applyDoctorFixes,
|
|
10
11
|
formatDoctorFixReport,
|
|
11
12
|
formatDoctorReport,
|
|
12
13
|
hasAutomaticFixes,
|
|
13
|
-
runDoctor
|
|
14
|
+
runDoctor,
|
|
15
|
+
verifyRegistryToken
|
|
14
16
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pulso-doctor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Diagnostico publico do ambiente de consumo das bibliotecas Pulso Web e App.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,11 +33,12 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsup && chmod 755 dist/cli.js",
|
|
35
35
|
"lint": "cd ../.. && biome check packages/doctor",
|
|
36
|
-
"test": "cd ../.. && vitest run packages/doctor/src",
|
|
36
|
+
"test": "cd ../.. && vitest run packages/doctor/src packages/doctor/scripts",
|
|
37
37
|
"test:package": "pnpm build && node scripts/verify-package.mjs",
|
|
38
|
-
"publish:stage": "npm
|
|
39
|
-
"publish:list": "npm
|
|
40
|
-
"publish:approve": "npm
|
|
38
|
+
"publish:stage": "npm stage publish",
|
|
39
|
+
"publish:list": "npm stage list pulso-doctor",
|
|
40
|
+
"publish:approve": "npm stage approve",
|
|
41
|
+
"publish:direct": "npm publish --access public"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"semver": "7.7.4",
|