opencode-dux 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 +452 -0
- package/dist/agents/descriptions.d.ts +6 -0
- package/dist/agents/designer.d.ts +2 -0
- package/dist/agents/explorer.d.ts +2 -0
- package/dist/agents/fixer.d.ts +2 -0
- package/dist/agents/index.d.ts +22 -0
- package/dist/agents/interpreter.d.ts +2 -0
- package/dist/agents/librarian.d.ts +2 -0
- package/dist/agents/oracle.d.ts +2 -0
- package/dist/agents/orchestrator.d.ts +27 -0
- package/dist/agents/overrides.d.ts +18 -0
- package/dist/agents/prompt-blocks.d.ts +97 -0
- package/dist/agents/steward.d.ts +3 -0
- package/dist/cli/config-io.d.ts +24 -0
- package/dist/cli/config-manager.d.ts +4 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1006 -0
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/mcps.d.ts +13 -0
- package/dist/cli/model-key-normalization.d.ts +1 -0
- package/dist/cli/paths.d.ts +35 -0
- package/dist/cli/providers.d.ts +137 -0
- package/dist/cli/skills.d.ts +22 -0
- package/dist/cli/system.d.ts +5 -0
- package/dist/cli/types.d.ts +38 -0
- package/dist/config/constants.d.ts +12 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/loader.d.ts +40 -0
- package/dist/config/runtime-preset.d.ts +12 -0
- package/dist/config/schema.d.ts +281 -0
- package/dist/config/utils.d.ts +10 -0
- package/dist/discovery/local/types.d.ts +79 -0
- package/dist/discovery/local.d.ts +73 -0
- package/dist/discovery/mcp-servers.d.ts +88 -0
- package/dist/discovery/skills.d.ts +94 -0
- package/dist/hooks/apply-patch/codec.d.ts +7 -0
- package/dist/hooks/apply-patch/errors.d.ts +25 -0
- package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
- package/dist/hooks/apply-patch/index.d.ts +15 -0
- package/dist/hooks/apply-patch/matching.d.ts +26 -0
- package/dist/hooks/apply-patch/operations.d.ts +3 -0
- package/dist/hooks/apply-patch/patch.d.ts +2 -0
- package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
- package/dist/hooks/apply-patch/resolution.d.ts +19 -0
- package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
- package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
- package/dist/hooks/apply-patch/types.d.ts +80 -0
- package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
- package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
- package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
- package/dist/hooks/auto-update-checker/index.d.ts +18 -0
- package/dist/hooks/auto-update-checker/types.d.ts +22 -0
- package/dist/hooks/chat-headers.d.ts +16 -0
- package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
- package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
- package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
- package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
- package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
- package/dist/hooks/filter-available-skills/index.d.ts +32 -0
- package/dist/hooks/foreground-fallback/index.d.ts +72 -0
- package/dist/hooks/image-hook.d.ts +5 -0
- package/dist/hooks/index.d.ts +14 -0
- package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
- package/dist/hooks/json-error-recovery/index.d.ts +1 -0
- package/dist/hooks/phase-reminder/index.d.ts +26 -0
- package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
- package/dist/hooks/task-session-manager/index.d.ts +52 -0
- package/dist/hooks/todo-continuation/index.d.ts +53 -0
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31782 -0
- package/dist/mcp/context7.d.ts +6 -0
- package/dist/mcp/grep-app.d.ts +6 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/types.d.ts +12 -0
- package/dist/mcp/websearch.d.ts +9 -0
- package/dist/skills/registry.d.ts +29 -0
- package/dist/subscriptions/accounts-store.d.ts +57 -0
- package/dist/subscriptions/index.d.ts +13 -0
- package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
- package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
- package/dist/subscriptions/types.d.ts +115 -0
- package/dist/subscriptions/usage-service.d.ts +74 -0
- package/dist/tools/ast-grep/cli.d.ts +15 -0
- package/dist/tools/ast-grep/constants.d.ts +25 -0
- package/dist/tools/ast-grep/downloader.d.ts +5 -0
- package/dist/tools/ast-grep/index.d.ts +10 -0
- package/dist/tools/ast-grep/tools.d.ts +3 -0
- package/dist/tools/ast-grep/types.d.ts +30 -0
- package/dist/tools/ast-grep/utils.d.ts +4 -0
- package/dist/tools/delegate.d.ts +14 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/preset-manager.d.ts +27 -0
- package/dist/tools/smartfetch/binary.d.ts +3 -0
- package/dist/tools/smartfetch/cache.d.ts +6 -0
- package/dist/tools/smartfetch/constants.d.ts +12 -0
- package/dist/tools/smartfetch/index.d.ts +3 -0
- package/dist/tools/smartfetch/network.d.ts +38 -0
- package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
- package/dist/tools/smartfetch/tool.d.ts +3 -0
- package/dist/tools/smartfetch/types.d.ts +122 -0
- package/dist/tools/smartfetch/utils.d.ts +18 -0
- package/dist/tui-state.d.ts +168 -0
- package/dist/tui.d.ts +37 -0
- package/dist/tui.js +1896 -0
- package/dist/utils/agent-variant.d.ts +63 -0
- package/dist/utils/compat.d.ts +30 -0
- package/dist/utils/env.d.ts +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/internal-initiator.d.ts +6 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/polling.d.ts +21 -0
- package/dist/utils/session-manager.d.ts +55 -0
- package/dist/utils/session.d.ts +90 -0
- package/dist/utils/subagent-depth.d.ts +35 -0
- package/dist/utils/system-collapse.d.ts +6 -0
- package/dist/utils/task.d.ts +4 -0
- package/dist/utils/zip-extractor.d.ts +1 -0
- package/index.ts +1 -0
- package/opencode-dux.schema.json +634 -0
- package/package.json +103 -0
- package/src/agents/descriptions.ts +55 -0
- package/src/agents/designer.test.ts +86 -0
- package/src/agents/designer.ts +154 -0
- package/src/agents/display-name.test.ts +186 -0
- package/src/agents/explorer.test.ts +79 -0
- package/src/agents/explorer.ts +144 -0
- package/src/agents/fixer.test.ts +79 -0
- package/src/agents/fixer.ts +145 -0
- package/src/agents/index.test.ts +472 -0
- package/src/agents/index.ts +248 -0
- package/src/agents/interpreter.ts +136 -0
- package/src/agents/librarian.test.ts +80 -0
- package/src/agents/librarian.ts +145 -0
- package/src/agents/oracle.test.ts +89 -0
- package/src/agents/oracle.ts +184 -0
- package/src/agents/orchestrator.test.ts +116 -0
- package/src/agents/orchestrator.ts +574 -0
- package/src/agents/overrides.ts +95 -0
- package/src/agents/prompt-blocks.test.ts +114 -0
- package/src/agents/prompt-blocks.ts +640 -0
- package/src/agents/steward.ts +146 -0
- package/src/cli/config-io.test.ts +536 -0
- package/src/cli/config-io.ts +473 -0
- package/src/cli/config-manager.test.ts +141 -0
- package/src/cli/config-manager.ts +4 -0
- package/src/cli/index.ts +88 -0
- package/src/cli/install.ts +282 -0
- package/src/cli/mcps.test.ts +62 -0
- package/src/cli/mcps.ts +39 -0
- package/src/cli/model-key-normalization.test.ts +21 -0
- package/src/cli/model-key-normalization.ts +60 -0
- package/src/cli/paths.test.ts +167 -0
- package/src/cli/paths.ts +144 -0
- package/src/cli/providers.test.ts +118 -0
- package/src/cli/providers.ts +141 -0
- package/src/cli/skills.test.ts +111 -0
- package/src/cli/skills.ts +103 -0
- package/src/cli/system.test.ts +91 -0
- package/src/cli/system.ts +180 -0
- package/src/cli/types.ts +43 -0
- package/src/config/constants.ts +58 -0
- package/src/config/index.ts +4 -0
- package/src/config/loader.test.ts +1194 -0
- package/src/config/loader.ts +269 -0
- package/src/config/model-resolution.test.ts +176 -0
- package/src/config/runtime-preset.test.ts +61 -0
- package/src/config/runtime-preset.ts +37 -0
- package/src/config/schema.ts +248 -0
- package/src/config/utils.test.ts +41 -0
- package/src/config/utils.ts +23 -0
- package/src/discovery/local/types.ts +85 -0
- package/src/discovery/local.ts +322 -0
- package/src/discovery/mcp-servers.ts +804 -0
- package/src/discovery/skills.ts +959 -0
- package/src/hooks/apply-patch/codec.test.ts +184 -0
- package/src/hooks/apply-patch/codec.ts +352 -0
- package/src/hooks/apply-patch/errors.ts +117 -0
- package/src/hooks/apply-patch/execution-context.ts +432 -0
- package/src/hooks/apply-patch/hook.test.ts +768 -0
- package/src/hooks/apply-patch/index.ts +126 -0
- package/src/hooks/apply-patch/matching.test.ts +215 -0
- package/src/hooks/apply-patch/matching.ts +586 -0
- package/src/hooks/apply-patch/operations.test.ts +1535 -0
- package/src/hooks/apply-patch/operations.ts +3 -0
- package/src/hooks/apply-patch/patch.ts +9 -0
- package/src/hooks/apply-patch/prepared-changes.ts +400 -0
- package/src/hooks/apply-patch/resolution.test.ts +420 -0
- package/src/hooks/apply-patch/resolution.ts +437 -0
- package/src/hooks/apply-patch/rewrite.ts +496 -0
- package/src/hooks/apply-patch/test-helpers.ts +52 -0
- package/src/hooks/apply-patch/types.ts +111 -0
- package/src/hooks/auto-update-checker/cache.test.ts +179 -0
- package/src/hooks/auto-update-checker/cache.ts +188 -0
- package/src/hooks/auto-update-checker/checker.test.ts +159 -0
- package/src/hooks/auto-update-checker/checker.ts +308 -0
- package/src/hooks/auto-update-checker/constants.ts +33 -0
- package/src/hooks/auto-update-checker/index.test.ts +282 -0
- package/src/hooks/auto-update-checker/index.ts +225 -0
- package/src/hooks/auto-update-checker/types.ts +26 -0
- package/src/hooks/chat-headers.test.ts +236 -0
- package/src/hooks/chat-headers.ts +97 -0
- package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
- package/src/hooks/context-pressure-reminder/index.ts +137 -0
- package/src/hooks/delegate-task-retry/guidance.ts +41 -0
- package/src/hooks/delegate-task-retry/hook.ts +23 -0
- package/src/hooks/delegate-task-retry/index.test.ts +38 -0
- package/src/hooks/delegate-task-retry/index.ts +7 -0
- package/src/hooks/delegate-task-retry/patterns.ts +79 -0
- package/src/hooks/filter-available-skills/index.test.ts +297 -0
- package/src/hooks/filter-available-skills/index.ts +160 -0
- package/src/hooks/foreground-fallback/index.test.ts +624 -0
- package/src/hooks/foreground-fallback/index.ts +374 -0
- package/src/hooks/image-hook.ts +6 -0
- package/src/hooks/index.ts +17 -0
- package/src/hooks/json-error-recovery/hook.ts +73 -0
- package/src/hooks/json-error-recovery/index.test.ts +111 -0
- package/src/hooks/json-error-recovery/index.ts +6 -0
- package/src/hooks/phase-reminder/index.test.ts +74 -0
- package/src/hooks/phase-reminder/index.ts +85 -0
- package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
- package/src/hooks/post-file-tool-nudge/index.ts +63 -0
- package/src/hooks/task-session-manager/index.test.ts +833 -0
- package/src/hooks/task-session-manager/index.ts +434 -0
- package/src/hooks/todo-continuation/index.test.ts +3026 -0
- package/src/hooks/todo-continuation/index.ts +878 -0
- package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
- package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
- package/src/index.ts +1672 -0
- package/src/mcp/context7.ts +14 -0
- package/src/mcp/grep-app.ts +11 -0
- package/src/mcp/index.test.ts +96 -0
- package/src/mcp/index.ts +66 -0
- package/src/mcp/types.ts +16 -0
- package/src/mcp/websearch.ts +47 -0
- package/src/skills/codemap/README.md +60 -0
- package/src/skills/codemap/SKILL.md +174 -0
- package/src/skills/codemap/scripts/codemap.mjs +483 -0
- package/src/skills/codemap/scripts/codemap.test.ts +129 -0
- package/src/skills/registry.ts +218 -0
- package/src/skills/simplify/README.md +19 -0
- package/src/skills/simplify/SKILL.md +138 -0
- package/src/subscriptions/accounts-store.test.ts +236 -0
- package/src/subscriptions/accounts-store.ts +184 -0
- package/src/subscriptions/index.ts +30 -0
- package/src/subscriptions/neuralwatt-scraper.ts +108 -0
- package/src/subscriptions/opencode-go-scraper.ts +301 -0
- package/src/subscriptions/types.ts +145 -0
- package/src/subscriptions/usage-service.test.ts +202 -0
- package/src/subscriptions/usage-service.ts +651 -0
- package/src/tools/ast-grep/cli.ts +257 -0
- package/src/tools/ast-grep/constants.ts +214 -0
- package/src/tools/ast-grep/downloader.ts +131 -0
- package/src/tools/ast-grep/index.ts +24 -0
- package/src/tools/ast-grep/tools.ts +117 -0
- package/src/tools/ast-grep/types.ts +51 -0
- package/src/tools/ast-grep/utils.ts +126 -0
- package/src/tools/delegate-handoff.test.ts +18 -0
- package/src/tools/delegate.ts +508 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/preset-manager.test.ts +795 -0
- package/src/tools/preset-manager.ts +332 -0
- package/src/tools/smartfetch/binary.ts +58 -0
- package/src/tools/smartfetch/cache.test.ts +34 -0
- package/src/tools/smartfetch/cache.ts +112 -0
- package/src/tools/smartfetch/constants.ts +29 -0
- package/src/tools/smartfetch/index.ts +8 -0
- package/src/tools/smartfetch/network.test.ts +178 -0
- package/src/tools/smartfetch/network.ts +614 -0
- package/src/tools/smartfetch/secondary-model.test.ts +85 -0
- package/src/tools/smartfetch/secondary-model.ts +276 -0
- package/src/tools/smartfetch/tool.test.ts +60 -0
- package/src/tools/smartfetch/tool.ts +832 -0
- package/src/tools/smartfetch/types.ts +135 -0
- package/src/tools/smartfetch/utils.test.ts +24 -0
- package/src/tools/smartfetch/utils.ts +456 -0
- package/src/tui-state.test.ts +867 -0
- package/src/tui-state.ts +1255 -0
- package/src/tui.test.ts +336 -0
- package/src/tui.ts +1539 -0
- package/src/utils/agent-variant.test.ts +244 -0
- package/src/utils/agent-variant.ts +187 -0
- package/src/utils/compat.ts +91 -0
- package/src/utils/env.ts +12 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/internal-initiator.ts +28 -0
- package/src/utils/logger.test.ts +220 -0
- package/src/utils/logger.ts +136 -0
- package/src/utils/polling.test.ts +191 -0
- package/src/utils/polling.ts +67 -0
- package/src/utils/session-manager.test.ts +173 -0
- package/src/utils/session-manager.ts +356 -0
- package/src/utils/session.test.ts +110 -0
- package/src/utils/session.ts +389 -0
- package/src/utils/subagent-depth.test.ts +170 -0
- package/src/utils/subagent-depth.ts +75 -0
- package/src/utils/system-collapse.test.ts +86 -0
- package/src/utils/system-collapse.ts +24 -0
- package/src/utils/task.test.ts +24 -0
- package/src/utils/task.ts +20 -0
- package/src/utils/zip-extractor.ts +102 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
formatPatch,
|
|
5
|
+
normalizeUnicode,
|
|
6
|
+
parsePatch,
|
|
7
|
+
parsePatchStrict,
|
|
8
|
+
stripHeredoc,
|
|
9
|
+
} from './codec';
|
|
10
|
+
import type { ParsedPatch } from './types';
|
|
11
|
+
|
|
12
|
+
describe('apply-patch/codec', () => {
|
|
13
|
+
test('stripHeredoc extracts the real patch content', () => {
|
|
14
|
+
expect(
|
|
15
|
+
stripHeredoc(`cat <<'PATCH'
|
|
16
|
+
*** Begin Patch
|
|
17
|
+
*** End Patch
|
|
18
|
+
PATCH`),
|
|
19
|
+
).toBe('*** Begin Patch\n*** End Patch');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('parsePatch recognizes add delete update and move', () => {
|
|
23
|
+
const parsed = parsePatch(`*** Begin Patch
|
|
24
|
+
*** Add File: added.txt
|
|
25
|
+
+alpha
|
|
26
|
+
*** Delete File: removed.txt
|
|
27
|
+
*** Update File: before.txt
|
|
28
|
+
*** Move to: after.txt
|
|
29
|
+
@@ ctx
|
|
30
|
+
line-a
|
|
31
|
+
-line-b
|
|
32
|
+
+line-c
|
|
33
|
+
*** End of File
|
|
34
|
+
*** End Patch`);
|
|
35
|
+
|
|
36
|
+
expect(parsed.hunks).toHaveLength(3);
|
|
37
|
+
expect(parsed.hunks[0]).toEqual({
|
|
38
|
+
type: 'add',
|
|
39
|
+
path: 'added.txt',
|
|
40
|
+
contents: 'alpha',
|
|
41
|
+
});
|
|
42
|
+
expect(parsed.hunks[1]).toEqual({ type: 'delete', path: 'removed.txt' });
|
|
43
|
+
expect(parsed.hunks[2]).toEqual({
|
|
44
|
+
type: 'update',
|
|
45
|
+
path: 'before.txt',
|
|
46
|
+
move_path: 'after.txt',
|
|
47
|
+
chunks: [
|
|
48
|
+
{
|
|
49
|
+
old_lines: ['line-a', 'line-b'],
|
|
50
|
+
new_lines: ['line-a', 'line-c'],
|
|
51
|
+
change_context: 'ctx',
|
|
52
|
+
is_end_of_file: true,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('parsePatch tolerates heredocs with aggressive CRLF and preserves EOF', () => {
|
|
59
|
+
const parsed = parsePatch(`cat <<'PATCH'\r
|
|
60
|
+
*** Begin Patch\r
|
|
61
|
+
*** Update File: sample.txt\r
|
|
62
|
+
@@\r
|
|
63
|
+
-alpha\r
|
|
64
|
+
+beta\r
|
|
65
|
+
*** End of File\r
|
|
66
|
+
*** End Patch\r
|
|
67
|
+
PATCH`);
|
|
68
|
+
|
|
69
|
+
expect(parsed.hunks).toEqual([
|
|
70
|
+
{
|
|
71
|
+
type: 'update',
|
|
72
|
+
path: 'sample.txt',
|
|
73
|
+
chunks: [
|
|
74
|
+
{
|
|
75
|
+
old_lines: ['alpha'],
|
|
76
|
+
new_lines: ['beta'],
|
|
77
|
+
change_context: undefined,
|
|
78
|
+
is_end_of_file: true,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('parsePatchStrict preserves End Patch text when it is hunk context', () => {
|
|
86
|
+
const markerPadding = ' ';
|
|
87
|
+
const parsed = parsePatchStrict(`*** Begin Patch${markerPadding}
|
|
88
|
+
*** Update File: sample.txt
|
|
89
|
+
@@ marker
|
|
90
|
+
*** End Patch
|
|
91
|
+
keep
|
|
92
|
+
*** End Patch${markerPadding}`);
|
|
93
|
+
|
|
94
|
+
expect(parsed.hunks).toEqual([
|
|
95
|
+
{
|
|
96
|
+
type: 'update',
|
|
97
|
+
path: 'sample.txt',
|
|
98
|
+
chunks: [
|
|
99
|
+
{
|
|
100
|
+
old_lines: ['*** End Patch', 'keep'],
|
|
101
|
+
new_lines: ['*** End Patch', 'keep'],
|
|
102
|
+
change_context: 'marker',
|
|
103
|
+
is_end_of_file: undefined,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('parsePatchStrict fails on garbage inside @@', () => {
|
|
111
|
+
expect(() =>
|
|
112
|
+
parsePatchStrict(`*** Begin Patch
|
|
113
|
+
*** Update File: sample.txt
|
|
114
|
+
@@
|
|
115
|
+
-alpha
|
|
116
|
+
garbage
|
|
117
|
+
+beta
|
|
118
|
+
*** End Patch`),
|
|
119
|
+
).toThrow('unexpected line in patch chunk');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('parsePatchStrict fails on garbage inside Add File', () => {
|
|
123
|
+
expect(() =>
|
|
124
|
+
parsePatchStrict(`*** Begin Patch
|
|
125
|
+
*** Add File: sample.txt
|
|
126
|
+
+alpha
|
|
127
|
+
garbage
|
|
128
|
+
*** End Patch`),
|
|
129
|
+
).toThrow('unexpected line in Add File body');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('parsePatchStrict fails on malformed Delete File', () => {
|
|
133
|
+
expect(() =>
|
|
134
|
+
parsePatchStrict(`*** Begin Patch
|
|
135
|
+
*** Delete File: sample.txt
|
|
136
|
+
+ghost
|
|
137
|
+
*** End Patch`),
|
|
138
|
+
).toThrow('unexpected line between hunks');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('parsePatchStrict fails on garbage after End Patch', () => {
|
|
142
|
+
expect(() =>
|
|
143
|
+
parsePatchStrict(`*** Begin Patch
|
|
144
|
+
*** Delete File: sample.txt
|
|
145
|
+
*** End Patch
|
|
146
|
+
garbage`),
|
|
147
|
+
).toThrow('unexpected line after End Patch');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('parsePatchStrict fails when Update File has no @@ chunks', () => {
|
|
151
|
+
expect(() =>
|
|
152
|
+
parsePatchStrict(`*** Begin Patch
|
|
153
|
+
*** Update File: sample.txt
|
|
154
|
+
*** End Patch`),
|
|
155
|
+
).toThrow('missing @@ chunk body');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('formatPatch allows stable parse -> format -> parse roundtrips', () => {
|
|
159
|
+
const parsed: ParsedPatch = {
|
|
160
|
+
hunks: [
|
|
161
|
+
{
|
|
162
|
+
type: 'update',
|
|
163
|
+
path: 'sample.txt',
|
|
164
|
+
chunks: [
|
|
165
|
+
{
|
|
166
|
+
old_lines: ['alpha', 'beta'],
|
|
167
|
+
new_lines: ['alpha', 'BETA'],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
expect(parsePatch(formatPatch(parsed))).toEqual(parsed);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('normalizeUnicode unifies expected typographic variants', () => {
|
|
178
|
+
expect(normalizeUnicode('“uno”…\u00A0dos-tres')).toBe('"uno"... dos-tres');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('normalizeUnicode covers less common typographic variants', () => {
|
|
182
|
+
expect(normalizeUnicode('‛uno‟―dos')).toBe(`'uno"-dos`);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import type { ParsedPatch, PatchChunk, PatchHunk } from './types';
|
|
2
|
+
|
|
3
|
+
type ParseMode = 'permissive' | 'strict';
|
|
4
|
+
|
|
5
|
+
function normalizeLineEndings(text: string): string {
|
|
6
|
+
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function normalizeUnicode(text: string): string {
|
|
10
|
+
return text
|
|
11
|
+
.replace(/[\u2018\u2019\u201A\u201B]/g, "'")
|
|
12
|
+
.replace(/[\u201C\u201D\u201E\u201F]/g, '"')
|
|
13
|
+
.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/g, '-')
|
|
14
|
+
.replace(/\u2026/g, '...')
|
|
15
|
+
.replace(/\u00A0/g, ' ');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function stripHeredoc(input: string): string {
|
|
19
|
+
const normalized = normalizeLineEndings(input);
|
|
20
|
+
const match = normalized.match(
|
|
21
|
+
/^(?:cat\s+)?<<['"]?(\w+)['"]?\s*\n([\s\S]*?)\n\1\s*$/,
|
|
22
|
+
);
|
|
23
|
+
return match ? match[2] : normalized;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function normalizePatchText(patchText: string): string {
|
|
27
|
+
return stripHeredoc(normalizeLineEndings(patchText).trim());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseHeader(lines: string[], index: number) {
|
|
31
|
+
const line = lines[index];
|
|
32
|
+
|
|
33
|
+
if (line.startsWith('*** Add File:')) {
|
|
34
|
+
const file = line.slice('*** Add File:'.length).trim();
|
|
35
|
+
return file ? { file, next: index + 1 } : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (line.startsWith('*** Delete File:')) {
|
|
39
|
+
const file = line.slice('*** Delete File:'.length).trim();
|
|
40
|
+
return file ? { file, next: index + 1 } : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (line.startsWith('*** Update File:')) {
|
|
44
|
+
const file = line.slice('*** Update File:'.length).trim();
|
|
45
|
+
let move: string | undefined;
|
|
46
|
+
let next = index + 1;
|
|
47
|
+
|
|
48
|
+
if (next < lines.length && lines[next].startsWith('*** Move to:')) {
|
|
49
|
+
const moveTarget = lines[next].slice('*** Move to:'.length).trim();
|
|
50
|
+
if (!moveTarget) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
move = moveTarget;
|
|
55
|
+
next += 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return file ? { file, move, next } : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function unexpectedPatchLine(context: string, line: string): never {
|
|
65
|
+
const rendered = line.length === 0 ? '<empty>' : line;
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Invalid patch format: unexpected line ${context}: ${rendered}`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseChangeContext(line: string): string | undefined {
|
|
72
|
+
const context = line.slice(2);
|
|
73
|
+
if (context.length === 0) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return context.startsWith(' ') ? context.slice(1) || undefined : context;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isPatchBoundary(line: string, marker: string): boolean {
|
|
81
|
+
return line.trimEnd() === marker;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseChunks(lines: string[], index: number, mode: ParseMode) {
|
|
85
|
+
const chunks: PatchChunk[] = [];
|
|
86
|
+
let at = index;
|
|
87
|
+
|
|
88
|
+
while (at < lines.length && !lines[at].startsWith('***')) {
|
|
89
|
+
if (!lines[at].startsWith('@@')) {
|
|
90
|
+
if (mode === 'strict') {
|
|
91
|
+
unexpectedPatchLine('in update body', lines[at]);
|
|
92
|
+
}
|
|
93
|
+
at += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const context = parseChangeContext(lines[at]);
|
|
98
|
+
at += 1;
|
|
99
|
+
|
|
100
|
+
const old_lines: string[] = [];
|
|
101
|
+
const new_lines: string[] = [];
|
|
102
|
+
let eof = false;
|
|
103
|
+
|
|
104
|
+
while (
|
|
105
|
+
at < lines.length &&
|
|
106
|
+
!lines[at].startsWith('@@') &&
|
|
107
|
+
(!lines[at].startsWith('***') || lines[at] === '*** End of File')
|
|
108
|
+
) {
|
|
109
|
+
const line = lines[at];
|
|
110
|
+
|
|
111
|
+
if (line === '*** End of File') {
|
|
112
|
+
eof = true;
|
|
113
|
+
at += 1;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (line.startsWith(' ')) {
|
|
118
|
+
old_lines.push(line.slice(1));
|
|
119
|
+
new_lines.push(line.slice(1));
|
|
120
|
+
at += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (line.startsWith('-')) {
|
|
125
|
+
old_lines.push(line.slice(1));
|
|
126
|
+
at += 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (line.startsWith('+')) {
|
|
131
|
+
new_lines.push(line.slice(1));
|
|
132
|
+
at += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (mode === 'strict') {
|
|
137
|
+
unexpectedPatchLine('in patch chunk', line);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
at += 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
chunks.push({
|
|
144
|
+
old_lines,
|
|
145
|
+
new_lines,
|
|
146
|
+
change_context: context,
|
|
147
|
+
is_end_of_file: eof || undefined,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { chunks, next: at };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function parseAdd(lines: string[], index: number, mode: ParseMode) {
|
|
155
|
+
const contents: string[] = [];
|
|
156
|
+
let at = index;
|
|
157
|
+
|
|
158
|
+
while (at < lines.length && !lines[at].startsWith('***')) {
|
|
159
|
+
if (lines[at].startsWith('+')) {
|
|
160
|
+
contents.push(lines[at].slice(1));
|
|
161
|
+
at += 1;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (mode === 'strict') {
|
|
166
|
+
unexpectedPatchLine('in Add File body', lines[at]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
at += 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { content: contents.join('\n'), next: at };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function parsePatchInternal(patchText: string, mode: ParseMode): ParsedPatch {
|
|
176
|
+
const clean = normalizePatchText(patchText);
|
|
177
|
+
const lines = clean.split('\n');
|
|
178
|
+
const begin = lines.findIndex((line) =>
|
|
179
|
+
isPatchBoundary(line, '*** Begin Patch'),
|
|
180
|
+
);
|
|
181
|
+
const end = lines.findIndex(
|
|
182
|
+
(line, index) => index > begin && isPatchBoundary(line, '*** End Patch'),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (begin === -1 || end === -1 || begin >= end) {
|
|
186
|
+
throw new Error('Invalid patch format: missing Begin/End markers');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (mode === 'strict') {
|
|
190
|
+
for (const line of lines.slice(0, begin)) {
|
|
191
|
+
unexpectedPatchLine('before Begin Patch', line);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const line of lines.slice(end + 1)) {
|
|
195
|
+
unexpectedPatchLine('after End Patch', line);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const hunks: PatchHunk[] = [];
|
|
200
|
+
let index = begin + 1;
|
|
201
|
+
|
|
202
|
+
while (index < end) {
|
|
203
|
+
const header = parseHeader(lines, index);
|
|
204
|
+
|
|
205
|
+
if (!header) {
|
|
206
|
+
if (mode === 'strict') {
|
|
207
|
+
unexpectedPatchLine('between hunks', lines[index]);
|
|
208
|
+
}
|
|
209
|
+
index += 1;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (lines[index].startsWith('*** Add File:')) {
|
|
214
|
+
const next = parseAdd(lines, header.next, mode);
|
|
215
|
+
hunks.push({
|
|
216
|
+
type: 'add',
|
|
217
|
+
path: header.file,
|
|
218
|
+
contents: next.content,
|
|
219
|
+
});
|
|
220
|
+
index = next.next;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (lines[index].startsWith('*** Delete File:')) {
|
|
225
|
+
hunks.push({ type: 'delete', path: header.file });
|
|
226
|
+
index = header.next;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const next = parseChunks(lines, header.next, mode);
|
|
231
|
+
if (mode === 'strict' && next.chunks.length === 0) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Invalid patch format: Update File is missing @@ chunk body: ${header.file}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
hunks.push({
|
|
238
|
+
type: 'update',
|
|
239
|
+
path: header.file,
|
|
240
|
+
move_path: header.move,
|
|
241
|
+
chunks: next.chunks,
|
|
242
|
+
});
|
|
243
|
+
index = next.next;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { hunks };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function parsePatch(patchText: string): ParsedPatch {
|
|
250
|
+
return parsePatchInternal(patchText, 'permissive');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function parsePatchStrict(patchText: string): ParsedPatch {
|
|
254
|
+
return parsePatchInternal(patchText, 'strict');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function diffMatrix(old_lines: string[], new_lines: string[]): number[][] {
|
|
258
|
+
const dp = Array.from({ length: old_lines.length + 1 }, () =>
|
|
259
|
+
Array<number>(new_lines.length + 1).fill(0),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
for (let oldIndex = 1; oldIndex <= old_lines.length; oldIndex += 1) {
|
|
263
|
+
for (let newIndex = 1; newIndex <= new_lines.length; newIndex += 1) {
|
|
264
|
+
dp[oldIndex][newIndex] =
|
|
265
|
+
old_lines[oldIndex - 1] === new_lines[newIndex - 1]
|
|
266
|
+
? dp[oldIndex - 1][newIndex - 1] + 1
|
|
267
|
+
: Math.max(dp[oldIndex - 1][newIndex], dp[oldIndex][newIndex - 1]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return dp;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function renderChunk(chunk: PatchChunk): string[] {
|
|
275
|
+
const lines = [chunk.change_context ? `@@ ${chunk.change_context}` : '@@'];
|
|
276
|
+
const dp = diffMatrix(chunk.old_lines, chunk.new_lines);
|
|
277
|
+
const body: string[] = [];
|
|
278
|
+
let oldIndex = chunk.old_lines.length;
|
|
279
|
+
let newIndex = chunk.new_lines.length;
|
|
280
|
+
|
|
281
|
+
while (oldIndex > 0 && newIndex > 0) {
|
|
282
|
+
if (chunk.old_lines[oldIndex - 1] === chunk.new_lines[newIndex - 1]) {
|
|
283
|
+
body.push(` ${chunk.old_lines[oldIndex - 1]}`);
|
|
284
|
+
oldIndex -= 1;
|
|
285
|
+
newIndex -= 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (dp[oldIndex - 1][newIndex] >= dp[oldIndex][newIndex - 1]) {
|
|
290
|
+
body.push(`-${chunk.old_lines[oldIndex - 1]}`);
|
|
291
|
+
oldIndex -= 1;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
body.push(`+${chunk.new_lines[newIndex - 1]}`);
|
|
296
|
+
newIndex -= 1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
while (oldIndex > 0) {
|
|
300
|
+
body.push(`-${chunk.old_lines[oldIndex - 1]}`);
|
|
301
|
+
oldIndex -= 1;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
while (newIndex > 0) {
|
|
305
|
+
body.push(`+${chunk.new_lines[newIndex - 1]}`);
|
|
306
|
+
newIndex -= 1;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
lines.push(...body.reverse());
|
|
310
|
+
|
|
311
|
+
if (chunk.is_end_of_file) {
|
|
312
|
+
lines.push('*** End of File');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return lines;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function renderAddContents(contents: string): string[] {
|
|
319
|
+
if (contents.length === 0) {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return contents.split('\n').map((line) => `+${line}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function formatPatch(patch: ParsedPatch): string {
|
|
327
|
+
const lines = ['*** Begin Patch'];
|
|
328
|
+
|
|
329
|
+
for (const hunk of patch.hunks) {
|
|
330
|
+
if (hunk.type === 'add') {
|
|
331
|
+
lines.push(`*** Add File: ${hunk.path}`);
|
|
332
|
+
lines.push(...renderAddContents(hunk.contents));
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (hunk.type === 'delete') {
|
|
337
|
+
lines.push(`*** Delete File: ${hunk.path}`);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
lines.push(`*** Update File: ${hunk.path}`);
|
|
342
|
+
if (hunk.move_path) {
|
|
343
|
+
lines.push(`*** Move to: ${hunk.move_path}`);
|
|
344
|
+
}
|
|
345
|
+
for (const chunk of hunk.chunks) {
|
|
346
|
+
lines.push(...renderChunk(chunk));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
lines.push('*** End Patch');
|
|
351
|
+
return lines.join('\n');
|
|
352
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ApplyPatchErrorCode, ApplyPatchErrorKind } from './types';
|
|
2
|
+
|
|
3
|
+
const APPLY_PATCH_ERROR_PREFIX: Record<ApplyPatchErrorKind, string> = {
|
|
4
|
+
blocked: 'apply_patch blocked',
|
|
5
|
+
validation: 'apply_patch validation failed',
|
|
6
|
+
verification: 'apply_patch verification failed',
|
|
7
|
+
internal: 'apply_patch internal error',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class ApplyPatchError extends Error {
|
|
11
|
+
override readonly cause?: unknown;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
readonly kind: ApplyPatchErrorKind,
|
|
15
|
+
readonly code: ApplyPatchErrorCode,
|
|
16
|
+
message: string,
|
|
17
|
+
options?: {
|
|
18
|
+
cause?: unknown;
|
|
19
|
+
},
|
|
20
|
+
) {
|
|
21
|
+
super(`${APPLY_PATCH_ERROR_PREFIX[kind]}: ${message}`);
|
|
22
|
+
this.name = 'ApplyPatchError';
|
|
23
|
+
this.cause = options?.cause;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getErrorMessage(error: unknown): string {
|
|
28
|
+
return error instanceof Error ? error.message : String(error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createApplyPatchBlockedError(
|
|
32
|
+
message: string,
|
|
33
|
+
cause?: unknown,
|
|
34
|
+
): ApplyPatchError {
|
|
35
|
+
return new ApplyPatchError('blocked', 'outside_workspace', message, {
|
|
36
|
+
cause,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createApplyPatchValidationError(
|
|
41
|
+
message: string,
|
|
42
|
+
cause?: unknown,
|
|
43
|
+
): ApplyPatchError {
|
|
44
|
+
return new ApplyPatchError('validation', 'malformed_patch', message, {
|
|
45
|
+
cause,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createApplyPatchVerificationError(
|
|
50
|
+
message: string,
|
|
51
|
+
cause?: unknown,
|
|
52
|
+
): ApplyPatchError {
|
|
53
|
+
return new ApplyPatchError('verification', 'verification_failed', message, {
|
|
54
|
+
cause,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createApplyPatchInternalError(
|
|
59
|
+
message: string,
|
|
60
|
+
cause?: unknown,
|
|
61
|
+
): ApplyPatchError {
|
|
62
|
+
return new ApplyPatchError('internal', 'internal_unexpected', message, {
|
|
63
|
+
cause,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isApplyPatchError(error: unknown): error is ApplyPatchError {
|
|
68
|
+
return error instanceof ApplyPatchError;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function isApplyPatchBlockedError(error: unknown): boolean {
|
|
72
|
+
return isApplyPatchError(error) && error.kind === 'blocked';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isApplyPatchValidationError(error: unknown): boolean {
|
|
76
|
+
return isApplyPatchError(error) && error.kind === 'validation';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isApplyPatchVerificationError(error: unknown): boolean {
|
|
80
|
+
return isApplyPatchError(error) && error.kind === 'verification';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isApplyPatchInternalError(error: unknown): boolean {
|
|
84
|
+
return isApplyPatchError(error) && error.kind === 'internal';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getApplyPatchErrorDetails(error: unknown):
|
|
88
|
+
| {
|
|
89
|
+
kind: ApplyPatchErrorKind;
|
|
90
|
+
code: ApplyPatchErrorCode;
|
|
91
|
+
message: string;
|
|
92
|
+
}
|
|
93
|
+
| undefined {
|
|
94
|
+
if (!isApplyPatchError(error)) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
kind: error.kind,
|
|
100
|
+
code: error.code,
|
|
101
|
+
message: error.message,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function ensureApplyPatchError(
|
|
106
|
+
error: unknown,
|
|
107
|
+
context: string,
|
|
108
|
+
): ApplyPatchError {
|
|
109
|
+
if (isApplyPatchError(error)) {
|
|
110
|
+
return error;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return createApplyPatchInternalError(
|
|
114
|
+
`${context}: ${getErrorMessage(error)}`,
|
|
115
|
+
error,
|
|
116
|
+
);
|
|
117
|
+
}
|