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
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const TOOL_NAME = 'redmine-context';
|
|
2
|
+
// Manter em sincronia com package.json (validado por tests/packaging/smoke-pack.test.ts).
|
|
3
|
+
export const TOOL_VERSION = '1.0.0';
|
|
4
|
+
// Superfície pública do core: contrato de tipos + padrão de progresso (ADR-005).
|
|
5
|
+
// As superfícies devem consumir o core somente por aqui / por ./contract.js.
|
|
6
|
+
export * from './contract.js';
|
|
7
|
+
// Orquestração get → normalize → bundle reutilizável por CLI (#17) e MCP (#18).
|
|
8
|
+
export { fetchIssueBundle, } from './fetch-issue-bundle.js';
|
|
9
|
+
// Orquestração de extração de anexos (M3-10): baixa → dispatcher → extrator →
|
|
10
|
+
// cache → Map<attachmentId, ExtractionResult>, embutida nos bundles pela flag
|
|
11
|
+
// `extractAttachments` de `fetchIssueBundle`. CLI/MCP ligam a flag na #55.
|
|
12
|
+
export { extractIssueAttachments, } from './extract-issue-attachments.js';
|
|
13
|
+
// Orquestração get → normalize → extração de UM anexo (M3-13): reutiliza o
|
|
14
|
+
// pipeline com cache de `extractIssueAttachments` para a tool MCP read-only
|
|
15
|
+
// `get_attachment_text` (#55). Devolve o ExtractionResult do anexo pedido.
|
|
16
|
+
export { fetchAttachmentText, fetchAttachmentTextCacheFirst, AttachmentNotFoundError, } from './fetch-attachment-text.js';
|
|
17
|
+
// Leitura cache-first não-bloqueante das extrações (M4-11 #70): lê o que está
|
|
18
|
+
// pronto e sinaliza `processing` para o resto, disparando a extração cara em
|
|
19
|
+
// background pela fila — a superfície MCP responde na hora (< 5s), sem bloquear.
|
|
20
|
+
export { extractIssueAttachmentsCacheFirst, makeQueueBackgroundExtractor, processingResult, } from './cache-first.js';
|
|
21
|
+
// Orquestração de busca (filtros + full-text best-effort) para a tool MCP (#19).
|
|
22
|
+
export { fetchIssueSearch, SEARCH_DEFAULT_LIMIT, } from './fetch-issue-search.js';
|
|
23
|
+
// Primitiva full-text `/search.json` (usada pela orquestração acima).
|
|
24
|
+
export { searchIssues } from './client/index.js';
|
|
25
|
+
// Client HTTP base (auth por api_key + retry) — usado por telas que precisam
|
|
26
|
+
// montar suas próprias chamadas ao core sem uma orquestração pronta (ex.: a
|
|
27
|
+
// home da TUI, #29, que lista "minhas issues" via `listIssues` abaixo).
|
|
28
|
+
export { createHttpClient, } from './client/index.js';
|
|
29
|
+
// Listagem paginada de issues (`GET /issues.json`), payload bruto sem
|
|
30
|
+
// normalização — usada pela home da TUI (#29) com o filtro `assigned_to_id=me`.
|
|
31
|
+
export { listIssues } from './client/index.js';
|
|
32
|
+
// Detalhe completo de uma issue (`GET /issues/{id}.json`), payload bruto sem
|
|
33
|
+
// normalização — usado pela tela de detalhe da TUI (#31), que normaliza com
|
|
34
|
+
// `normalizeIssue` abaixo para render o modelo estável `Issue` (sem passar
|
|
35
|
+
// pelo bundle Markdown/JSON, que é o formato de saída da CLI/MCP, não do
|
|
36
|
+
// modelo em memória que a TUI precisa para paginar/rolar o conteúdo).
|
|
37
|
+
export { getIssue } from './client/index.js';
|
|
38
|
+
// Normalização do payload bruto de issue no modelo estável `Issue` do
|
|
39
|
+
// contrato — usada por `fetchIssueBundle` (CLI/MCP) e, desde a #31, também
|
|
40
|
+
// diretamente pela tela de detalhe da TUI (`getIssue` + `normalizeIssue`,
|
|
41
|
+
// sem serializar para Markdown/JSON).
|
|
42
|
+
export { normalizeIssue } from './normalize/index.js';
|
|
43
|
+
// Empacotamento Markdown/JSON direto (M1-09/M1-10) — usado por
|
|
44
|
+
// `fetchIssueBundle` internamente e, desde a #33, também diretamente pela
|
|
45
|
+
// tela de exportação da TUI: a issue já está normalizada em memória (`./use-issue-detail.js`,
|
|
46
|
+
// #31), então gravar o bundle não precisa refazer a busca+normalização via
|
|
47
|
+
// `fetchIssueBundle` — só empacotar o que já foi carregado.
|
|
48
|
+
export { buildMarkdownBundle, buildJsonBundle, fenceBlock, } from './bundle/index.js';
|
|
49
|
+
// Erros HTTP tipados — usados pelas superfícies para mapear exit codes (ADR-005).
|
|
50
|
+
export { RedmineHttpError, RedmineAuthError, RedmineForbiddenError, RedmineNotFoundError, } from './client/index.js';
|
|
51
|
+
// Login por senha (M1-07) e cascata de credenciais (M1-08) para as superfícies.
|
|
52
|
+
export { loginWithPassword, validateApiKey, RedmineLoginError, } from './config/index.js';
|
|
53
|
+
export { createCredentialCascade, resolveApiKey, describeCredentialSource, normalizeInstanceUrl, CredentialStoreError, } from './config/index.js';
|
|
54
|
+
// Persistência da URL da instância (#187) — fallback além de REDMINE_URL/--url.
|
|
55
|
+
export { defaultSettingsStore, resolveInstanceUrl, } from './config/index.js';
|
|
56
|
+
// Diagnóstico de binários de mídia (M3-11 #53, M4-01 #57) — núcleo do comando
|
|
57
|
+
// `doctor` (CLI) e da seção "Binários de mídia" da TUI. Detecção de tesseract,
|
|
58
|
+
// ffmpeg e whisper.cpp + status do modelo GGUF, com hint de instalação por SO
|
|
59
|
+
// (ADR-002).
|
|
60
|
+
export { diagnoseBinaries, tesseractInstallHint, pdftotextInstallHint, ffmpegInstallHint, whisperInstallHint, } from './config/index.js';
|
|
61
|
+
// Localização de binários de mídia e path canônico do modelo GGUF (M4-01, #57).
|
|
62
|
+
// `whisperModelDir` é o ponto único de verdade do cache de modelos, consumido
|
|
63
|
+
// pelo `doctor` e pelo download do modelo (#58) (ADR-002).
|
|
64
|
+
export { findFfmpeg, detectFfmpegVersion, findWhisper, whisperModelDir, } from './extract/index.js';
|
|
65
|
+
// Download do modelo GGUF com SHA-256 pinado e guard headless (M4-02, #58).
|
|
66
|
+
// Opt-in interativo (ADR-002): recusa em MCP/headless; consome `whisperModelDir`.
|
|
67
|
+
export { downloadGgufModel, GgufDownloadError, GGUF_MODEL_NAME, GGUF_MODEL_URL, GGUF_MODEL_SHA256, } from './extract/index.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalização das coleções aninhadas de uma issue (issue #12).
|
|
3
|
+
*
|
|
4
|
+
* Cobre o que a issue #11 deixou como coleções vazias: custom_fields, relations,
|
|
5
|
+
* parent/children e watchers. Mesmo contrato de robustez de {@link normalizeIssue}
|
|
6
|
+
* (`src/normalize/issue.ts`): reutiliza os helpers defensivos de `./helpers.js` e
|
|
7
|
+
* NUNCA lança — itens malformados são descartados e campos ausentes degradam para
|
|
8
|
+
* defaults estáveis do contrato (ADR-005).
|
|
9
|
+
*/
|
|
10
|
+
import type { CustomField, IssueChild, IssueRelation, RedmineRef } from '../contract.js';
|
|
11
|
+
/**
|
|
12
|
+
* Normaliza a coleção de custom fields de uma issue.
|
|
13
|
+
*
|
|
14
|
+
* @param value - Valor bruto de `issue.custom_fields`.
|
|
15
|
+
* @param fieldFormats - Mapa opcional `id → field_format` fornecido externamente.
|
|
16
|
+
* @returns Array de custom fields válidos (itens malformados descartados).
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeCustomFields(value: unknown, fieldFormats?: Map<number, string>): CustomField[];
|
|
19
|
+
/**
|
|
20
|
+
* Normaliza a coleção de relations de uma issue.
|
|
21
|
+
*
|
|
22
|
+
* @param value - Valor bruto de `issue.relations`.
|
|
23
|
+
* @returns Array de relations válidas (itens malformados descartados).
|
|
24
|
+
*/
|
|
25
|
+
export declare function normalizeRelations(value: unknown): IssueRelation[];
|
|
26
|
+
/**
|
|
27
|
+
* Normaliza a coleção de children (nível de topo) de uma issue.
|
|
28
|
+
*
|
|
29
|
+
* @param value - Valor bruto de `issue.children`.
|
|
30
|
+
* @returns Array de children válidos (itens malformados descartados).
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeChildren(value: unknown): IssueChild[];
|
|
33
|
+
/**
|
|
34
|
+
* Extrai a referência de parent (`{ id }`) do payload.
|
|
35
|
+
*
|
|
36
|
+
* @param value - Valor bruto de `issue.parent`.
|
|
37
|
+
* @returns `{ id }` quando há `id` numérico; senão `undefined`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function normalizeParent(value: unknown): {
|
|
40
|
+
id: number;
|
|
41
|
+
} | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Normaliza a lista de watchers (refs `{ id, name }`).
|
|
44
|
+
*
|
|
45
|
+
* Só deve ser chamada quando a chave `watchers` está presente no payload — a
|
|
46
|
+
* ausência da chave (include não pedido ou 403) é degradação e o campo do
|
|
47
|
+
* contrato fica omitido pelo chamador.
|
|
48
|
+
*
|
|
49
|
+
* @param value - Valor bruto de `issue.watchers`.
|
|
50
|
+
* @returns Array de refs válidas (itens malformados descartados).
|
|
51
|
+
*/
|
|
52
|
+
export declare function normalizeWatchers(value: unknown): RedmineRef[];
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalização das coleções aninhadas de uma issue (issue #12).
|
|
3
|
+
*
|
|
4
|
+
* Cobre o que a issue #11 deixou como coleções vazias: custom_fields, relations,
|
|
5
|
+
* parent/children e watchers. Mesmo contrato de robustez de {@link normalizeIssue}
|
|
6
|
+
* (`src/normalize/issue.ts`): reutiliza os helpers defensivos de `./helpers.js` e
|
|
7
|
+
* NUNCA lança — itens malformados são descartados e campos ausentes degradam para
|
|
8
|
+
* defaults estáveis do contrato (ADR-005).
|
|
9
|
+
*/
|
|
10
|
+
import { asArray, asNumber, asRecord, asString, isDefined, normalizeRef } from './helpers.js';
|
|
11
|
+
/**
|
|
12
|
+
* Extrai o `raw_value` de um custom field preservando o bruto da API.
|
|
13
|
+
*
|
|
14
|
+
* Mantém `string`, `""` e arrays (filtrando itens não-textuais) exatamente como
|
|
15
|
+
* vieram; `null`, ausência e tipos inesperados colapsam para `null`.
|
|
16
|
+
*
|
|
17
|
+
* @param value - Valor bruto de `custom_field.value`.
|
|
18
|
+
* @returns O bruto preservado no formato do contrato.
|
|
19
|
+
*/
|
|
20
|
+
function rawCustomFieldValue(value) {
|
|
21
|
+
if (Array.isArray(value))
|
|
22
|
+
return value.filter((item) => typeof item === 'string');
|
|
23
|
+
return asString(value) ?? null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Normaliza APENAS a ausência de um custom field (`""`/`null` → `null`).
|
|
27
|
+
*
|
|
28
|
+
* Não há coerção de tipo (ADR-005): qualquer valor presente e não-vazio é
|
|
29
|
+
* repassado intacto — inclusive arrays.
|
|
30
|
+
*
|
|
31
|
+
* @param raw - O `raw_value` já extraído.
|
|
32
|
+
* @returns `null` para ausência (`""`/`null`); senão o próprio bruto.
|
|
33
|
+
*/
|
|
34
|
+
function normalizedCustomFieldValue(raw) {
|
|
35
|
+
if (raw === null || raw === '')
|
|
36
|
+
return null;
|
|
37
|
+
return raw;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Normaliza um custom field para o contrato {@link CustomField}.
|
|
41
|
+
*
|
|
42
|
+
* `field_format` não vem na API de issue: só é anotado quando o chamador fornece
|
|
43
|
+
* o formato via `fieldFormats` (ex.: de `/custom_fields.json`).
|
|
44
|
+
*
|
|
45
|
+
* @param value - Item bruto de `issue.custom_fields[]`.
|
|
46
|
+
* @param fieldFormats - Mapa opcional `id → field_format` fornecido externamente.
|
|
47
|
+
* @returns O custom field normalizado, ou `undefined` se não tiver `id` numérico.
|
|
48
|
+
*/
|
|
49
|
+
function normalizeCustomField(value, fieldFormats) {
|
|
50
|
+
const record = asRecord(value);
|
|
51
|
+
if (record === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
const id = asNumber(record.id);
|
|
54
|
+
if (id === undefined)
|
|
55
|
+
return undefined;
|
|
56
|
+
const raw = rawCustomFieldValue(record.value);
|
|
57
|
+
const field = {
|
|
58
|
+
id,
|
|
59
|
+
name: asString(record.name) ?? '',
|
|
60
|
+
value: normalizedCustomFieldValue(raw),
|
|
61
|
+
raw_value: raw,
|
|
62
|
+
};
|
|
63
|
+
const format = fieldFormats?.get(id);
|
|
64
|
+
if (format !== undefined)
|
|
65
|
+
field.field_format = format;
|
|
66
|
+
return field;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Normaliza a coleção de custom fields de uma issue.
|
|
70
|
+
*
|
|
71
|
+
* @param value - Valor bruto de `issue.custom_fields`.
|
|
72
|
+
* @param fieldFormats - Mapa opcional `id → field_format` fornecido externamente.
|
|
73
|
+
* @returns Array de custom fields válidos (itens malformados descartados).
|
|
74
|
+
*/
|
|
75
|
+
export function normalizeCustomFields(value, fieldFormats) {
|
|
76
|
+
return asArray(value)
|
|
77
|
+
.map((item) => normalizeCustomField(item, fieldFormats))
|
|
78
|
+
.filter(isDefined);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Normaliza uma relação entre issues para o contrato {@link IssueRelation}.
|
|
82
|
+
*
|
|
83
|
+
* @param value - Item bruto de `issue.relations[]`.
|
|
84
|
+
* @returns A relação normalizada, ou `undefined` se não tiver `id` numérico.
|
|
85
|
+
* `delay` ausente vira `null`; demais campos numéricos/textuais degradam.
|
|
86
|
+
*/
|
|
87
|
+
function normalizeRelation(value) {
|
|
88
|
+
const record = asRecord(value);
|
|
89
|
+
if (record === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
const id = asNumber(record.id);
|
|
92
|
+
if (id === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
issue_id: asNumber(record.issue_id) ?? 0,
|
|
97
|
+
issue_to_id: asNumber(record.issue_to_id) ?? 0,
|
|
98
|
+
relation_type: asString(record.relation_type) ?? '',
|
|
99
|
+
delay: asNumber(record.delay) ?? null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Normaliza a coleção de relations de uma issue.
|
|
104
|
+
*
|
|
105
|
+
* @param value - Valor bruto de `issue.relations`.
|
|
106
|
+
* @returns Array de relations válidas (itens malformados descartados).
|
|
107
|
+
*/
|
|
108
|
+
export function normalizeRelations(value) {
|
|
109
|
+
return asArray(value).map(normalizeRelation).filter(isDefined);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Normaliza uma issue-filha para o contrato {@link IssueChild}.
|
|
113
|
+
*
|
|
114
|
+
* O contrato é plano (`{id, tracker?, subject?}`): eventuais `children` aninhados
|
|
115
|
+
* do payload são ignorados aqui — cada nível é uma issue por si.
|
|
116
|
+
*
|
|
117
|
+
* @param value - Item bruto de `issue.children[]`.
|
|
118
|
+
* @returns A filha normalizada, ou `undefined` se não tiver `id` numérico.
|
|
119
|
+
*/
|
|
120
|
+
function normalizeChild(value) {
|
|
121
|
+
const record = asRecord(value);
|
|
122
|
+
if (record === undefined)
|
|
123
|
+
return undefined;
|
|
124
|
+
const id = asNumber(record.id);
|
|
125
|
+
if (id === undefined)
|
|
126
|
+
return undefined;
|
|
127
|
+
const child = { id };
|
|
128
|
+
const tracker = normalizeRef(record.tracker);
|
|
129
|
+
if (tracker !== undefined)
|
|
130
|
+
child.tracker = tracker;
|
|
131
|
+
const subject = asString(record.subject);
|
|
132
|
+
if (subject !== undefined)
|
|
133
|
+
child.subject = subject;
|
|
134
|
+
return child;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Normaliza a coleção de children (nível de topo) de uma issue.
|
|
138
|
+
*
|
|
139
|
+
* @param value - Valor bruto de `issue.children`.
|
|
140
|
+
* @returns Array de children válidos (itens malformados descartados).
|
|
141
|
+
*/
|
|
142
|
+
export function normalizeChildren(value) {
|
|
143
|
+
return asArray(value).map(normalizeChild).filter(isDefined);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Extrai a referência de parent (`{ id }`) do payload.
|
|
147
|
+
*
|
|
148
|
+
* @param value - Valor bruto de `issue.parent`.
|
|
149
|
+
* @returns `{ id }` quando há `id` numérico; senão `undefined`.
|
|
150
|
+
*/
|
|
151
|
+
export function normalizeParent(value) {
|
|
152
|
+
const record = asRecord(value);
|
|
153
|
+
if (record === undefined)
|
|
154
|
+
return undefined;
|
|
155
|
+
const id = asNumber(record.id);
|
|
156
|
+
return id === undefined ? undefined : { id };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Normaliza a lista de watchers (refs `{ id, name }`).
|
|
160
|
+
*
|
|
161
|
+
* Só deve ser chamada quando a chave `watchers` está presente no payload — a
|
|
162
|
+
* ausência da chave (include não pedido ou 403) é degradação e o campo do
|
|
163
|
+
* contrato fica omitido pelo chamador.
|
|
164
|
+
*
|
|
165
|
+
* @param value - Valor bruto de `issue.watchers`.
|
|
166
|
+
* @returns Array de refs válidas (itens malformados descartados).
|
|
167
|
+
*/
|
|
168
|
+
export function normalizeWatchers(value) {
|
|
169
|
+
return asArray(value).map(normalizeRef).filter(isDefined);
|
|
170
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers de parsing defensivo compartilhados pela normalização (issues #11/#12).
|
|
3
|
+
*
|
|
4
|
+
* Núcleo do estilo "nunca lança" de `src/normalize/*`: cada função converte um
|
|
5
|
+
* valor `unknown` em um tipo estável ou num `undefined`/default previsível,
|
|
6
|
+
* permitindo que payloads parciais ou malformados degradem campo a campo sem
|
|
7
|
+
* crashar. Reutilizados por {@link normalizeIssue} e pelos normalizadores de
|
|
8
|
+
* coleções (custom_fields, relations, children, watchers).
|
|
9
|
+
*/
|
|
10
|
+
import type { RedmineRef } from '../contract.js';
|
|
11
|
+
/**
|
|
12
|
+
* Converte um valor desconhecido em `Record<string, unknown>` para acesso seguro.
|
|
13
|
+
*
|
|
14
|
+
* @param value - Valor a inspecionar.
|
|
15
|
+
* @returns O objeto tipado, ou `undefined` se não for um objeto simples.
|
|
16
|
+
*/
|
|
17
|
+
export declare function asRecord(value: unknown): Record<string, unknown> | undefined;
|
|
18
|
+
/** Devolve o array bruto, ou `[]` quando o valor não é um array. */
|
|
19
|
+
export declare function asArray(value: unknown): unknown[];
|
|
20
|
+
/** `string` quando o valor é textual, senão `undefined`. */
|
|
21
|
+
export declare function asString(value: unknown): string | undefined;
|
|
22
|
+
/** `number` quando o valor é numérico, senão `undefined`. */
|
|
23
|
+
export declare function asNumber(value: unknown): number | undefined;
|
|
24
|
+
/** Preserva `string`/`null` de valores brutos; tipos inesperados viram `null`. */
|
|
25
|
+
export declare function asStringOrNull(value: unknown): string | null;
|
|
26
|
+
/** Type guard para remover `undefined` após um `map` defensivo. */
|
|
27
|
+
export declare function isDefined<T>(value: T | undefined): value is T;
|
|
28
|
+
/**
|
|
29
|
+
* Normaliza uma ref nomeada do Redmine (`{ id, name }`).
|
|
30
|
+
*
|
|
31
|
+
* @param value - Valor bruto (objeto esperado).
|
|
32
|
+
* @returns A ref quando há `id` numérico; senão `undefined`. `name` não-string
|
|
33
|
+
* degrada para `""`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function normalizeRef(value: unknown): RedmineRef | undefined;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers de parsing defensivo compartilhados pela normalização (issues #11/#12).
|
|
3
|
+
*
|
|
4
|
+
* Núcleo do estilo "nunca lança" de `src/normalize/*`: cada função converte um
|
|
5
|
+
* valor `unknown` em um tipo estável ou num `undefined`/default previsível,
|
|
6
|
+
* permitindo que payloads parciais ou malformados degradem campo a campo sem
|
|
7
|
+
* crashar. Reutilizados por {@link normalizeIssue} e pelos normalizadores de
|
|
8
|
+
* coleções (custom_fields, relations, children, watchers).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Converte um valor desconhecido em `Record<string, unknown>` para acesso seguro.
|
|
12
|
+
*
|
|
13
|
+
* @param value - Valor a inspecionar.
|
|
14
|
+
* @returns O objeto tipado, ou `undefined` se não for um objeto simples.
|
|
15
|
+
*/
|
|
16
|
+
export function asRecord(value) {
|
|
17
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Devolve o array bruto, ou `[]` quando o valor não é um array. */
|
|
23
|
+
export function asArray(value) {
|
|
24
|
+
return Array.isArray(value) ? value : [];
|
|
25
|
+
}
|
|
26
|
+
/** `string` quando o valor é textual, senão `undefined`. */
|
|
27
|
+
export function asString(value) {
|
|
28
|
+
return typeof value === 'string' ? value : undefined;
|
|
29
|
+
}
|
|
30
|
+
/** `number` quando o valor é numérico, senão `undefined`. */
|
|
31
|
+
export function asNumber(value) {
|
|
32
|
+
return typeof value === 'number' ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Preserva `string`/`null` de valores brutos; tipos inesperados viram `null`. */
|
|
35
|
+
export function asStringOrNull(value) {
|
|
36
|
+
if (value === null)
|
|
37
|
+
return null;
|
|
38
|
+
return typeof value === 'string' ? value : null;
|
|
39
|
+
}
|
|
40
|
+
/** Type guard para remover `undefined` após um `map` defensivo. */
|
|
41
|
+
export function isDefined(value) {
|
|
42
|
+
return value !== undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Normaliza uma ref nomeada do Redmine (`{ id, name }`).
|
|
46
|
+
*
|
|
47
|
+
* @param value - Valor bruto (objeto esperado).
|
|
48
|
+
* @returns A ref quando há `id` numérico; senão `undefined`. `name` não-string
|
|
49
|
+
* degrada para `""`.
|
|
50
|
+
*/
|
|
51
|
+
export function normalizeRef(value) {
|
|
52
|
+
const record = asRecord(value);
|
|
53
|
+
if (record === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
const id = asNumber(record.id);
|
|
56
|
+
if (id === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
return { id, name: asString(record.name) ?? '' };
|
|
59
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalização do payload bruto de issue (issues #11/#12).
|
|
3
|
+
*
|
|
4
|
+
* Converte o {@link RedmineIssuePayload} devolvido por `getIssue`
|
|
5
|
+
* (`/issues/{id}.json?include=journals,attachments,relations,children`) no modelo
|
|
6
|
+
* estável {@link Issue} do contrato. Cobre o núcleo (#11) — id, subject,
|
|
7
|
+
* description, refs, datas, journals e attachments — e as coleções aninhadas
|
|
8
|
+
* (#12) delegadas a `./collections.js`: custom_fields, relations, parent/children
|
|
9
|
+
* e watchers.
|
|
10
|
+
*
|
|
11
|
+
* Parsing 100% defensivo (estilo `src/client/issues.ts`): payload parcial ou
|
|
12
|
+
* malformado NUNCA lança — cada campo degrada para um default estável.
|
|
13
|
+
*/
|
|
14
|
+
import type { RedmineIssuePayload } from '../client/issues.js';
|
|
15
|
+
import type { Issue } from '../contract.js';
|
|
16
|
+
/**
|
|
17
|
+
* Normaliza o payload bruto de uma issue no modelo {@link Issue} do contrato.
|
|
18
|
+
*
|
|
19
|
+
* Núcleo (#11): id, subject, description, refs, datas, journals e attachments.
|
|
20
|
+
* Coleções (#12): custom_fields, relations, parent/children e watchers. Nunca
|
|
21
|
+
* lança: payload parcial/malformado degrada campo a campo para defaults estáveis.
|
|
22
|
+
*
|
|
23
|
+
* `watchers` só aparece quando a chave está presente no payload; a ausência
|
|
24
|
+
* (include não pedido ou 403) omite o campo como degradação. A API de issue não
|
|
25
|
+
* devolve `field_format` dos custom fields — o chamador pode anotá-lo via
|
|
26
|
+
* `fieldFormats` (ex.: a partir de `/custom_fields.json`).
|
|
27
|
+
*
|
|
28
|
+
* @param payload - Payload bruto de `getIssue`.
|
|
29
|
+
* @param fieldFormats - Mapa opcional `custom_field_id → field_format`.
|
|
30
|
+
* @returns A issue normalizada, sempre válida perante o contrato.
|
|
31
|
+
* @example
|
|
32
|
+
* const issue = normalizeIssue(await getIssue(http, 100));
|
|
33
|
+
*/
|
|
34
|
+
export declare function normalizeIssue(payload: RedmineIssuePayload, fieldFormats?: Map<number, string>): Issue;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalização do payload bruto de issue (issues #11/#12).
|
|
3
|
+
*
|
|
4
|
+
* Converte o {@link RedmineIssuePayload} devolvido por `getIssue`
|
|
5
|
+
* (`/issues/{id}.json?include=journals,attachments,relations,children`) no modelo
|
|
6
|
+
* estável {@link Issue} do contrato. Cobre o núcleo (#11) — id, subject,
|
|
7
|
+
* description, refs, datas, journals e attachments — e as coleções aninhadas
|
|
8
|
+
* (#12) delegadas a `./collections.js`: custom_fields, relations, parent/children
|
|
9
|
+
* e watchers.
|
|
10
|
+
*
|
|
11
|
+
* Parsing 100% defensivo (estilo `src/client/issues.ts`): payload parcial ou
|
|
12
|
+
* malformado NUNCA lança — cada campo degrada para um default estável.
|
|
13
|
+
*/
|
|
14
|
+
import { normalizeChildren, normalizeCustomFields, normalizeParent, normalizeRelations, normalizeWatchers, } from './collections.js';
|
|
15
|
+
import { asArray, asNumber, asRecord, asString, asStringOrNull, isDefined, normalizeRef, } from './helpers.js';
|
|
16
|
+
/** Ref placeholder para campos obrigatórios do contrato ausentes no payload. */
|
|
17
|
+
// Congelado: instância compartilhada entre todas as issues degradadas — mutação
|
|
18
|
+
// acidental corromperia o placeholder globalmente (modo strict lança).
|
|
19
|
+
const NULL_REF = Object.freeze({ id: 0, name: '' });
|
|
20
|
+
/** Ref obrigatória do contrato: cai para {@link NULL_REF} se ausente/inválida. */
|
|
21
|
+
function requireRef(value) {
|
|
22
|
+
return normalizeRef(value) ?? NULL_REF;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Normaliza um detalhe bruto de journal, preservando o histórico sem interpretar.
|
|
26
|
+
*
|
|
27
|
+
* @param value - Item bruto de `journal.details[]`.
|
|
28
|
+
* @returns O detalhe normalizado, ou `undefined` se não for um objeto.
|
|
29
|
+
*/
|
|
30
|
+
function normalizeJournalDetail(value) {
|
|
31
|
+
const record = asRecord(value);
|
|
32
|
+
if (record === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
const detail = {
|
|
35
|
+
property: asString(record.property) ?? '',
|
|
36
|
+
name: asString(record.name) ?? '',
|
|
37
|
+
};
|
|
38
|
+
if ('old_value' in record)
|
|
39
|
+
detail.old_value = asStringOrNull(record.old_value);
|
|
40
|
+
if ('new_value' in record)
|
|
41
|
+
detail.new_value = asStringOrNull(record.new_value);
|
|
42
|
+
return detail;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Normaliza uma entrada de journal (nota opcional + `details[]` brutos).
|
|
46
|
+
*
|
|
47
|
+
* @param value - Item bruto de `issue.journals[]`.
|
|
48
|
+
* @returns O journal normalizado, ou `undefined` se não tiver `id` numérico.
|
|
49
|
+
*/
|
|
50
|
+
function normalizeJournal(value) {
|
|
51
|
+
const record = asRecord(value);
|
|
52
|
+
if (record === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
const id = asNumber(record.id);
|
|
55
|
+
if (id === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
const journal = {
|
|
58
|
+
id,
|
|
59
|
+
created_on: asString(record.created_on) ?? '',
|
|
60
|
+
details: asArray(record.details).map(normalizeJournalDetail).filter(isDefined),
|
|
61
|
+
};
|
|
62
|
+
// notes só é significativa quando não-vazia (Redmine devolve "" em journal de detalhe).
|
|
63
|
+
const notes = asString(record.notes);
|
|
64
|
+
if (notes !== undefined && notes !== '')
|
|
65
|
+
journal.notes = notes;
|
|
66
|
+
const user = normalizeRef(record.user);
|
|
67
|
+
if (user !== undefined)
|
|
68
|
+
journal.user = user;
|
|
69
|
+
return journal;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Normaliza um anexo, mantendo `content_type`/`digest` quando presentes.
|
|
73
|
+
*
|
|
74
|
+
* @param value - Item bruto de `issue.attachments[]`.
|
|
75
|
+
* @returns O anexo normalizado, ou `undefined` se não tiver `id` numérico.
|
|
76
|
+
*/
|
|
77
|
+
function normalizeAttachment(value) {
|
|
78
|
+
const record = asRecord(value);
|
|
79
|
+
if (record === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
const id = asNumber(record.id);
|
|
82
|
+
if (id === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
const attachment = {
|
|
85
|
+
id,
|
|
86
|
+
filename: asString(record.filename) ?? '',
|
|
87
|
+
filesize: asNumber(record.filesize) ?? 0,
|
|
88
|
+
created_on: asString(record.created_on) ?? '',
|
|
89
|
+
content_url: asString(record.content_url) ?? '',
|
|
90
|
+
};
|
|
91
|
+
const contentType = asString(record.content_type);
|
|
92
|
+
if (contentType !== undefined)
|
|
93
|
+
attachment.content_type = contentType;
|
|
94
|
+
const description = asString(record.description);
|
|
95
|
+
if (description !== undefined && description !== '')
|
|
96
|
+
attachment.description = description;
|
|
97
|
+
const author = normalizeRef(record.author);
|
|
98
|
+
if (author !== undefined)
|
|
99
|
+
attachment.author = author;
|
|
100
|
+
const digest = asString(record.digest);
|
|
101
|
+
if (digest !== undefined)
|
|
102
|
+
attachment.digest = digest;
|
|
103
|
+
return attachment;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Normaliza o payload bruto de uma issue no modelo {@link Issue} do contrato.
|
|
107
|
+
*
|
|
108
|
+
* Núcleo (#11): id, subject, description, refs, datas, journals e attachments.
|
|
109
|
+
* Coleções (#12): custom_fields, relations, parent/children e watchers. Nunca
|
|
110
|
+
* lança: payload parcial/malformado degrada campo a campo para defaults estáveis.
|
|
111
|
+
*
|
|
112
|
+
* `watchers` só aparece quando a chave está presente no payload; a ausência
|
|
113
|
+
* (include não pedido ou 403) omite o campo como degradação. A API de issue não
|
|
114
|
+
* devolve `field_format` dos custom fields — o chamador pode anotá-lo via
|
|
115
|
+
* `fieldFormats` (ex.: a partir de `/custom_fields.json`).
|
|
116
|
+
*
|
|
117
|
+
* @param payload - Payload bruto de `getIssue`.
|
|
118
|
+
* @param fieldFormats - Mapa opcional `custom_field_id → field_format`.
|
|
119
|
+
* @returns A issue normalizada, sempre válida perante o contrato.
|
|
120
|
+
* @example
|
|
121
|
+
* const issue = normalizeIssue(await getIssue(http, 100));
|
|
122
|
+
*/
|
|
123
|
+
export function normalizeIssue(payload, fieldFormats) {
|
|
124
|
+
const record = asRecord(payload) ?? {};
|
|
125
|
+
const issue = {
|
|
126
|
+
id: asNumber(record.id) ?? 0,
|
|
127
|
+
subject: asString(record.subject) ?? '',
|
|
128
|
+
project: requireRef(record.project),
|
|
129
|
+
tracker: requireRef(record.tracker),
|
|
130
|
+
status: requireRef(record.status),
|
|
131
|
+
priority: requireRef(record.priority),
|
|
132
|
+
author: requireRef(record.author),
|
|
133
|
+
created_on: asString(record.created_on) ?? '',
|
|
134
|
+
updated_on: asString(record.updated_on) ?? '',
|
|
135
|
+
journals: asArray(record.journals).map(normalizeJournal).filter(isDefined),
|
|
136
|
+
attachments: asArray(record.attachments).map(normalizeAttachment).filter(isDefined),
|
|
137
|
+
custom_fields: normalizeCustomFields(record.custom_fields, fieldFormats),
|
|
138
|
+
relations: normalizeRelations(record.relations),
|
|
139
|
+
children: normalizeChildren(record.children),
|
|
140
|
+
};
|
|
141
|
+
const description = asString(record.description);
|
|
142
|
+
if (description !== undefined && description !== '')
|
|
143
|
+
issue.description = description;
|
|
144
|
+
const assignedTo = normalizeRef(record.assigned_to);
|
|
145
|
+
if (assignedTo !== undefined)
|
|
146
|
+
issue.assigned_to = assignedTo;
|
|
147
|
+
const parent = normalizeParent(record.parent);
|
|
148
|
+
if (parent !== undefined)
|
|
149
|
+
issue.parent = parent;
|
|
150
|
+
// Ausência da chave `watchers` = degradação (403/include ausente): campo omitido.
|
|
151
|
+
if ('watchers' in record)
|
|
152
|
+
issue.watchers = normalizeWatchers(record.watchers);
|
|
153
|
+
return issue;
|
|
154
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handlers dos comandos do CLI (M1-11): `issue` e `login`.
|
|
3
|
+
*
|
|
4
|
+
* Consomem EXCLUSIVAMENTE a superfície pública do core (`../../index.js`,
|
|
5
|
+
* fronteira do ADR-005) — a orquestração `fetchIssueBundle`, o `loginWithPassword`
|
|
6
|
+
* e a cascata de credenciais. Cada handler devolve o exit code do processo; o
|
|
7
|
+
* mapeamento status→código vive em {@link exitCodeForError} e é documentado no
|
|
8
|
+
* `--help`.
|
|
9
|
+
*/
|
|
10
|
+
import type { ParsedArgs, RunDeps } from './types.js';
|
|
11
|
+
/** Exit codes do CLI (documentados no `--help`). */
|
|
12
|
+
export declare const EXIT: {
|
|
13
|
+
/** Erro genérico ou uso inválido. */
|
|
14
|
+
readonly GENERIC: 1;
|
|
15
|
+
/** Falha de autenticação ou credencial ausente. */
|
|
16
|
+
readonly AUTH: 2;
|
|
17
|
+
/** Erro de rede ou HTTP. */
|
|
18
|
+
readonly NETWORK: 3;
|
|
19
|
+
/** Issue inexistente. */
|
|
20
|
+
readonly NOT_FOUND: 4;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Mapeia um erro para o exit code do CLI.
|
|
24
|
+
*
|
|
25
|
+
* Ordem importa: os erros específicos (404, 401) estendem `RedmineHttpError` e
|
|
26
|
+
* precisam ser testados antes do genérico HTTP. Erros de rede (sem status) são
|
|
27
|
+
* detectados pela mensagem do client e também caem em {@link EXIT.NETWORK}.
|
|
28
|
+
*
|
|
29
|
+
* @param error - Erro capturado durante a operação.
|
|
30
|
+
* @returns O exit code correspondente.
|
|
31
|
+
*/
|
|
32
|
+
export declare function exitCodeForError(error: unknown): number;
|
|
33
|
+
/**
|
|
34
|
+
* Comando `issue <id>`: resolve credencial pela cascata, empacota e emite o
|
|
35
|
+
* bundle (stdout ou `--out <dir>`), com progresso em stderr.
|
|
36
|
+
*
|
|
37
|
+
* @param parsed - Argumentos parseados (posicional `<id>` + flags).
|
|
38
|
+
* @param deps - Dependências injetáveis (I/O, env).
|
|
39
|
+
* @returns Exit code do processo.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runIssue(parsed: ParsedArgs, deps: RunDeps): Promise<number>;
|
|
42
|
+
/**
|
|
43
|
+
* Comando `doctor`: diagnostica os binários de mídia (hoje o `tesseract`) e
|
|
44
|
+
* imprime um relatório em TEXTO PURO no stdout. Degrada naturalmente em
|
|
45
|
+
* `NO_COLOR`/não-TTY (não emite cor/ANSI). Exit 0 se todos presentes, 1 se
|
|
46
|
+
* faltar algum — o exit code deixa o resultado programável em scripts.
|
|
47
|
+
*
|
|
48
|
+
* @param _parsed - Argumentos parseados (o comando não usa flags hoje).
|
|
49
|
+
* @param deps - Dependências injetáveis (I/O).
|
|
50
|
+
* @returns Exit code do processo (0 = tudo ok, 1 = binário faltando).
|
|
51
|
+
*/
|
|
52
|
+
export declare function runDoctor(_parsed: ParsedArgs, deps: RunDeps): Promise<number>;
|
|
53
|
+
/**
|
|
54
|
+
* Comando `login`: autentica (senha ou `--api-key`) e salva a api_key na
|
|
55
|
+
* cascata para a instância informada.
|
|
56
|
+
*
|
|
57
|
+
* @param parsed - Argumentos parseados (flags `--url`, `--api-key`, `--insecure`).
|
|
58
|
+
* @param deps - Dependências injetáveis (prompts, I/O, env).
|
|
59
|
+
* @returns Exit code do processo.
|
|
60
|
+
*/
|
|
61
|
+
export declare function runLogin(parsed: ParsedArgs, deps: RunDeps): Promise<number>;
|