@cspeach/cli 0.6.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 +8 -0
- package/README.md +108 -0
- package/dist/agent/anthropic-provider.js +59 -0
- package/dist/agent/llm-provider.js +1 -0
- package/dist/agent/loop.js +709 -0
- package/dist/agent/maybe-build-project-context.js +126 -0
- package/dist/agent/providers/ai-hub-provider.js +58 -0
- package/dist/agent/providers/byok-provider.js +53 -0
- package/dist/agent/providers/factory.js +13 -0
- package/dist/agent/providers/local-provider.js +125 -0
- package/dist/agent/repair-partial.js +31 -0
- package/dist/agent/retry-key.js +58 -0
- package/dist/agent/retry.js +17 -0
- package/dist/agent/sap-connection-adapter.js +82 -0
- package/dist/agent/skill-checkpoint.js +119 -0
- package/dist/agent/tool-dispatch.js +47 -0
- package/dist/agent/turn-assistant-text.js +49 -0
- package/dist/agent/turn-error-ux.js +126 -0
- package/dist/agent/turn-stream.js +79 -0
- package/dist/agent/turn-watchdog.js +71 -0
- package/dist/approvals/advisory-prompt.js +40 -0
- package/dist/approvals/advisory-render.js +38 -0
- package/dist/approvals/approval-prompt.js +100 -0
- package/dist/approvals/jwt.js +33 -0
- package/dist/approvals/render.js +211 -0
- package/dist/approvals/risk-floor.js +26 -0
- package/dist/auth/api-key.js +40 -0
- package/dist/auth/auth-file.js +59 -0
- package/dist/auth/device.js +8 -0
- package/dist/auth/me.js +19 -0
- package/dist/classifier/client.js +58 -0
- package/dist/cli-args.js +38 -0
- package/dist/cli.js +148 -0
- package/dist/commands/config-set.js +245 -0
- package/dist/commands/config-show.js +159 -0
- package/dist/commands/help.js +93 -0
- package/dist/commands/login.js +122 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/project-context-impact.js +215 -0
- package/dist/commands/reroute.js +60 -0
- package/dist/commands/spec-gap-status.js +52 -0
- package/dist/commands/whoami.js +35 -0
- package/dist/config/loader.js +67 -0
- package/dist/config/paths.js +20 -0
- package/dist/doctor/checks/_http-probe.js +56 -0
- package/dist/doctor/checks/auth.js +15 -0
- package/dist/doctor/checks/cert.js +24 -0
- package/dist/doctor/checks/forge-rules.js +102 -0
- package/dist/doctor/checks/keychain-fallback.js +14 -0
- package/dist/doctor/checks/keychain.js +23 -0
- package/dist/doctor/checks/llm-mode.js +27 -0
- package/dist/doctor/checks/proxy.js +13 -0
- package/dist/doctor/checks/sap.js +33 -0
- package/dist/doctor/checks/skill.js +24 -0
- package/dist/doctor/checks/write-mode.js +34 -0
- package/dist/doctor/checks/zcspeach.js +76 -0
- package/dist/doctor/run.js +46 -0
- package/dist/errors/codes.js +12 -0
- package/dist/index.js +6 -0
- package/dist/lock-contention.js +22 -0
- package/dist/one-shot.js +104 -0
- package/dist/project-context/conventions.js +309 -0
- package/dist/project-context/detect.js +250 -0
- package/dist/project-context/domain/abap-cloud.js +26 -0
- package/dist/project-context/domain/abapgit.js +177 -0
- package/dist/project-context/domain/cap.js +164 -0
- package/dist/project-context/domain/fiori.js +326 -0
- package/dist/project-context/git.js +115 -0
- package/dist/project-context/index-files.js +235 -0
- package/dist/project-context/index.js +117 -0
- package/dist/project-context/render.js +308 -0
- package/dist/project-context/types.js +14 -0
- package/dist/projects/build.js +20 -0
- package/dist/projects/canonicalize.js +39 -0
- package/dist/projects/email-template.js +54 -0
- package/dist/projects/extract-cca.js +139 -0
- package/dist/projects/extract-design.js +107 -0
- package/dist/projects/extract-estimate.js +93 -0
- package/dist/projects/extract-modernize.js +130 -0
- package/dist/projects/extract-spec-gap.js +101 -0
- package/dist/projects/extract-test-coverage.js +137 -0
- package/dist/projects/extract-upgrade.js +230 -0
- package/dist/projects/filename.js +18 -0
- package/dist/projects/index.js +8 -0
- package/dist/projects/migration.js +111 -0
- package/dist/projects/promote-command.js +96 -0
- package/dist/projects/promote.js +107 -0
- package/dist/projects/save-command.js +124 -0
- package/dist/projects/save.js +21 -0
- package/dist/projects/status.js +170 -0
- package/dist/projects/types.js +1 -0
- package/dist/projects/validate.js +146 -0
- package/dist/projects/workspace.js +478 -0
- package/dist/renderer/abap-inline.js +121 -0
- package/dist/renderer/banners.js +39 -0
- package/dist/renderer/highlighters/abap.js +126 -0
- package/dist/renderer/highlighters/bdef.js +81 -0
- package/dist/renderer/highlighters/cds.js +91 -0
- package/dist/renderer/markdown.js +291 -0
- package/dist/renderer/pipeline.js +201 -0
- package/dist/renderer/progress-chatter.js +237 -0
- package/dist/renderer/question-normalizer.js +306 -0
- package/dist/renderer/severity.js +61 -0
- package/dist/renderer/status-footer.js +50 -0
- package/dist/renderer/syntax.js +58 -0
- package/dist/renderer/tables.js +55 -0
- package/dist/renderer/thinking-heartbeat.js +70 -0
- package/dist/renderer/tool-widget.js +199 -0
- package/dist/renderer/tty.js +66 -0
- package/dist/renderer/widget-extractor.js +87 -0
- package/dist/renderer/widget-fallback.js +78 -0
- package/dist/renderer/widget-schemas.js +43 -0
- package/dist/repl/at-completer.js +64 -0
- package/dist/repl/at-picker.js +122 -0
- package/dist/repl/bracketed-paste.js +284 -0
- package/dist/repl/current-transport.js +46 -0
- package/dist/repl/diff-display.js +41 -0
- package/dist/repl/file-picker.js +219 -0
- package/dist/repl/inquirer-guard.js +130 -0
- package/dist/repl/inquirer-theme.js +41 -0
- package/dist/repl/rule8-detector.js +99 -0
- package/dist/repl/safety-confirm.js +106 -0
- package/dist/repl/safety-mode-state.js +36 -0
- package/dist/repl/slash-completer.js +59 -0
- package/dist/repl/slash-picker.js +124 -0
- package/dist/repl/update-method-preview-hook.js +45 -0
- package/dist/repl.js +1383 -0
- package/dist/router/classifier.js +38 -0
- package/dist/router/intent-extractor.js +140 -0
- package/dist/router/routing-decision.js +19 -0
- package/dist/sap/connection-manager.js +52 -0
- package/dist/sap/onboarding.js +178 -0
- package/dist/sap/system-info.js +515 -0
- package/dist/session/awaiting-answer.js +73 -0
- package/dist/session/gc.js +28 -0
- package/dist/session/pending.js +37 -0
- package/dist/session/resume.js +77 -0
- package/dist/session/schema.js +20 -0
- package/dist/session/store.js +147 -0
- package/dist/session/time-ago.js +41 -0
- package/dist/skill-catalog.js +222 -0
- package/dist/skills/bundled-skills.js +1 -0
- package/dist/skills/canonical.js +12 -0
- package/dist/skills/manifest-client.js +93 -0
- package/dist/skills/promotion-dispatch.js +24 -0
- package/dist/skills/signing-public-key.js +4 -0
- package/dist/skills/source-bundled.js +20 -0
- package/dist/skills/source-managed.js +26 -0
- package/dist/skills/source-manifest.js +26 -0
- package/dist/tools/_command-shared.js +110 -0
- package/dist/tools/_filesystem-shared.js +81 -0
- package/dist/tools/_flag.js +39 -0
- package/dist/tools/approval.js +228 -0
- package/dist/tools/ask-question.js +205 -0
- package/dist/tools/dispatch-skill.js +81 -0
- package/dist/tools/filesystem/file-edit.js +140 -0
- package/dist/tools/filesystem/file-read.js +89 -0
- package/dist/tools/filesystem/file-write.js +128 -0
- package/dist/tools/filesystem/glob.js +177 -0
- package/dist/tools/filesystem/grep.js +163 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/project/convention_get.js +91 -0
- package/dist/tools/project/playbook_get.js +132 -0
- package/dist/tools/project/project_context_get.js +101 -0
- package/dist/tools/sap-read.js +454 -0
- package/dist/tools/sap-write.js +746 -0
- package/dist/tools/shell/shell_exec.js +209 -0
- package/dist/tools/snapshot.js +107 -0
- package/dist/tools/subagent/_background-shared.js +133 -0
- package/dist/tools/subagent/agent_run.js +186 -0
- package/dist/tools/subagent/background_run.js +143 -0
- package/dist/tools/subagent/monitor_emit.js +65 -0
- package/dist/tools/subagent/schedule_create.js +131 -0
- package/dist/tools/transport.js +233 -0
- package/dist/tools/update-method-intercept.js +119 -0
- package/dist/tools/verify.js +39 -0
- package/dist/tools/web/_web-shared.js +251 -0
- package/dist/tools/web/web_fetch.js +257 -0
- package/dist/tools/web/web_search.js +195 -0
- package/dist/tools/write-mode.js +22 -0
- package/dist/ui/app.js +95 -0
- package/dist/ui/approval-emitter.js +10 -0
- package/dist/ui/approval-modal.js +53 -0
- package/dist/ui/ascii-chars.js +6 -0
- package/dist/ui/body.js +102 -0
- package/dist/ui/coaching-picker-classic.js +36 -0
- package/dist/ui/coaching-picker-emitter.js +27 -0
- package/dist/ui/command-palette.js +34 -0
- package/dist/ui/error-emitter.js +21 -0
- package/dist/ui/footer.js +103 -0
- package/dist/ui/header.js +17 -0
- package/dist/ui/ink-classifier-route.js +19 -0
- package/dist/ui/login-banner.js +72 -0
- package/dist/ui/rich-error-box.js +9 -0
- package/dist/ui/sap-state-store.js +65 -0
- package/dist/ui/session-timeline.js +31 -0
- package/dist/ui/sidebar.js +10 -0
- package/dist/ui/skill-picker.js +50 -0
- package/dist/ui/status-row.js +12 -0
- package/dist/ui/widget-control.js +4 -0
- package/dist/ui/widgets/bar-chart.js +15 -0
- package/dist/ui/widgets/coaching-picker.js +41 -0
- package/dist/ui/widgets/component-registry.js +12 -0
- package/dist/ui/widgets/dep-graph.js +9 -0
- package/dist/ui/widgets/diff-viewer.js +11 -0
- package/dist/ui/widgets/question-card.js +11 -0
- package/dist/ui/widgets/stack-frames.js +5 -0
- package/dist/upgrade-check.js +28 -0
- package/dist/upgrade.js +13 -0
- package/package.json +83 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-highlight language definition for ABAP.
|
|
3
|
+
*
|
|
4
|
+
* Registered as 'abap'. Matches fenced code blocks with ```abap.
|
|
5
|
+
* 150+ keywords covering SELECT/FROM/WHERE, OOP class/interface/method,
|
|
6
|
+
* structured data (types/data/field-symbols), control flow, RAP/EML,
|
|
7
|
+
* CDS annotations are handled separately in cds.ts.
|
|
8
|
+
*/
|
|
9
|
+
// Keywords are unioned into a single Set during grammar build; duplicates
|
|
10
|
+
// are harmless at runtime but noisy in source. Kept alphabetical within
|
|
11
|
+
// each section for diff-friendliness. Keywords that appear in multiple
|
|
12
|
+
// conceptual sections (e.g. READ — both DML and EML, TABLES — declaration
|
|
13
|
+
// and selection-screen) are listed ONCE in the most-DML-like section with a
|
|
14
|
+
// trailing comment noting the other usage.
|
|
15
|
+
const KEYWORDS = [
|
|
16
|
+
// DML / Open SQL (READ/MODIFY/DELETE/CREATE also used in EML — listed here)
|
|
17
|
+
'ALL', 'APPENDING', 'AS', 'BY', 'BYPASSING', 'BUFFER', 'CLIENT',
|
|
18
|
+
'COMMIT', 'CORRESPONDING', 'CREATE', 'DELETE', 'DISTINCT', 'ENTRIES',
|
|
19
|
+
'FIELDS', 'FOR', 'FROM', 'GROUP', 'HAVING', 'IN', 'INNER', 'INSERT',
|
|
20
|
+
'INTO', 'JOIN', 'LEFT', 'MODIFY', 'OF', 'ON', 'ORDER', 'OUTER',
|
|
21
|
+
'READ', 'ROLLBACK', 'ROWS', 'SELECT', 'SINGLE', 'SPECIFIED', 'TABLE',
|
|
22
|
+
'TO', 'UNION', 'UP', 'UPDATE', 'WHERE', 'WORK',
|
|
23
|
+
// Data declaration (TABLES also used in selection-screen — listed here)
|
|
24
|
+
'ALIASES', 'BEGIN', 'CLASS-DATA', 'CLASS-METHODS', 'CONSTANTS', 'DATA',
|
|
25
|
+
'DEFAULT', 'END', 'EVENTS', 'HASHED', 'HEADER', 'INCLUDE', 'INTERFACES',
|
|
26
|
+
'KEY', 'LIKE', 'LINE', 'METHODS', 'NEW', 'NON-UNIQUE', 'OCCURS',
|
|
27
|
+
'REF', 'SORTED', 'STANDARD', 'STRUCTURE', 'TABLES', 'TYPE', 'TYPES',
|
|
28
|
+
'UNIQUE', 'WITH',
|
|
29
|
+
// OOP (ABSTRACT/FINAL also in 'misc' — listed here)
|
|
30
|
+
'ABSTRACT', 'CHANGING', 'CLASS', 'DEFERRED', 'DEFINITION', 'ENDCLASS',
|
|
31
|
+
'ENDINTERFACE', 'ENDMETHOD', 'EXCEPTIONS', 'EXPORTING', 'FINAL',
|
|
32
|
+
'FRIENDS', 'IMPLEMENTATION', 'IMPORTING', 'INHERITING', 'INTERFACE',
|
|
33
|
+
'METHOD', 'OBJECT', 'OPTIONAL', 'OVERRIDING', 'PARAMETER', 'PARAMETERS',
|
|
34
|
+
'PREFERRED', 'PRIVATE', 'PROTECTED', 'PUBLIC', 'RAISING', 'REDEFINITION',
|
|
35
|
+
'RETURNING', 'SECTION',
|
|
36
|
+
// Exception / flow
|
|
37
|
+
'ASSERT', 'CATCH', 'CLEANUP', 'ENDTRY', 'EXCEPTION', 'MESSAGE',
|
|
38
|
+
'RAISE', 'RESUME', 'TRY',
|
|
39
|
+
// Control
|
|
40
|
+
'AT', 'CASE', 'CHECK', 'CONTINUE', 'DO', 'ELSE', 'ELSEIF', 'ENDAT',
|
|
41
|
+
'ENDCASE', 'ENDDO', 'ENDIF', 'ENDLOOP', 'ENDWHILE', 'EXIT', 'IF',
|
|
42
|
+
'LOOP', 'OTHERS', 'RETURN', 'STOP', 'WHEN', 'WHILE',
|
|
43
|
+
// Field symbols
|
|
44
|
+
'ASSIGN', 'FIELD-SYMBOLS', 'UNASSIGN',
|
|
45
|
+
// String / table ops
|
|
46
|
+
'APPEND', 'BINARY', 'CLEAR', 'COLLECT', 'CONCATENATE', 'CONDENSE',
|
|
47
|
+
'DESCRIBE', 'FILTER', 'FIND', 'FREE', 'MOVE', 'REDUCE', 'REPLACE',
|
|
48
|
+
'SEARCH', 'SHIFT', 'SORT', 'SPLIT', 'TRANSLATE', 'VALUE', 'WRITE',
|
|
49
|
+
// Report / selection screen
|
|
50
|
+
'CLASS-POOL', 'END-OF-SELECTION', 'FUNCTION-POOL', 'INITIALIZATION',
|
|
51
|
+
'PROGRAM', 'REPORT', 'SELECT-OPTIONS', 'SELECTION-SCREEN',
|
|
52
|
+
'START-OF-SELECTION',
|
|
53
|
+
// FORM / PERFORM (legacy)
|
|
54
|
+
'CALL', 'ENDFORM', 'FORM', 'FUNCTION', 'PERFORM', 'USING',
|
|
55
|
+
// Misc
|
|
56
|
+
'ACTVT', 'AUTHORITY-CHECK', 'CAST', 'COND', 'CONV', 'DEQUEUE',
|
|
57
|
+
'ENQUEUE', 'EXACT', 'FAIL', 'FIELD', 'ID', 'LET', 'LOCK', 'NOTICE',
|
|
58
|
+
'STATIC', 'SWITCH', 'UNLOCK',
|
|
59
|
+
// EML / RAP (only terms NOT already covered above — READ/MODIFY/DELETE/
|
|
60
|
+
// CREATE/IN/FIELDS/WITH/BY are in the DML section)
|
|
61
|
+
'ASSIGNING', 'AUTHORITY', 'ENTITIES', 'ENTITY', 'EXECUTE', 'FAILED',
|
|
62
|
+
'LOCAL', 'MAPPED', 'MODE', 'REPORTED', 'RESPONSE', 'RESULT',
|
|
63
|
+
'TRANSPORTING',
|
|
64
|
+
];
|
|
65
|
+
const grammar = {
|
|
66
|
+
case_insensitive: true,
|
|
67
|
+
keywords: {
|
|
68
|
+
keyword: KEYWORDS.join(' '),
|
|
69
|
+
},
|
|
70
|
+
contains: [
|
|
71
|
+
// Line comments: * at start of line
|
|
72
|
+
{
|
|
73
|
+
className: 'comment',
|
|
74
|
+
begin: /^\*/,
|
|
75
|
+
end: /$/,
|
|
76
|
+
relevance: 5,
|
|
77
|
+
},
|
|
78
|
+
// Inline comments: " anywhere mid-line
|
|
79
|
+
{
|
|
80
|
+
className: 'comment',
|
|
81
|
+
begin: /"/,
|
|
82
|
+
end: /$/,
|
|
83
|
+
relevance: 3,
|
|
84
|
+
},
|
|
85
|
+
// String literals: single-quoted
|
|
86
|
+
{
|
|
87
|
+
className: 'string',
|
|
88
|
+
begin: /'/,
|
|
89
|
+
end: /'/,
|
|
90
|
+
illegal: /\n/,
|
|
91
|
+
contains: [{ begin: /''/ }], // escaped single quote
|
|
92
|
+
relevance: 4,
|
|
93
|
+
},
|
|
94
|
+
// String templates: |...|
|
|
95
|
+
// Classical begin/end pair with `illegal: /\n/` to prevent the string
|
|
96
|
+
// from consuming across line boundaries. Using zero-width lookahead as
|
|
97
|
+
// `end` is non-standard in highlight.js mode syntax and was rejected in
|
|
98
|
+
// review (H9) — the begin/end/illegal triple is the idiomatic form.
|
|
99
|
+
{
|
|
100
|
+
className: 'string',
|
|
101
|
+
begin: /\|/,
|
|
102
|
+
end: /\|/,
|
|
103
|
+
illegal: /\n/,
|
|
104
|
+
relevance: 0,
|
|
105
|
+
},
|
|
106
|
+
// Z* / Y* identifiers — custom namespace (highlight distinctly as 'title')
|
|
107
|
+
{
|
|
108
|
+
className: 'title',
|
|
109
|
+
begin: /\b[ZY][A-Z0-9_]{1,30}\b/,
|
|
110
|
+
relevance: 3,
|
|
111
|
+
},
|
|
112
|
+
// Numeric literals
|
|
113
|
+
{
|
|
114
|
+
className: 'number',
|
|
115
|
+
begin: /\b\d+(\.\d+)?\b/,
|
|
116
|
+
relevance: 2,
|
|
117
|
+
},
|
|
118
|
+
// Built-in type keywords used after TYPE (treated as type keyword)
|
|
119
|
+
{
|
|
120
|
+
className: 'type',
|
|
121
|
+
begin: /\b(string|xstring|i|int1|int2|int4|int8|f|d|t|c|n|x|p|decfloat16|decfloat34|char|numc|dats|tims|clnt|lang|syst|boolean)\b/i,
|
|
122
|
+
relevance: 3,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
export default grammar;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-highlight language definition for BDEF (Behavior Definition).
|
|
3
|
+
*
|
|
4
|
+
* Registered as 'bdef'. Matches fenced code blocks with ```bdef.
|
|
5
|
+
* Covers managed/unmanaged syntax, persistent/draft table, operations,
|
|
6
|
+
* validations, determinations, actions, feature control, authorization.
|
|
7
|
+
*/
|
|
8
|
+
const BDEF_KEYWORDS = [
|
|
9
|
+
// Top-level
|
|
10
|
+
'managed', 'unmanaged', 'abstract', 'projection',
|
|
11
|
+
'implementation', 'in', 'class', 'unique',
|
|
12
|
+
'define', 'behavior', 'for', 'alias',
|
|
13
|
+
// Persistence
|
|
14
|
+
'persistent', 'table', 'draft', 'lock', 'master', 'dependent',
|
|
15
|
+
'total', 'etag',
|
|
16
|
+
// Authorization
|
|
17
|
+
'authorization', 'instance', 'global',
|
|
18
|
+
// Operations
|
|
19
|
+
'create', 'update', 'delete', 'read',
|
|
20
|
+
'use', 'action', 'function', 'static', 'internal',
|
|
21
|
+
'validation', 'determination', 'on', 'save', 'modify',
|
|
22
|
+
'association', 'composition', 'to', 'parent',
|
|
23
|
+
// Field control
|
|
24
|
+
'field', 'readonly', 'mandatory', 'features',
|
|
25
|
+
// RAP features
|
|
26
|
+
'with', 'draft', 'side', 'effects', 'target',
|
|
27
|
+
'mapping', 'from', 'numbering', 'managed', 'early', 'late',
|
|
28
|
+
'result', 'parameter', 'uses', 'augment',
|
|
29
|
+
// Feature modes
|
|
30
|
+
'instance', 'global', 'static',
|
|
31
|
+
// Triggers
|
|
32
|
+
'create', 'update', 'delete', 'activate', 'discard', 'edit', 'resume',
|
|
33
|
+
];
|
|
34
|
+
const grammar = {
|
|
35
|
+
case_insensitive: true,
|
|
36
|
+
keywords: {
|
|
37
|
+
keyword: BDEF_KEYWORDS.join(' '),
|
|
38
|
+
},
|
|
39
|
+
contains: [
|
|
40
|
+
// Line comments: //
|
|
41
|
+
{
|
|
42
|
+
className: 'comment',
|
|
43
|
+
begin: /\/\//,
|
|
44
|
+
end: /$/,
|
|
45
|
+
relevance: 5,
|
|
46
|
+
},
|
|
47
|
+
// Block comments: /* ... */
|
|
48
|
+
{
|
|
49
|
+
className: 'comment',
|
|
50
|
+
begin: /\/\*/,
|
|
51
|
+
end: /\*\//,
|
|
52
|
+
relevance: 5,
|
|
53
|
+
},
|
|
54
|
+
// Class/table names in double-quotes (e.g. implementation in class "zbp_sales_order")
|
|
55
|
+
{
|
|
56
|
+
className: 'string',
|
|
57
|
+
begin: /"/,
|
|
58
|
+
end: /"/,
|
|
59
|
+
relevance: 3,
|
|
60
|
+
},
|
|
61
|
+
// Z* / Y* identifiers (behavior entity names, class names, table names)
|
|
62
|
+
{
|
|
63
|
+
className: 'title',
|
|
64
|
+
begin: /\b[ZY][A-Za-z0-9_]{1,30}\b/,
|
|
65
|
+
relevance: 4,
|
|
66
|
+
},
|
|
67
|
+
// ZBP_ class references (behavior pool class naming convention)
|
|
68
|
+
{
|
|
69
|
+
className: 'title',
|
|
70
|
+
begin: /\bZBP_[A-Za-z0-9_]+\b/i,
|
|
71
|
+
relevance: 5,
|
|
72
|
+
},
|
|
73
|
+
// Numeric literals
|
|
74
|
+
{
|
|
75
|
+
className: 'number',
|
|
76
|
+
begin: /\b\d+\b/,
|
|
77
|
+
relevance: 2,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
export default grammar;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-highlight language definition for CDS (Core Data Services).
|
|
3
|
+
*
|
|
4
|
+
* Registered as 'cds'. Matches fenced code blocks with ```cds.
|
|
5
|
+
* Covers: define view/table syntax, @Annotations, association/composition,
|
|
6
|
+
* SQL-like keywords used in CDS projections.
|
|
7
|
+
*/
|
|
8
|
+
const CDS_KEYWORDS = [
|
|
9
|
+
'define', 'view', 'entity', 'as', 'select', 'from', 'projection', 'on',
|
|
10
|
+
'association', 'composition', 'to', 'parent', 'redirected',
|
|
11
|
+
'join', 'inner', 'left', 'outer', 'right', 'cross',
|
|
12
|
+
'where', 'group', 'by', 'having', 'union', 'all', 'distinct',
|
|
13
|
+
'extend', 'annotate', 'with', 'abstract',
|
|
14
|
+
'key', 'virtual', 'literal', 'cast', 'case', 'when', 'then', 'else', 'end',
|
|
15
|
+
'not', 'null', 'is', 'and', 'or', 'in', 'between', 'like',
|
|
16
|
+
'aspect', 'type', 'element', 'action', 'function', 'returns',
|
|
17
|
+
'service', 'namespace', 'using', 'context',
|
|
18
|
+
'managed', 'unmanaged', 'implementation', 'class', 'unique',
|
|
19
|
+
'draft', 'table', 'lock', 'authorization', 'master', 'instance',
|
|
20
|
+
'persistent', 'field', 'validation', 'determination',
|
|
21
|
+
'create', 'update', 'delete',
|
|
22
|
+
];
|
|
23
|
+
const grammar = {
|
|
24
|
+
case_insensitive: true,
|
|
25
|
+
keywords: {
|
|
26
|
+
keyword: CDS_KEYWORDS.join(' '),
|
|
27
|
+
},
|
|
28
|
+
contains: [
|
|
29
|
+
// Line comments: //
|
|
30
|
+
{
|
|
31
|
+
className: 'comment',
|
|
32
|
+
begin: /\/\//,
|
|
33
|
+
end: /$/,
|
|
34
|
+
relevance: 5,
|
|
35
|
+
},
|
|
36
|
+
// Block comments: /* ... */
|
|
37
|
+
{
|
|
38
|
+
className: 'comment',
|
|
39
|
+
begin: /\/\*/,
|
|
40
|
+
end: /\*\//,
|
|
41
|
+
relevance: 5,
|
|
42
|
+
},
|
|
43
|
+
// Annotations: @Something or @Something.nested or @Something.nested: value
|
|
44
|
+
// Annotation value strings handled below; here we color the annotation name.
|
|
45
|
+
{
|
|
46
|
+
className: 'meta',
|
|
47
|
+
begin: /@[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*/,
|
|
48
|
+
relevance: 8,
|
|
49
|
+
contains: [
|
|
50
|
+
// Annotation value strings inside annotations e.g. @EndUserText.label: 'Sales Order'
|
|
51
|
+
{
|
|
52
|
+
className: 'string',
|
|
53
|
+
begin: /'/,
|
|
54
|
+
end: /'/,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
// String literals
|
|
59
|
+
{
|
|
60
|
+
className: 'string',
|
|
61
|
+
begin: /'/,
|
|
62
|
+
end: /'/,
|
|
63
|
+
relevance: 3,
|
|
64
|
+
},
|
|
65
|
+
// Z* / Y* / C* identifiers (custom CDS view names)
|
|
66
|
+
{
|
|
67
|
+
className: 'title',
|
|
68
|
+
begin: /\b[ZYC][A-Za-z_][A-Za-z0-9_]{0,29}\b/,
|
|
69
|
+
relevance: 4,
|
|
70
|
+
},
|
|
71
|
+
// $projection, $session, $parameters pseudo-elements
|
|
72
|
+
{
|
|
73
|
+
className: 'variable',
|
|
74
|
+
begin: /\$[a-z_]+/,
|
|
75
|
+
relevance: 5,
|
|
76
|
+
},
|
|
77
|
+
// Numeric literals
|
|
78
|
+
{
|
|
79
|
+
className: 'number',
|
|
80
|
+
begin: /\b\d+(\.\d+)?\b/,
|
|
81
|
+
relevance: 2,
|
|
82
|
+
},
|
|
83
|
+
// Cardinality annotations like [0..1], [1..*]
|
|
84
|
+
{
|
|
85
|
+
className: 'number',
|
|
86
|
+
begin: /\[\d+\.\.[*\d]+\]/,
|
|
87
|
+
relevance: 3,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
export default grammar;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown → ANSI via marked + marked-terminal.
|
|
3
|
+
*
|
|
4
|
+
* marked-terminal@6 TerminalRenderer options accept CHALK STYLE FUNCTIONS (e.g.,
|
|
5
|
+
* chalk.cyan.bold) — NOT arg-taking callbacks. See
|
|
6
|
+
* https://github.com/mikaelbr/marked-terminal/blob/v6.2.0/README.md for the full
|
|
7
|
+
* option table. Arg-taking callbacks are allowed only for `list(body, ordered)`
|
|
8
|
+
* and `image(href, title, text)`; every other key is a wrapper applied to the
|
|
9
|
+
* already-rendered text.
|
|
10
|
+
*
|
|
11
|
+
* We cannot distinguish h1/h2/h3/h4 with a single chalk function, so we pick one
|
|
12
|
+
* styling for all heading levels — terminal output rarely needs a level hierarchy.
|
|
13
|
+
*/
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import { marked } from 'marked';
|
|
16
|
+
import { markedTerminal } from 'marked-terminal';
|
|
17
|
+
import Table from 'cli-table3';
|
|
18
|
+
import { applySeverityToCell } from './severity.js';
|
|
19
|
+
const PEACH = '#FFB88C';
|
|
20
|
+
// marked-terminal uses these sentinels internally. We parse them back out to
|
|
21
|
+
// rebuild a width-capped cli-table3 in our table renderer override below.
|
|
22
|
+
// Source: node_modules/marked-terminal/index.js lines 10-15.
|
|
23
|
+
const MT_CELL_SPLIT = '^*||*^';
|
|
24
|
+
const MT_ROW_WRAP_RE = new RegExp('\\*\\|\\*\\|\\*\\|\\*', 'g');
|
|
25
|
+
// marked-terminal replaces `:` in codespans with `*#COLON|*` before markdown
|
|
26
|
+
// parses the table (otherwise `:` would be read as column alignment syntax).
|
|
27
|
+
// It reverses this via `undoColon` inside its own transform pipeline; we must
|
|
28
|
+
// do the same in our custom table renderer or raw sentinels leak into cells.
|
|
29
|
+
const MT_COLON_RE = /\*#COLON\|\*/g;
|
|
30
|
+
const ANSI_RE = /\x1B\[[0-9;]*[mGKHFABCDsuJSTfrl]|\x1B\][^\x07]*\x07/g;
|
|
31
|
+
function stripAnsi(s) {
|
|
32
|
+
return s.replace(ANSI_RE, '');
|
|
33
|
+
}
|
|
34
|
+
/** Apply marked-terminal's transform pipeline (undoColon + HTML entity unescape). */
|
|
35
|
+
function mtTransform(s) {
|
|
36
|
+
return s
|
|
37
|
+
.replace(MT_COLON_RE, ':')
|
|
38
|
+
.replace(/&/g, '&')
|
|
39
|
+
.replace(/</g, '<')
|
|
40
|
+
.replace(/>/g, '>')
|
|
41
|
+
.replace(/"/g, '"')
|
|
42
|
+
.replace(/'/g, "'");
|
|
43
|
+
}
|
|
44
|
+
function parseMtTableRows(text, applyTransform = true) {
|
|
45
|
+
if (!text)
|
|
46
|
+
return [];
|
|
47
|
+
const rows = [];
|
|
48
|
+
const normalized = applyTransform ? mtTransform(text) : text;
|
|
49
|
+
for (const line of normalized.split('\n')) {
|
|
50
|
+
if (!line)
|
|
51
|
+
continue;
|
|
52
|
+
const cells = line.replace(MT_ROW_WRAP_RE, '').split(MT_CELL_SPLIT);
|
|
53
|
+
// marked-terminal's tablecell emits `content + TABLE_CELL_SPLIT` — split leaves
|
|
54
|
+
// one trailing empty element we drop.
|
|
55
|
+
rows.push(cells.slice(0, -1));
|
|
56
|
+
}
|
|
57
|
+
return rows;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Compute per-column widths that fit within the terminal.
|
|
61
|
+
*
|
|
62
|
+
* Strategy:
|
|
63
|
+
* 1. Natural: widest cell in each column (no wrapping).
|
|
64
|
+
* 2. Min: longest single word in each column (prevents mid-word truncation).
|
|
65
|
+
* 3. If natural + padding + borders ≤ termWidth → use natural.
|
|
66
|
+
* 4. Else → each column gets at least its min, remaining budget scales
|
|
67
|
+
* proportionally from (natural - min).
|
|
68
|
+
*
|
|
69
|
+
* cli-table3 layout: `│ <content> │ <content> │`
|
|
70
|
+
* - Each `│` separator is 1 char → borders = colCount + 1
|
|
71
|
+
* - Each cell has 1-char L-pad + content + 1-char R-pad
|
|
72
|
+
* - `colWidths[i]` in cli-table3 = content width + 2 (padding)
|
|
73
|
+
* - totalTableWidth = sum(colWidths) + colCount + 1
|
|
74
|
+
*/
|
|
75
|
+
function computeColWidths(naturalContentWidths, minContentWidths, termWidth) {
|
|
76
|
+
const colCount = naturalContentWidths.length;
|
|
77
|
+
const borders = colCount + 1;
|
|
78
|
+
const paddingTotal = colCount * 2;
|
|
79
|
+
const SAFETY = 2; // leave 2 cols headroom at the terminal right edge
|
|
80
|
+
const contentAvailable = Math.max(minContentWidths.reduce((a, b) => a + b, 0), termWidth - borders - paddingTotal - SAFETY);
|
|
81
|
+
const naturalSum = naturalContentWidths.reduce((a, b) => a + b, 0);
|
|
82
|
+
if (naturalSum <= contentAvailable) {
|
|
83
|
+
// Fits — use natural widths.
|
|
84
|
+
return naturalContentWidths.map((w) => w + 2);
|
|
85
|
+
}
|
|
86
|
+
// Overflow — each col gets its min, remainder distributed proportionally to
|
|
87
|
+
// the (natural - min) "stretch" of each column. Short columns (fixed-width
|
|
88
|
+
// severity labels, short IDs) stay at their min; long columns absorb the shrink.
|
|
89
|
+
const minSum = minContentWidths.reduce((a, b) => a + b, 0);
|
|
90
|
+
const stretchAvailable = Math.max(0, contentAvailable - minSum);
|
|
91
|
+
const stretchNatural = naturalSum - minSum;
|
|
92
|
+
const scale = stretchNatural > 0 ? stretchAvailable / stretchNatural : 0;
|
|
93
|
+
const widths = naturalContentWidths.map((nat, i) => {
|
|
94
|
+
const min = minContentWidths[i];
|
|
95
|
+
const extra = Math.floor((nat - min) * scale);
|
|
96
|
+
return min + Math.max(0, extra);
|
|
97
|
+
});
|
|
98
|
+
// Distribute rounding leftover to the widest natural column (so long-content
|
|
99
|
+
// columns like Detail get the extra space).
|
|
100
|
+
let leftover = contentAvailable - widths.reduce((a, b) => a + b, 0);
|
|
101
|
+
if (leftover > 0) {
|
|
102
|
+
const widestIdx = naturalContentWidths.indexOf(Math.max(...naturalContentWidths));
|
|
103
|
+
widths[widestIdx] += leftover;
|
|
104
|
+
}
|
|
105
|
+
return widths.map((w) => w + 2);
|
|
106
|
+
}
|
|
107
|
+
// Width for wrapping prose + list items. Use the terminal width minus a small
|
|
108
|
+
// safety margin so we don't render right up to the last column and get
|
|
109
|
+
// double-wrapped by the terminal. Snapshot at module-load — if the terminal
|
|
110
|
+
// is resized mid-session the wrap will be slightly off but still word-safe.
|
|
111
|
+
const PROSE_WIDTH = Math.max(60, (process.stdout.columns || 100) - 2);
|
|
112
|
+
marked.use(markedTerminal({
|
|
113
|
+
heading: chalk.cyan.bold,
|
|
114
|
+
firstHeading: chalk.hex(PEACH).bold,
|
|
115
|
+
// `strong` (markdown bold via `**...**`) was previously plain `chalk.bold`,
|
|
116
|
+
// which emits only `[1m`. On many terminals — and inside Ink's default
|
|
117
|
+
// <Text> wrap — bold weight on white text is visually indistinguishable
|
|
118
|
+
// from normal-weight white. Pair bold with the peach brand colour so
|
|
119
|
+
// labels like `**Headline:**` stand out even when bold weight isn't
|
|
120
|
+
// rendered strongly. Cheap, safe, and consistent with firstHeading.
|
|
121
|
+
strong: chalk.bold.hex(PEACH),
|
|
122
|
+
em: chalk.italic,
|
|
123
|
+
codespan: chalk.bgGray.white,
|
|
124
|
+
code: chalk.gray,
|
|
125
|
+
blockquote: chalk.gray.italic,
|
|
126
|
+
link: chalk.blue.underline,
|
|
127
|
+
href: chalk.blue.underline,
|
|
128
|
+
listitem: chalk.white,
|
|
129
|
+
hr: chalk.gray,
|
|
130
|
+
paragraph: chalk.white,
|
|
131
|
+
// Enable reflow so paragraphs and list items wrap at WORD boundaries at
|
|
132
|
+
// the configured width, and continuation lines of list items align under
|
|
133
|
+
// the bullet content (not at column 0). Without this, marked-terminal
|
|
134
|
+
// emits unwrapped text and the terminal hard-wraps at its column limit,
|
|
135
|
+
// producing mid-word breaks like "busine\nss".
|
|
136
|
+
reflowText: true,
|
|
137
|
+
width: PROSE_WIDTH,
|
|
138
|
+
// tableOptions is consumed by our override below, not marked-terminal's default
|
|
139
|
+
// renderer (we replace it in the next marked.use call).
|
|
140
|
+
}));
|
|
141
|
+
// Override marked-terminal's table renderer with one that caps column widths to the
|
|
142
|
+
// terminal width + word-wraps long cell content. marked-terminal's default auto-sizes
|
|
143
|
+
// each column to fit its widest cell without constraint, so a single long Detail cell
|
|
144
|
+
// (e.g. a radar finding with a 200-char explanation) produces a 235-column table that
|
|
145
|
+
// the terminal then wraps visually, destroying the box layout.
|
|
146
|
+
marked.use({
|
|
147
|
+
renderer: {
|
|
148
|
+
table(header, body) {
|
|
149
|
+
const headerRow = parseMtTableRows(header)[0] ?? [];
|
|
150
|
+
const rawBodyRows = parseMtTableRows(body);
|
|
151
|
+
if (headerRow.length === 0)
|
|
152
|
+
return `${header}${body}\n\n`;
|
|
153
|
+
// Apply severity pills to EACH CELL before cli-table3 sees them. If we
|
|
154
|
+
// left severity for the outer pipeline (applySeverity after markdown),
|
|
155
|
+
// it would wrap "HIGH" (4 chars) with " HIGH " (6 visible chars) AFTER
|
|
156
|
+
// cli-table3 had already allocated a 4-char column — the extra 2 chars
|
|
157
|
+
// then push the next `│` border right and break every following row's
|
|
158
|
+
// alignment. Applying here keeps visible widths correct via string-width.
|
|
159
|
+
const bodyRows = rawBodyRows.map((row) => row.map((c) => applySeverityToCell(c)));
|
|
160
|
+
// Natural content width per column (stripped of ANSI escapes) AND
|
|
161
|
+
// minimum word width per column (so wrap points never cut mid-word).
|
|
162
|
+
const colCount = headerRow.length;
|
|
163
|
+
const naturalWidths = new Array(colCount).fill(0);
|
|
164
|
+
const minWidths = new Array(colCount).fill(1);
|
|
165
|
+
for (const row of [headerRow, ...bodyRows]) {
|
|
166
|
+
for (let i = 0; i < colCount; i++) {
|
|
167
|
+
const content = row[i] ?? '';
|
|
168
|
+
const visible = stripAnsi(content);
|
|
169
|
+
if (visible.length > naturalWidths[i])
|
|
170
|
+
naturalWidths[i] = visible.length;
|
|
171
|
+
// Longest whitespace-separated token — the minimum column width that
|
|
172
|
+
// wordWrap can respect without mid-word breakage.
|
|
173
|
+
for (const word of visible.split(/\s+/)) {
|
|
174
|
+
if (word.length > minWidths[i])
|
|
175
|
+
minWidths[i] = word.length;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const termWidth = process.stdout.columns || 120;
|
|
180
|
+
const colWidths = computeColWidths(naturalWidths, minWidths, termWidth);
|
|
181
|
+
// Decide whether row separators are needed. When every row fits on one
|
|
182
|
+
// visual line we suppress them (avoids stairstep on small tables, see
|
|
183
|
+
// earlier comment). When ANY row will wrap (some cell content exceeds
|
|
184
|
+
// its allocated column width), the wrap line otherwise looks like a
|
|
185
|
+
// separate empty row to the reader — so re-enable thin separators
|
|
186
|
+
// between body rows. The header/body separator is suppressed in both
|
|
187
|
+
// cases (the cyan-bold header colour already separates them visually).
|
|
188
|
+
let anyRowWraps = false;
|
|
189
|
+
for (const row of bodyRows) {
|
|
190
|
+
for (let i = 0; i < colCount; i++) {
|
|
191
|
+
const visible = stripAnsi(row[i] ?? '');
|
|
192
|
+
// colWidths[i] = content width + 2 padding. Wrap happens when the
|
|
193
|
+
// visible content exceeds the content width (colWidths[i] - 2).
|
|
194
|
+
if (visible.length > (colWidths[i] - 2)) {
|
|
195
|
+
anyRowWraps = true;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (anyRowWraps)
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
const tableChars = anyRowWraps
|
|
203
|
+
? {} // default chars — keeps `mid: ─` so wrapped rows have separators
|
|
204
|
+
: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' };
|
|
205
|
+
const table = new Table({
|
|
206
|
+
head: headerRow,
|
|
207
|
+
colWidths,
|
|
208
|
+
wordWrap: true,
|
|
209
|
+
wrapOnWordBoundary: true,
|
|
210
|
+
style: { head: ['cyan', 'bold'], border: ['gray'] },
|
|
211
|
+
chars: tableChars,
|
|
212
|
+
});
|
|
213
|
+
for (const row of bodyRows)
|
|
214
|
+
table.push(row);
|
|
215
|
+
return zebraStripe(table.toString()) + '\n\n';
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
/**
|
|
220
|
+
* Apply subtle dark-gray background to every other body row.
|
|
221
|
+
*
|
|
222
|
+
* Since we suppressed the `├─┼─┤` separators (avoids stairstep on variable-height
|
|
223
|
+
* rows), the rows would otherwise visually blend together. Zebra striping
|
|
224
|
+
* distinguishes them without adding horizontal rules.
|
|
225
|
+
*
|
|
226
|
+
* Approach — post-process the rendered cli-table3 string:
|
|
227
|
+
* - First `│…│` line after the `┌─┐` top border = header (pass through).
|
|
228
|
+
* - Subsequent `│…│` lines up to the `└─┘` bottom = body.
|
|
229
|
+
* - A body line is a NEW row start when the first column (between the first
|
|
230
|
+
* two `│` chars) contains non-whitespace (cli-table3 pads continuation
|
|
231
|
+
* lines of multi-line cells with whitespace in non-wrapping columns).
|
|
232
|
+
* - Odd row indices get wrapped with `\x1b[48;5;235m … \x1b[49m`. Mid-line
|
|
233
|
+
* bg-resets (e.g. when a severity pill's `\x1b[49m` ends) are rewritten
|
|
234
|
+
* to re-apply the zebra bg so the reset doesn't punch a hole after the pill.
|
|
235
|
+
*/
|
|
236
|
+
const ZEBRA_BG = '\x1b[48;5;235m'; // dark gray — subtle but visible
|
|
237
|
+
const ZEBRA_RESET = '\x1b[49m';
|
|
238
|
+
function applyZebraBg(line) {
|
|
239
|
+
// Re-apply zebra bg after any in-line bg-reset or full-reset so the stripe
|
|
240
|
+
// survives through severity pills and inline-code spans.
|
|
241
|
+
const reZebraed = line
|
|
242
|
+
.replace(/\x1B\[49m/g, ZEBRA_BG)
|
|
243
|
+
.replace(/\x1B\[0m/g, '\x1B[0m' + ZEBRA_BG);
|
|
244
|
+
return ZEBRA_BG + reZebraed + ZEBRA_RESET;
|
|
245
|
+
}
|
|
246
|
+
function zebraStripe(tableText) {
|
|
247
|
+
const lines = tableText.split('\n');
|
|
248
|
+
const result = [];
|
|
249
|
+
let sawTop = false;
|
|
250
|
+
let sawHeader = false;
|
|
251
|
+
let rowIndex = -1;
|
|
252
|
+
for (const line of lines) {
|
|
253
|
+
const clean = stripAnsi(line);
|
|
254
|
+
if (clean.startsWith('┌')) {
|
|
255
|
+
sawTop = true;
|
|
256
|
+
result.push(line);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (clean.startsWith('└')) {
|
|
260
|
+
result.push(line);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (sawTop && !sawHeader && clean.startsWith('│')) {
|
|
264
|
+
// First `│…│` line after top border = header. Pass through (cyan+bold already applied).
|
|
265
|
+
result.push(line);
|
|
266
|
+
sawHeader = true;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (sawHeader && clean.startsWith('│')) {
|
|
270
|
+
// Detect new row: first cell (between first and second `│`) has non-whitespace.
|
|
271
|
+
const m = clean.match(/^│([^│]*)│/);
|
|
272
|
+
const firstCellTrimmed = m ? m[1].trim() : '';
|
|
273
|
+
if (firstCellTrimmed !== '')
|
|
274
|
+
rowIndex++;
|
|
275
|
+
if (rowIndex >= 0 && rowIndex % 2 === 1) {
|
|
276
|
+
result.push(applyZebraBg(line));
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
result.push(line);
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
// Pre-table blank lines, etc. — pass through.
|
|
284
|
+
result.push(line);
|
|
285
|
+
}
|
|
286
|
+
return result.join('\n');
|
|
287
|
+
}
|
|
288
|
+
export function renderMarkdown(input) {
|
|
289
|
+
const result = marked.parse(input);
|
|
290
|
+
return typeof result === 'string' ? result : input;
|
|
291
|
+
}
|