redmine-context 1.0.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/LICENSE +21 -0
- package/README.md +449 -0
- package/dist/bundle/index.d.ts +5 -0
- package/dist/bundle/index.js +5 -0
- package/dist/bundle/json.d.ts +90 -0
- package/dist/bundle/json.js +266 -0
- package/dist/bundle/markdown.d.ts +75 -0
- package/dist/bundle/markdown.js +294 -0
- package/dist/bundle/search-list.d.ts +43 -0
- package/dist/bundle/search-list.js +53 -0
- package/dist/bundle/stable-stringify.d.ts +26 -0
- package/dist/bundle/stable-stringify.js +50 -0
- package/dist/cache/contract.d.ts +157 -0
- package/dist/cache/contract.js +0 -0
- package/dist/cache/disk-index.d.ts +82 -0
- package/dist/cache/disk-index.js +220 -0
- package/dist/cache/disk.d.ts +133 -0
- package/dist/cache/disk.js +313 -0
- package/dist/cache/gc.d.ts +78 -0
- package/dist/cache/gc.js +123 -0
- package/dist/cache/get-or-compute.d.ts +36 -0
- package/dist/cache/get-or-compute.js +52 -0
- package/dist/cache/index.d.ts +9 -0
- package/dist/cache/index.js +8 -0
- package/dist/cache/keys.d.ts +76 -0
- package/dist/cache/keys.js +78 -0
- package/dist/cache/memory.d.ts +48 -0
- package/dist/cache/memory.js +110 -0
- package/dist/cache-first.d.ts +127 -0
- package/dist/cache-first.js +227 -0
- package/dist/client/errors.d.ts +33 -0
- package/dist/client/errors.js +49 -0
- package/dist/client/http.d.ts +110 -0
- package/dist/client/http.js +207 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +5 -0
- package/dist/client/issues.d.ts +71 -0
- package/dist/client/issues.js +100 -0
- package/dist/client/search.d.ts +58 -0
- package/dist/client/search.js +81 -0
- package/dist/config/credentials.d.ts +247 -0
- package/dist/config/credentials.js +427 -0
- package/dist/config/doctor.d.ts +123 -0
- package/dist/config/doctor.js +260 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.js +6 -0
- package/dist/config/keyring.d.ts +96 -0
- package/dist/config/keyring.js +158 -0
- package/dist/config/login.d.ts +97 -0
- package/dist/config/login.js +189 -0
- package/dist/config/settings.d.ts +94 -0
- package/dist/config/settings.js +140 -0
- package/dist/contract.d.ts +173 -0
- package/dist/contract.js +27 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +8 -0
- package/dist/extract/audio-extractor.d.ts +105 -0
- package/dist/extract/audio-extractor.js +156 -0
- package/dist/extract/audio.d.ts +126 -0
- package/dist/extract/audio.js +184 -0
- package/dist/extract/dispatcher.d.ts +132 -0
- package/dist/extract/dispatcher.js +115 -0
- package/dist/extract/download.d.ts +111 -0
- package/dist/extract/download.js +261 -0
- package/dist/extract/duration.d.ts +106 -0
- package/dist/extract/duration.js +148 -0
- package/dist/extract/ffmpeg.d.ts +56 -0
- package/dist/extract/ffmpeg.js +95 -0
- package/dist/extract/gguf.d.ts +137 -0
- package/dist/extract/gguf.js +215 -0
- package/dist/extract/index.d.ts +19 -0
- package/dist/extract/index.js +19 -0
- package/dist/extract/magic.d.ts +80 -0
- package/dist/extract/magic.js +282 -0
- package/dist/extract/ooxml.d.ts +131 -0
- package/dist/extract/ooxml.js +336 -0
- package/dist/extract/pdf.d.ts +147 -0
- package/dist/extract/pdf.js +322 -0
- package/dist/extract/queue.d.ts +167 -0
- package/dist/extract/queue.js +217 -0
- package/dist/extract/subprocess.d.ts +145 -0
- package/dist/extract/subprocess.js +181 -0
- package/dist/extract/tesseract.d.ts +153 -0
- package/dist/extract/tesseract.js +321 -0
- package/dist/extract/video-extractor.d.ts +84 -0
- package/dist/extract/video-extractor.js +89 -0
- package/dist/extract/video.d.ts +198 -0
- package/dist/extract/video.js +418 -0
- package/dist/extract/which.d.ts +63 -0
- package/dist/extract/which.js +72 -0
- package/dist/extract/whisper-extract.d.ts +211 -0
- package/dist/extract/whisper-extract.js +323 -0
- package/dist/extract/whisper.d.ts +50 -0
- package/dist/extract/whisper.js +67 -0
- package/dist/extract/zip.d.ts +50 -0
- package/dist/extract/zip.js +165 -0
- package/dist/extract-issue-attachments.d.ts +82 -0
- package/dist/extract-issue-attachments.js +156 -0
- package/dist/fetch-attachment-text.d.ts +113 -0
- package/dist/fetch-attachment-text.js +155 -0
- package/dist/fetch-issue-bundle.d.ts +81 -0
- package/dist/fetch-issue-bundle.js +93 -0
- package/dist/fetch-issue-search.d.ts +74 -0
- package/dist/fetch-issue-search.js +120 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +67 -0
- package/dist/normalize/collections.d.ts +52 -0
- package/dist/normalize/collections.js +170 -0
- package/dist/normalize/helpers.d.ts +35 -0
- package/dist/normalize/helpers.js +59 -0
- package/dist/normalize/index.d.ts +2 -0
- package/dist/normalize/index.js +2 -0
- package/dist/normalize/issue.d.ts +34 -0
- package/dist/normalize/issue.js +154 -0
- package/dist/surfaces/cli/commands.d.ts +61 -0
- package/dist/surfaces/cli/commands.js +262 -0
- package/dist/surfaces/cli/main.d.ts +32 -0
- package/dist/surfaces/cli/main.js +210 -0
- package/dist/surfaces/cli/prompts.d.ts +66 -0
- package/dist/surfaces/cli/prompts.js +147 -0
- package/dist/surfaces/cli/tty.d.ts +27 -0
- package/dist/surfaces/cli/tty.js +35 -0
- package/dist/surfaces/cli/types.d.ts +39 -0
- package/dist/surfaces/cli/types.js +7 -0
- package/dist/surfaces/mcp/server.d.ts +171 -0
- package/dist/surfaces/mcp/server.js +427 -0
- package/dist/surfaces/tui/app.d.ts +55 -0
- package/dist/surfaces/tui/app.js +180 -0
- package/dist/surfaces/tui/attachment-status.d.ts +79 -0
- package/dist/surfaces/tui/attachment-status.js +113 -0
- package/dist/surfaces/tui/components/breadcrumb.d.ts +7 -0
- package/dist/surfaces/tui/components/breadcrumb.js +31 -0
- package/dist/surfaces/tui/components/gradient-text.d.ts +23 -0
- package/dist/surfaces/tui/components/gradient-text.js +75 -0
- package/dist/surfaces/tui/components/scroll-view.d.ts +25 -0
- package/dist/surfaces/tui/components/scroll-view.js +77 -0
- package/dist/surfaces/tui/components/spinner.d.ts +12 -0
- package/dist/surfaces/tui/components/spinner.js +39 -0
- package/dist/surfaces/tui/components/text-input.d.ts +45 -0
- package/dist/surfaces/tui/components/text-input.js +114 -0
- package/dist/surfaces/tui/format-file-size.d.ts +24 -0
- package/dist/surfaces/tui/format-file-size.js +43 -0
- package/dist/surfaces/tui/glyphs.d.ts +47 -0
- package/dist/surfaces/tui/glyphs.js +84 -0
- package/dist/surfaces/tui/hooks/use-auth-guard.d.ts +39 -0
- package/dist/surfaces/tui/hooks/use-auth-guard.js +135 -0
- package/dist/surfaces/tui/hooks/use-doctor-status.d.ts +64 -0
- package/dist/surfaces/tui/hooks/use-doctor-status.js +123 -0
- package/dist/surfaces/tui/hooks/use-escape-interceptor.d.ts +25 -0
- package/dist/surfaces/tui/hooks/use-escape-interceptor.js +65 -0
- package/dist/surfaces/tui/hooks/use-exit-guard.d.ts +18 -0
- package/dist/surfaces/tui/hooks/use-exit-guard.js +66 -0
- package/dist/surfaces/tui/hooks/use-export-bundle.d.ts +62 -0
- package/dist/surfaces/tui/hooks/use-export-bundle.js +100 -0
- package/dist/surfaces/tui/hooks/use-issue-detail.d.ts +61 -0
- package/dist/surfaces/tui/hooks/use-issue-detail.js +132 -0
- package/dist/surfaces/tui/hooks/use-issue-search.d.ts +71 -0
- package/dist/surfaces/tui/hooks/use-issue-search.js +168 -0
- package/dist/surfaces/tui/hooks/use-list-navigation.d.ts +44 -0
- package/dist/surfaces/tui/hooks/use-list-navigation.js +82 -0
- package/dist/surfaces/tui/hooks/use-media-binaries.d.ts +24 -0
- package/dist/surfaces/tui/hooks/use-media-binaries.js +44 -0
- package/dist/surfaces/tui/hooks/use-my-issues.d.ts +66 -0
- package/dist/surfaces/tui/hooks/use-my-issues.js +151 -0
- package/dist/surfaces/tui/hooks/use-onboarding-callbacks.d.ts +11 -0
- package/dist/surfaces/tui/hooks/use-onboarding-callbacks.js +104 -0
- package/dist/surfaces/tui/hooks/use-terminal-width.d.ts +52 -0
- package/dist/surfaces/tui/hooks/use-terminal-width.js +90 -0
- package/dist/surfaces/tui/index.d.ts +50 -0
- package/dist/surfaces/tui/index.js +158 -0
- package/dist/surfaces/tui/instance.d.ts +37 -0
- package/dist/surfaces/tui/instance.js +36 -0
- package/dist/surfaces/tui/job-registry.d.ts +113 -0
- package/dist/surfaces/tui/job-registry.js +123 -0
- package/dist/surfaces/tui/job-status.d.ts +45 -0
- package/dist/surfaces/tui/job-status.js +81 -0
- package/dist/surfaces/tui/navigation.d.ts +74 -0
- package/dist/surfaces/tui/navigation.js +87 -0
- package/dist/surfaces/tui/palettes.d.ts +38 -0
- package/dist/surfaces/tui/palettes.js +244 -0
- package/dist/surfaces/tui/screen.d.ts +30 -0
- package/dist/surfaces/tui/screen.js +51 -0
- package/dist/surfaces/tui/screens/about.d.ts +2 -0
- package/dist/surfaces/tui/screens/about.js +35 -0
- package/dist/surfaces/tui/screens/appearance.d.ts +2 -0
- package/dist/surfaces/tui/screens/appearance.js +74 -0
- package/dist/surfaces/tui/screens/config.d.ts +2 -0
- package/dist/surfaces/tui/screens/config.js +82 -0
- package/dist/surfaces/tui/screens/doctor.d.ts +2 -0
- package/dist/surfaces/tui/screens/doctor.js +109 -0
- package/dist/surfaces/tui/screens/export.d.ts +2 -0
- package/dist/surfaces/tui/screens/export.js +168 -0
- package/dist/surfaces/tui/screens/home-selection.d.ts +70 -0
- package/dist/surfaces/tui/screens/home-selection.js +80 -0
- package/dist/surfaces/tui/screens/home.d.ts +2 -0
- package/dist/surfaces/tui/screens/home.js +200 -0
- package/dist/surfaces/tui/screens/issue-detail.d.ts +6 -0
- package/dist/surfaces/tui/screens/issue-detail.js +182 -0
- package/dist/surfaces/tui/screens/jobs.d.ts +7 -0
- package/dist/surfaces/tui/screens/jobs.js +89 -0
- package/dist/surfaces/tui/screens/loaded-issue-context.d.ts +49 -0
- package/dist/surfaces/tui/screens/loaded-issue-context.js +57 -0
- package/dist/surfaces/tui/screens/onboarding/api-key.d.ts +2 -0
- package/dist/surfaces/tui/screens/onboarding/api-key.js +69 -0
- package/dist/surfaces/tui/screens/onboarding/login.d.ts +2 -0
- package/dist/surfaces/tui/screens/onboarding/login.js +50 -0
- package/dist/surfaces/tui/screens/onboarding/mode.d.ts +2 -0
- package/dist/surfaces/tui/screens/onboarding/mode.js +42 -0
- package/dist/surfaces/tui/screens/onboarding/onboarding-context.d.ts +221 -0
- package/dist/surfaces/tui/screens/onboarding/onboarding-context.js +131 -0
- package/dist/surfaces/tui/screens/onboarding/success.d.ts +2 -0
- package/dist/surfaces/tui/screens/onboarding/success.js +41 -0
- package/dist/surfaces/tui/screens/onboarding/url.d.ts +24 -0
- package/dist/surfaces/tui/screens/onboarding/url.js +84 -0
- package/dist/surfaces/tui/screens/onboarding/validating.d.ts +1 -0
- package/dist/surfaces/tui/screens/onboarding/validating.js +86 -0
- package/dist/surfaces/tui/screens/welcome.d.ts +6 -0
- package/dist/surfaces/tui/screens/welcome.js +95 -0
- package/dist/surfaces/tui/status-color.d.ts +21 -0
- package/dist/surfaces/tui/status-color.js +23 -0
- package/dist/surfaces/tui/symbols.d.ts +231 -0
- package/dist/surfaces/tui/symbols.js +14 -0
- package/dist/surfaces/tui/terminal-colors.d.ts +29 -0
- package/dist/surfaces/tui/terminal-colors.js +39 -0
- package/dist/surfaces/tui/theme.d.ts +156 -0
- package/dist/surfaces/tui/theme.js +86 -0
- package/dist/surfaces/tui/truncate.d.ts +31 -0
- package/dist/surfaces/tui/truncate.js +81 -0
- package/package.json +93 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operações de leitura de issues sobre o {@link HttpClient} do M1.
|
|
3
|
+
*
|
|
4
|
+
* Escopo (issue #9): apenas `getIssue` e `listIssues`. Sem retry/backoff
|
|
5
|
+
* (issue #10) e SEM normalização (issue #11) — o payload é devolvido bruto,
|
|
6
|
+
* tipado de forma rasa. A normalização para o modelo {@link Issue} do contrato
|
|
7
|
+
* acontece em outra camada.
|
|
8
|
+
*/
|
|
9
|
+
import type { HttpClient, QueryParams } from './http.js';
|
|
10
|
+
/**
|
|
11
|
+
* Payload bruto de uma issue, exatamente como a API do Redmine devolve.
|
|
12
|
+
* Tipo raso e intencionalmente permissivo: só garante `id`; os demais campos
|
|
13
|
+
* (subject, journals, attachments, custom_fields, ...) ficam sob a assinatura
|
|
14
|
+
* de índice e serão interpretados na normalização (issue #11).
|
|
15
|
+
*/
|
|
16
|
+
export interface RedmineIssuePayload {
|
|
17
|
+
id: number;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
/** Opções de {@link listIssues}: filtros de query + controle de paginação. */
|
|
21
|
+
export interface ListIssuesOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Filtros repassados ao endpoint `/issues.json` (ex.: `{ project_id: 5,
|
|
24
|
+
* status_id: 'open' }`). São enviados junto de `offset`/`limit` em cada página.
|
|
25
|
+
*/
|
|
26
|
+
filters?: QueryParams;
|
|
27
|
+
/**
|
|
28
|
+
* Itens por página (parâmetro `limit`). Default e teto: {@link REDMINE_MAX_LIMIT}.
|
|
29
|
+
* Valores acima do teto são reduzidos silenciosamente para 100.
|
|
30
|
+
*/
|
|
31
|
+
pageSize?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Máximo de itens a acumular no total. Ausente = esgota toda a coleção
|
|
34
|
+
* (respeitando `total_count`). Também reduz o `limit` da última página para
|
|
35
|
+
* não buscar além do necessário.
|
|
36
|
+
*/
|
|
37
|
+
maxItems?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Busca o detalhe completo de uma issue com todas as coleções aninhadas.
|
|
41
|
+
*
|
|
42
|
+
* Usa `include=journals,attachments,relations,children` e devolve o payload
|
|
43
|
+
* BRUTO (sem normalizar). A resposta do Redmine tem o formato `{ issue: {...} }`;
|
|
44
|
+
* o objeto interno é retornado tipado como {@link RedmineIssuePayload}.
|
|
45
|
+
*
|
|
46
|
+
* @param http - Client HTTP autenticado (ver {@link HttpClient}).
|
|
47
|
+
* @param id - Identificador da issue.
|
|
48
|
+
* @returns O payload bruto da issue.
|
|
49
|
+
* @throws {Error} Se a resposta não contiver a chave `issue`.
|
|
50
|
+
* @throws {RedmineHttpError} Propagado pelo client em respostas ≥ 400.
|
|
51
|
+
* @example
|
|
52
|
+
* const issue = await getIssue(http, 100);
|
|
53
|
+
*/
|
|
54
|
+
export declare function getIssue(http: HttpClient, id: number): Promise<RedmineIssuePayload>;
|
|
55
|
+
/**
|
|
56
|
+
* Lista issues paginando `/issues.json` via `offset`/`limit` até esgotar a
|
|
57
|
+
* coleção (respeitando `total_count`) ou atingir `maxItems`.
|
|
58
|
+
*
|
|
59
|
+
* Cada página respeita o teto de {@link REDMINE_MAX_LIMIT} do Redmine. Quando
|
|
60
|
+
* `maxItems` está definido, o `limit` da última página é reduzido para não
|
|
61
|
+
* buscar itens além do necessário. Sem retry (issue #10).
|
|
62
|
+
*
|
|
63
|
+
* @param http - Client HTTP autenticado (ver {@link HttpClient}).
|
|
64
|
+
* @param options - Filtros e controle de paginação (ver {@link ListIssuesOptions}).
|
|
65
|
+
* @returns Array acumulado com os payloads brutos das issues.
|
|
66
|
+
* @throws {Error} Se alguma página não trouxer o array `issues`.
|
|
67
|
+
* @throws {RedmineHttpError} Propagado pelo client em respostas ≥ 400.
|
|
68
|
+
* @example
|
|
69
|
+
* const all = await listIssues(http, { filters: { project_id: 5 }, maxItems: 500 });
|
|
70
|
+
*/
|
|
71
|
+
export declare function listIssues(http: HttpClient, options?: ListIssuesOptions): Promise<RedmineIssuePayload[]>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operações de leitura de issues sobre o {@link HttpClient} do M1.
|
|
3
|
+
*
|
|
4
|
+
* Escopo (issue #9): apenas `getIssue` e `listIssues`. Sem retry/backoff
|
|
5
|
+
* (issue #10) e SEM normalização (issue #11) — o payload é devolvido bruto,
|
|
6
|
+
* tipado de forma rasa. A normalização para o modelo {@link Issue} do contrato
|
|
7
|
+
* acontece em outra camada.
|
|
8
|
+
*/
|
|
9
|
+
/** Include padrão do detalhe de issue (ADR-005): histórico e coleções aninhadas. */
|
|
10
|
+
const ISSUE_INCLUDE = 'journals,attachments,relations,children';
|
|
11
|
+
/** Teto de itens por página imposto pelo Redmine ao parâmetro `limit`. */
|
|
12
|
+
const REDMINE_MAX_LIMIT = 100;
|
|
13
|
+
/**
|
|
14
|
+
* Converte um valor desconhecido em `Record<string, unknown>` para acesso seguro.
|
|
15
|
+
*
|
|
16
|
+
* @param value - Valor a inspecionar.
|
|
17
|
+
* @returns O objeto tipado, ou `undefined` se não for um objeto simples.
|
|
18
|
+
*/
|
|
19
|
+
function asRecord(value) {
|
|
20
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Busca o detalhe completo de uma issue com todas as coleções aninhadas.
|
|
27
|
+
*
|
|
28
|
+
* Usa `include=journals,attachments,relations,children` e devolve o payload
|
|
29
|
+
* BRUTO (sem normalizar). A resposta do Redmine tem o formato `{ issue: {...} }`;
|
|
30
|
+
* o objeto interno é retornado tipado como {@link RedmineIssuePayload}.
|
|
31
|
+
*
|
|
32
|
+
* @param http - Client HTTP autenticado (ver {@link HttpClient}).
|
|
33
|
+
* @param id - Identificador da issue.
|
|
34
|
+
* @returns O payload bruto da issue.
|
|
35
|
+
* @throws {Error} Se a resposta não contiver a chave `issue`.
|
|
36
|
+
* @throws {RedmineHttpError} Propagado pelo client em respostas ≥ 400.
|
|
37
|
+
* @example
|
|
38
|
+
* const issue = await getIssue(http, 100);
|
|
39
|
+
*/
|
|
40
|
+
export async function getIssue(http, id) {
|
|
41
|
+
const raw = await http.get(`/issues/${id}.json`, { include: ISSUE_INCLUDE });
|
|
42
|
+
const body = asRecord(raw);
|
|
43
|
+
const issue = body === undefined ? undefined : asRecord(body.issue);
|
|
44
|
+
if (issue === undefined) {
|
|
45
|
+
throw new Error(`Resposta de /issues/${id}.json não contém a chave "issue".`);
|
|
46
|
+
}
|
|
47
|
+
return issue;
|
|
48
|
+
}
|
|
49
|
+
/** Extrai `issues[]` e `total_count` de uma página de `/issues.json`. */
|
|
50
|
+
function parsePage(raw) {
|
|
51
|
+
const body = asRecord(raw);
|
|
52
|
+
const issues = body?.issues;
|
|
53
|
+
if (!Array.isArray(issues)) {
|
|
54
|
+
throw new Error('Resposta de /issues.json não contém o array "issues".');
|
|
55
|
+
}
|
|
56
|
+
// total_count pode faltar em respostas inesperadas: assume o tamanho da página.
|
|
57
|
+
const totalCount = typeof body?.total_count === 'number' ? body.total_count : issues.length;
|
|
58
|
+
return { issues: issues, totalCount };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Lista issues paginando `/issues.json` via `offset`/`limit` até esgotar a
|
|
62
|
+
* coleção (respeitando `total_count`) ou atingir `maxItems`.
|
|
63
|
+
*
|
|
64
|
+
* Cada página respeita o teto de {@link REDMINE_MAX_LIMIT} do Redmine. Quando
|
|
65
|
+
* `maxItems` está definido, o `limit` da última página é reduzido para não
|
|
66
|
+
* buscar itens além do necessário. Sem retry (issue #10).
|
|
67
|
+
*
|
|
68
|
+
* @param http - Client HTTP autenticado (ver {@link HttpClient}).
|
|
69
|
+
* @param options - Filtros e controle de paginação (ver {@link ListIssuesOptions}).
|
|
70
|
+
* @returns Array acumulado com os payloads brutos das issues.
|
|
71
|
+
* @throws {Error} Se alguma página não trouxer o array `issues`.
|
|
72
|
+
* @throws {RedmineHttpError} Propagado pelo client em respostas ≥ 400.
|
|
73
|
+
* @example
|
|
74
|
+
* const all = await listIssues(http, { filters: { project_id: 5 }, maxItems: 500 });
|
|
75
|
+
*/
|
|
76
|
+
export async function listIssues(http, options = {}) {
|
|
77
|
+
const { filters = {}, maxItems } = options;
|
|
78
|
+
const pageSize = Math.min(options.pageSize ?? REDMINE_MAX_LIMIT, REDMINE_MAX_LIMIT);
|
|
79
|
+
const acc = [];
|
|
80
|
+
let offset = 0;
|
|
81
|
+
for (;;) {
|
|
82
|
+
// Reduz o limit da última página quando maxItems se aproxima do total.
|
|
83
|
+
const remaining = maxItems === undefined ? pageSize : Math.min(pageSize, maxItems - acc.length);
|
|
84
|
+
if (remaining <= 0) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const params = { ...filters, offset, limit: remaining };
|
|
88
|
+
const { issues, totalCount } = parsePage(await http.get('/issues.json', params));
|
|
89
|
+
acc.push(...issues);
|
|
90
|
+
offset += issues.length;
|
|
91
|
+
// Para em página vazia (guarda contra loop infinito) ou ao esgotar a coleção.
|
|
92
|
+
if (issues.length === 0 || offset >= totalCount) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
if (maxItems !== undefined && acc.length >= maxItems) {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return acc;
|
|
100
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Busca full-text de issues via `/search.json` (M1-13).
|
|
3
|
+
*
|
|
4
|
+
* Camada baixa sobre o {@link HttpClient} do M1: dispara `GET /search.json` com o
|
|
5
|
+
* termo `q` restrito a issues (`issues=1`) e devolve os acertos de forma rasa
|
|
6
|
+
* (id/título/tipo/url), SEM normalizar. É best-effort por natureza — um Redmine
|
|
7
|
+
* sem o módulo de busca responde 404; o orquestrador ({@link fetchIssueSearch})
|
|
8
|
+
* captura a falha e degrada para os filtros estruturados.
|
|
9
|
+
*
|
|
10
|
+
* Nunca recebe URL/host: o {@link HttpClient} já carrega a instância/credencial.
|
|
11
|
+
*/
|
|
12
|
+
import type { HttpClient } from './http.js';
|
|
13
|
+
/** Opções da busca full-text sobre `/search.json`. */
|
|
14
|
+
export interface SearchIssuesOptions {
|
|
15
|
+
/** Termo de busca (`q`). Deve ser não vazio (validado na superfície). */
|
|
16
|
+
query: string;
|
|
17
|
+
/** Deslocamento de paginação (`offset`). Default: `0`. */
|
|
18
|
+
offset?: number;
|
|
19
|
+
/** Itens por página (`limit`). Default e teto: {@link SEARCH_MAX_LIMIT}. */
|
|
20
|
+
limit?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Acerto raso de `/search.json` já filtrado para o tipo `issue`. */
|
|
23
|
+
export interface RedmineSearchHit {
|
|
24
|
+
/** Id da issue correspondente ao acerto. */
|
|
25
|
+
id: number;
|
|
26
|
+
/** Título do acerto como a API devolve (ex.: `Bug #12: assunto`). */
|
|
27
|
+
title: string;
|
|
28
|
+
/** Tipo do acerto (`issue`, `issue-closed`, ...). */
|
|
29
|
+
type: string;
|
|
30
|
+
/** URL do acerto na instância Redmine. */
|
|
31
|
+
url: string;
|
|
32
|
+
}
|
|
33
|
+
/** Página de resultados de `/search.json`: acertos + total reportado. */
|
|
34
|
+
export interface SearchIssuesPage {
|
|
35
|
+
/** Acertos do tipo issue, na ordem devolvida pela API. */
|
|
36
|
+
hits: RedmineSearchHit[];
|
|
37
|
+
/** `total_count` reportado (ou o tamanho da página se ausente). */
|
|
38
|
+
totalCount: number;
|
|
39
|
+
}
|
|
40
|
+
/** Teto de itens por página imposto ao parâmetro `limit` de `/search.json`. */
|
|
41
|
+
export declare const SEARCH_MAX_LIMIT = 100;
|
|
42
|
+
/**
|
|
43
|
+
* Busca issues por texto livre em `/search.json`, restrito a issues.
|
|
44
|
+
*
|
|
45
|
+
* Devolve apenas os acertos do tipo issue (ids reutilizáveis como filtro
|
|
46
|
+
* `issue_id` em `/issues.json`). Não retenta e não normaliza — propaga os erros
|
|
47
|
+
* tipados do {@link HttpClient} (ex.: 404 quando a busca está indisponível) para
|
|
48
|
+
* o chamador decidir a degradação.
|
|
49
|
+
*
|
|
50
|
+
* @param http - Client HTTP autenticado (ver {@link HttpClient}).
|
|
51
|
+
* @param options - Termo e paginação (ver {@link SearchIssuesOptions}).
|
|
52
|
+
* @returns Página com os acertos de issue e o `total_count` reportado.
|
|
53
|
+
* @throws {Error} Se a resposta não contiver o array `results`.
|
|
54
|
+
* @throws {RedmineHttpError} Propagado pelo client em respostas ≥ 400.
|
|
55
|
+
* @example
|
|
56
|
+
* const page = await searchIssues(http, { query: 'timeout', limit: 25 });
|
|
57
|
+
*/
|
|
58
|
+
export declare function searchIssues(http: HttpClient, options: SearchIssuesOptions): Promise<SearchIssuesPage>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Busca full-text de issues via `/search.json` (M1-13).
|
|
3
|
+
*
|
|
4
|
+
* Camada baixa sobre o {@link HttpClient} do M1: dispara `GET /search.json` com o
|
|
5
|
+
* termo `q` restrito a issues (`issues=1`) e devolve os acertos de forma rasa
|
|
6
|
+
* (id/título/tipo/url), SEM normalizar. É best-effort por natureza — um Redmine
|
|
7
|
+
* sem o módulo de busca responde 404; o orquestrador ({@link fetchIssueSearch})
|
|
8
|
+
* captura a falha e degrada para os filtros estruturados.
|
|
9
|
+
*
|
|
10
|
+
* Nunca recebe URL/host: o {@link HttpClient} já carrega a instância/credencial.
|
|
11
|
+
*/
|
|
12
|
+
/** Teto de itens por página imposto ao parâmetro `limit` de `/search.json`. */
|
|
13
|
+
export const SEARCH_MAX_LIMIT = 100;
|
|
14
|
+
/**
|
|
15
|
+
* Converte um valor desconhecido em `Record<string, unknown>` para acesso seguro.
|
|
16
|
+
*
|
|
17
|
+
* @param value - Valor a inspecionar.
|
|
18
|
+
* @returns O objeto tipado, ou `undefined` se não for um objeto simples.
|
|
19
|
+
*/
|
|
20
|
+
function asRecord(value) {
|
|
21
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Extrai um acerto do array `results`, aceitando apenas os do tipo `issue*`. */
|
|
27
|
+
function parseHit(entry) {
|
|
28
|
+
const rec = asRecord(entry);
|
|
29
|
+
if (rec === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const { id, type } = rec;
|
|
32
|
+
// Reason: o /search.json mistura tipos (issue, wiki-page, ...); só issues nos
|
|
33
|
+
// interessam. Redmine varia entre `issue` e `issue-closed` — casamos o prefixo.
|
|
34
|
+
if (typeof id !== 'number' || typeof type !== 'string' || !type.startsWith('issue')) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
type,
|
|
40
|
+
title: typeof rec.title === 'string' ? rec.title : '',
|
|
41
|
+
url: typeof rec.url === 'string' ? rec.url : '',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Busca issues por texto livre em `/search.json`, restrito a issues.
|
|
46
|
+
*
|
|
47
|
+
* Devolve apenas os acertos do tipo issue (ids reutilizáveis como filtro
|
|
48
|
+
* `issue_id` em `/issues.json`). Não retenta e não normaliza — propaga os erros
|
|
49
|
+
* tipados do {@link HttpClient} (ex.: 404 quando a busca está indisponível) para
|
|
50
|
+
* o chamador decidir a degradação.
|
|
51
|
+
*
|
|
52
|
+
* @param http - Client HTTP autenticado (ver {@link HttpClient}).
|
|
53
|
+
* @param options - Termo e paginação (ver {@link SearchIssuesOptions}).
|
|
54
|
+
* @returns Página com os acertos de issue e o `total_count` reportado.
|
|
55
|
+
* @throws {Error} Se a resposta não contiver o array `results`.
|
|
56
|
+
* @throws {RedmineHttpError} Propagado pelo client em respostas ≥ 400.
|
|
57
|
+
* @example
|
|
58
|
+
* const page = await searchIssues(http, { query: 'timeout', limit: 25 });
|
|
59
|
+
*/
|
|
60
|
+
export async function searchIssues(http, options) {
|
|
61
|
+
const limit = Math.min(options.limit ?? SEARCH_MAX_LIMIT, SEARCH_MAX_LIMIT);
|
|
62
|
+
const params = {
|
|
63
|
+
q: options.query,
|
|
64
|
+
issues: 1,
|
|
65
|
+
limit,
|
|
66
|
+
offset: options.offset ?? 0,
|
|
67
|
+
};
|
|
68
|
+
const body = asRecord(await http.get('/search.json', params));
|
|
69
|
+
const results = body?.results;
|
|
70
|
+
if (!Array.isArray(results)) {
|
|
71
|
+
throw new Error('Resposta de /search.json não contém o array "results".');
|
|
72
|
+
}
|
|
73
|
+
const hits = [];
|
|
74
|
+
for (const entry of results) {
|
|
75
|
+
const hit = parseHit(entry);
|
|
76
|
+
if (hit !== undefined)
|
|
77
|
+
hits.push(hit);
|
|
78
|
+
}
|
|
79
|
+
const totalCount = typeof body?.total_count === 'number' ? body.total_count : hits.length;
|
|
80
|
+
return { hits, totalCount };
|
|
81
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential store da api_key por instância (M1-08).
|
|
3
|
+
*
|
|
4
|
+
* Persiste a api_key em um arquivo JSON `{ "<instance>": { "apiKey": "..." } }`
|
|
5
|
+
* com permissão `0600` dentro de um diretório `0700` — uma entrada por URL base
|
|
6
|
+
* normalizada, suportando múltiplas instâncias Redmine. O caminho padrão vem de
|
|
7
|
+
* `env-paths` (diretório de config do usuário).
|
|
8
|
+
*
|
|
9
|
+
* A resolução segue a cascata do ADR-003: keychain → arquivo → variável de
|
|
10
|
+
* ambiente `REDMINE_API_KEY` (o env é sempre aceito, para CI/MCP headless). A
|
|
11
|
+
* interface {@link CredentialStore} (get/set/delete) é comum a todas as
|
|
12
|
+
* implementações; a {@link MigratingCredentialCascade} as consulta em ordem e,
|
|
13
|
+
* quando o keychain está disponível, migra a chave do arquivo para ele.
|
|
14
|
+
*
|
|
15
|
+
* A api_key nunca é logada e nunca aparece em mensagens de erro — os erros de
|
|
16
|
+
* permissão carregam apenas caminhos e o comando `chmod` a ser executado. A
|
|
17
|
+
* verificação de permissão POSIX fica atrás de `process.platform !== 'win32'`
|
|
18
|
+
* porque o Windows não tem o mesmo modelo de bits `0600` (o hardening real do
|
|
19
|
+
* Windows é o M5-08).
|
|
20
|
+
*/
|
|
21
|
+
import type { Logger } from '../client/index.js';
|
|
22
|
+
import { KeyringCredentialStore, type KeyringModuleLoader } from './keyring.js';
|
|
23
|
+
/**
|
|
24
|
+
* Contrato de armazenamento de api_key por instância. A implementação de
|
|
25
|
+
* arquivo (M1) e a de keychain (M2) satisfazem esta interface, o que permite à
|
|
26
|
+
* cascata tratá-las de forma uniforme.
|
|
27
|
+
*/
|
|
28
|
+
export interface CredentialStore {
|
|
29
|
+
/**
|
|
30
|
+
* Recupera a api_key da instância, ou `undefined` se não houver.
|
|
31
|
+
* @param instance - URL base da instância (será normalizada).
|
|
32
|
+
*/
|
|
33
|
+
get(instance: string): Promise<string | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Persiste (ou substitui) a api_key da instância.
|
|
36
|
+
* @param instance - URL base da instância (será normalizada).
|
|
37
|
+
* @param apiKey - api_key a armazenar.
|
|
38
|
+
*/
|
|
39
|
+
set(instance: string, apiKey: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Remove a api_key da instância; no-op se não existir.
|
|
42
|
+
* @param instance - URL base da instância (será normalizada).
|
|
43
|
+
*/
|
|
44
|
+
delete(instance: string): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/** Erro do credential store — sempre construído sem expor a api_key. */
|
|
47
|
+
export declare class CredentialStoreError extends Error {
|
|
48
|
+
constructor(message: string);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Normaliza a URL base de uma instância para uma chave estável.
|
|
52
|
+
*
|
|
53
|
+
* Aplica minúsculas (protocolo e host), remove a porta padrão (443 p/ https, 80
|
|
54
|
+
* p/ http), remove a barra final e preserva o subpath. Mantém a mesma ideia do
|
|
55
|
+
* futuro `instance_hash`: formas equivalentes colapsam na mesma chave.
|
|
56
|
+
*
|
|
57
|
+
* @param instance - URL base da instância (ex.: `https://Redmine.Example/`).
|
|
58
|
+
* @returns A URL normalizada (ex.: `https://redmine.example`).
|
|
59
|
+
* @throws {TypeError} Se `instance` não for uma URL válida.
|
|
60
|
+
* @example
|
|
61
|
+
* normalizeInstanceUrl('HTTPS://Redmine.Example:443/'); // 'https://redmine.example'
|
|
62
|
+
*/
|
|
63
|
+
export declare function normalizeInstanceUrl(instance: string): string;
|
|
64
|
+
/** Caminho padrão do arquivo de credenciais (diretório de config do usuário). */
|
|
65
|
+
export declare function defaultCredentialsPath(): string;
|
|
66
|
+
/** Opções do {@link FileCredentialStore}. */
|
|
67
|
+
export interface FileCredentialStoreOptions {
|
|
68
|
+
/** Caminho do arquivo; default via `env-paths`. Útil para testes. */
|
|
69
|
+
filePath?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Implementação de {@link CredentialStore} baseada em arquivo `0600` num
|
|
73
|
+
* diretório `0700`. Verifica as permissões ao ler (boot) e as reaplica ao
|
|
74
|
+
* escrever, independentemente do umask.
|
|
75
|
+
*/
|
|
76
|
+
export declare class FileCredentialStore implements CredentialStore {
|
|
77
|
+
private readonly filePath;
|
|
78
|
+
/** @param options - Ver {@link FileCredentialStoreOptions}. */
|
|
79
|
+
constructor(options?: FileCredentialStoreOptions);
|
|
80
|
+
/** @inheritdoc */
|
|
81
|
+
get(instance: string): Promise<string | undefined>;
|
|
82
|
+
/** @inheritdoc */
|
|
83
|
+
set(instance: string, apiKey: string): Promise<void>;
|
|
84
|
+
/** @inheritdoc */
|
|
85
|
+
delete(instance: string): Promise<void>;
|
|
86
|
+
/** Lê e valida o arquivo; `undefined` se ainda não existir. */
|
|
87
|
+
private read;
|
|
88
|
+
/** Escreve o arquivo garantindo dir `0700` e arquivo `0600`. */
|
|
89
|
+
private write;
|
|
90
|
+
/** Falha com erro acionável se as permissões do arquivo estiverem erradas. */
|
|
91
|
+
private assertSecureMode;
|
|
92
|
+
}
|
|
93
|
+
/** Opções do {@link EnvCredentialStore}. */
|
|
94
|
+
export interface EnvCredentialStoreOptions {
|
|
95
|
+
/** Nome da variável de ambiente; default `REDMINE_API_KEY`. */
|
|
96
|
+
varName?: string;
|
|
97
|
+
/** Ambiente a consultar; default `process.env` (injetável em testes). */
|
|
98
|
+
env?: NodeJS.ProcessEnv;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Implementação de {@link CredentialStore} somente-leitura sobre uma variável
|
|
102
|
+
* de ambiente. É o fallback headless (CI/MCP) da cascata: `get` devolve a chave
|
|
103
|
+
* do env para qualquer instância; `set`/`delete` lançam.
|
|
104
|
+
*/
|
|
105
|
+
export declare class EnvCredentialStore implements CredentialStore {
|
|
106
|
+
private readonly varName;
|
|
107
|
+
private readonly env;
|
|
108
|
+
/** @param options - Ver {@link EnvCredentialStoreOptions}. */
|
|
109
|
+
constructor(options?: EnvCredentialStoreOptions);
|
|
110
|
+
/** @inheritdoc */
|
|
111
|
+
get(): Promise<string | undefined>;
|
|
112
|
+
/** Somente leitura: sempre lança. */
|
|
113
|
+
set(): Promise<void>;
|
|
114
|
+
/** Somente leitura: sempre lança. */
|
|
115
|
+
delete(): Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Cascata de {@link CredentialStore}: `get` consulta os stores em ordem e
|
|
119
|
+
* devolve o primeiro valor encontrado; escritas vão para o primeiro store (o
|
|
120
|
+
* gravável). A ordem M1 é arquivo → env.
|
|
121
|
+
*/
|
|
122
|
+
export declare class CascadingCredentialStore implements CredentialStore {
|
|
123
|
+
private readonly stores;
|
|
124
|
+
/**
|
|
125
|
+
* @param stores - Stores em ordem de prioridade (o primeiro é o gravável).
|
|
126
|
+
* @throws {CredentialStoreError} Se a lista estiver vazia.
|
|
127
|
+
*/
|
|
128
|
+
constructor(stores: readonly CredentialStore[]);
|
|
129
|
+
/** @inheritdoc */
|
|
130
|
+
get(instance: string): Promise<string | undefined>;
|
|
131
|
+
/** @inheritdoc */
|
|
132
|
+
set(instance: string, apiKey: string): Promise<void>;
|
|
133
|
+
/** @inheritdoc */
|
|
134
|
+
delete(instance: string): Promise<void>;
|
|
135
|
+
/** O primeiro store da cascata — destino das escritas. */
|
|
136
|
+
private writableStore;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Cascata de credenciais M2 com migração para o keychain (ADR-003).
|
|
140
|
+
*
|
|
141
|
+
* A resolução (`get`) segue a ordem keychain → arquivo `0600` → env; o primeiro
|
|
142
|
+
* valor encontrado vence. Quando a chave vem do ARQUIVO e o keychain está
|
|
143
|
+
* DISPONÍVEL, a chave é migrada — gravada no keychain e só então removida do
|
|
144
|
+
* arquivo (a remoção é condicionada à confirmação da gravação, evitando perda da
|
|
145
|
+
* credencial se o keychain degradar) — com um único aviso informativo por
|
|
146
|
+
* cascata. As escritas (`set`) preferem o keychain quando disponível, com
|
|
147
|
+
* fallback para o arquivo; o env é somente leitura. A api_key nunca é logada.
|
|
148
|
+
*/
|
|
149
|
+
export declare class MigratingCredentialCascade implements CredentialStore {
|
|
150
|
+
private readonly keyring;
|
|
151
|
+
private readonly file;
|
|
152
|
+
private readonly env;
|
|
153
|
+
private readonly logger;
|
|
154
|
+
/** Garante que o aviso de migração seja emitido uma única vez por cascata. */
|
|
155
|
+
private migrationWarned;
|
|
156
|
+
/**
|
|
157
|
+
* @param keyring - Store do keychain nativo (fonte prioritária e destino da migração).
|
|
158
|
+
* @param file - Store de arquivo `0600` (fallback e origem da migração).
|
|
159
|
+
* @param env - Store de ambiente somente leitura (fallback headless).
|
|
160
|
+
* @param logger - Logger do aviso único de migração; default no-op.
|
|
161
|
+
*/
|
|
162
|
+
constructor(keyring: KeyringCredentialStore, file: CredentialStore, env: CredentialStore, logger?: Logger);
|
|
163
|
+
/** @inheritdoc */
|
|
164
|
+
get(instance: string): Promise<string | undefined>;
|
|
165
|
+
/** @inheritdoc */
|
|
166
|
+
set(instance: string, apiKey: string): Promise<void>;
|
|
167
|
+
/** @inheritdoc */
|
|
168
|
+
delete(instance: string): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* Identifica QUAL fonte resolveria a credencial da instância (keychain →
|
|
171
|
+
* arquivo → env), sem expor a api_key e SEM disparar a migração
|
|
172
|
+
* arquivo→keychain feita por {@link get} — diagnóstico somente-leitura
|
|
173
|
+
* usado pela tela `doctor` da TUI (#35), que só precisa mostrar o método em
|
|
174
|
+
* uso, nunca o segredo.
|
|
175
|
+
*
|
|
176
|
+
* @param instance - URL base da instância (será normalizada pelos stores).
|
|
177
|
+
* @returns A fonte em uso, ou `'none'` se nenhuma tiver a credencial.
|
|
178
|
+
*/
|
|
179
|
+
describeSource(instance: string): Promise<CredentialSourceKind>;
|
|
180
|
+
/**
|
|
181
|
+
* Migra a chave do arquivo para o keychain quando este está disponível. A
|
|
182
|
+
* remoção do arquivo só ocorre após confirmar a gravação no keychain, evitando
|
|
183
|
+
* perda da credencial se a escrita degradar. Emite um único aviso, sem expor a
|
|
184
|
+
* api_key.
|
|
185
|
+
*
|
|
186
|
+
* @param instance - URL base da instância (será normalizada pelos stores).
|
|
187
|
+
* @param apiKey - Chave lida do arquivo a ser migrada.
|
|
188
|
+
*/
|
|
189
|
+
private migrateFromFile;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Fonte que resolveria a credencial de uma instância na cascata M2, sem
|
|
193
|
+
* expor a api_key em si (consumido pela tela `doctor` da TUI, #35).
|
|
194
|
+
* `'none'` quando nenhuma das três fontes tem a credencial.
|
|
195
|
+
*/
|
|
196
|
+
export type CredentialSourceKind = 'keyring' | 'file' | 'env' | 'none';
|
|
197
|
+
/** Opções de {@link createCredentialCascade} / {@link resolveApiKey}. */
|
|
198
|
+
export interface CredentialCascadeOptions {
|
|
199
|
+
/** Caminho do arquivo de credenciais; default via `env-paths`. */
|
|
200
|
+
filePath?: string;
|
|
201
|
+
/** Nome da variável de ambiente; default `REDMINE_API_KEY`. */
|
|
202
|
+
envVarName?: string;
|
|
203
|
+
/** Ambiente a consultar; default `process.env`. */
|
|
204
|
+
env?: NodeJS.ProcessEnv;
|
|
205
|
+
/** Logger para o aviso único de migração; default no-op. */
|
|
206
|
+
logger?: Logger;
|
|
207
|
+
/** Service do keychain; default `redmine-context`. */
|
|
208
|
+
keyringService?: string;
|
|
209
|
+
/** Carregador do módulo nativo do keychain; injetável em testes. */
|
|
210
|
+
keyringLoader?: KeyringModuleLoader;
|
|
211
|
+
/**
|
|
212
|
+
* Permite a credencial de AMBIENTE (`REDMINE_API_KEY`) como fallback. Default
|
|
213
|
+
* `true` (retrocompatível). SEGURANÇA (#187): a env-key é INSTANCE-AGNÓSTICA
|
|
214
|
+
* (o `EnvCredentialStore` a devolve para QUALQUER URL). Quando a URL da
|
|
215
|
+
* instância vem de uma fonte MUTÁVEL/menos confiável (ex.: `settings.json`
|
|
216
|
+
* persistido, origem `config`), passe `false` — assim uma URL adulterada não
|
|
217
|
+
* pode fazer a chave real ser enviada a um host arbitrário; só credencial
|
|
218
|
+
* PINADA à instância (keychain/arquivo) é aceita (fail-closed).
|
|
219
|
+
*/
|
|
220
|
+
allowEnvFallback?: boolean;
|
|
221
|
+
}
|
|
222
|
+
export declare function createCredentialCascade(options?: CredentialCascadeOptions): MigratingCredentialCascade;
|
|
223
|
+
/**
|
|
224
|
+
* Resolve a api_key de uma instância pela cascata M2 (keychain → arquivo → env),
|
|
225
|
+
* migrando do arquivo para o keychain quando este estiver disponível.
|
|
226
|
+
*
|
|
227
|
+
* @param instance - URL base da instância (será normalizada).
|
|
228
|
+
* @param options - Ver {@link CredentialCascadeOptions}.
|
|
229
|
+
* @returns A api_key resolvida, ou `undefined` se nenhuma fonte a tiver.
|
|
230
|
+
* @example
|
|
231
|
+
* const apiKey = await resolveApiKey('https://redmine.example');
|
|
232
|
+
*/
|
|
233
|
+
export declare function resolveApiKey(instance: string, options?: CredentialCascadeOptions): Promise<string | undefined>;
|
|
234
|
+
/**
|
|
235
|
+
* Identifica a fonte que resolveria a credencial de uma instância na cascata
|
|
236
|
+
* M2 (keychain → arquivo → env), SEM expor a api_key e sem migrar do arquivo
|
|
237
|
+
* para o keychain — ver {@link MigratingCredentialCascade.describeSource}.
|
|
238
|
+
* Usada pela tela `doctor` da TUI (#35) para relatar o método de credencial
|
|
239
|
+
* em uso.
|
|
240
|
+
*
|
|
241
|
+
* @param instance - URL base da instância (será normalizada).
|
|
242
|
+
* @param options - Ver {@link CredentialCascadeOptions}.
|
|
243
|
+
* @returns A fonte em uso ('keyring' | 'file' | 'env'), ou 'none'.
|
|
244
|
+
* @example
|
|
245
|
+
* const source = await describeCredentialSource('https://redmine.example');
|
|
246
|
+
*/
|
|
247
|
+
export declare function describeCredentialSource(instance: string, options?: CredentialCascadeOptions): Promise<CredentialSourceKind>;
|