opencode-snippets 1.3.0 → 1.4.1
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/README.md +42 -0
- package/index.ts +3 -2
- package/package.json +1 -1
- package/src/arg-parser.test.ts +177 -0
- package/src/arg-parser.ts +87 -0
- package/src/commands.test.ts +188 -0
- package/src/commands.ts +121 -43
- package/src/expander.test.ts +428 -48
- package/src/expander.ts +165 -7
- package/src/loader.test.ts +91 -0
- package/src/loader.ts +7 -5
- package/src/notification.ts +2 -1
- package/src/types.ts +35 -2
package/README.md
CHANGED
|
@@ -190,6 +190,48 @@ I reference I reference I reference ... (15 times) ... I reference #self
|
|
|
190
190
|
|
|
191
191
|
This generous limit supports complex snippet hierarchies while preventing infinite loops.
|
|
192
192
|
|
|
193
|
+
### Prepend and Append Blocks
|
|
194
|
+
|
|
195
|
+
For long reference material that would break your writing flow, use `<append>` blocks to place content at the end of your message:
|
|
196
|
+
|
|
197
|
+
```markdown
|
|
198
|
+
---
|
|
199
|
+
aliases: jira-mcp
|
|
200
|
+
---
|
|
201
|
+
Jira MCP server
|
|
202
|
+
<append>
|
|
203
|
+
## Jira MCP Usage
|
|
204
|
+
|
|
205
|
+
Use these custom field mappings when creating issues:
|
|
206
|
+
- customfield_16570 => Acceptance Criteria
|
|
207
|
+
- customfield_11401 => Team
|
|
208
|
+
</append>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Input:** `Create a bug ticket in #jira-mcp about the memory leak`
|
|
212
|
+
|
|
213
|
+
**Output:**
|
|
214
|
+
```
|
|
215
|
+
Create a bug ticket in Jira MCP server about the memory leak
|
|
216
|
+
|
|
217
|
+
## Jira MCP Usage
|
|
218
|
+
|
|
219
|
+
Use these custom field mappings when creating issues:
|
|
220
|
+
- customfield_16570 => Acceptance Criteria
|
|
221
|
+
- customfield_11401 => Team
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Write naturally—reference what you need mid-sentence—and the context follows at the bottom.
|
|
225
|
+
|
|
226
|
+
Use `<prepend>` for content that should appear at the top of your message. Multiple blocks of the same type are concatenated in order of appearance.
|
|
227
|
+
|
|
228
|
+
**Block behavior:**
|
|
229
|
+
- Content outside `<prepend>`/`<append>` blocks replaces the hashtag inline
|
|
230
|
+
- If a snippet has only blocks (no inline content), the hashtag is simply removed
|
|
231
|
+
- Blocks from nested snippets are collected and assembled in the final message
|
|
232
|
+
- Unclosed tags are handled leniently (rest of content becomes the block)
|
|
233
|
+
- Nested blocks are not allowed—the hashtag is left unchanged
|
|
234
|
+
|
|
193
235
|
## Example Snippets
|
|
194
236
|
|
|
195
237
|
### `~/.config/opencode/snippet/context.md`
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
2
|
import { createCommandExecuteHandler } from "./src/commands.js";
|
|
3
|
-
import { expandHashtags } from "./src/expander.js";
|
|
3
|
+
import { assembleMessage, expandHashtags } from "./src/expander.js";
|
|
4
4
|
import { loadSnippets } from "./src/loader.js";
|
|
5
5
|
import { logger } from "./src/logger.js";
|
|
6
6
|
import { executeShellCommands, type ShellContext } from "./src/shell.js";
|
|
@@ -53,7 +53,8 @@ export const SnippetsPlugin: Plugin = async (ctx) => {
|
|
|
53
53
|
if (part.type === "text" && part.text) {
|
|
54
54
|
// 1. Expand hashtags recursively with loop detection
|
|
55
55
|
const expandStart = performance.now();
|
|
56
|
-
|
|
56
|
+
const expansionResult = expandHashtags(part.text, snippets);
|
|
57
|
+
part.text = assembleMessage(expansionResult);
|
|
57
58
|
const expandTime = performance.now() - expandStart;
|
|
58
59
|
expandTimeTotal += expandTime;
|
|
59
60
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { parseCommandArgs } from "./arg-parser.js";
|
|
3
|
+
|
|
4
|
+
describe("parseCommandArgs", () => {
|
|
5
|
+
// Basic splitting
|
|
6
|
+
describe("basic argument splitting", () => {
|
|
7
|
+
it("splits simple space-separated args", () => {
|
|
8
|
+
expect(parseCommandArgs("add test")).toEqual(["add", "test"]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("handles empty input", () => {
|
|
12
|
+
expect(parseCommandArgs("")).toEqual([]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("handles whitespace-only input", () => {
|
|
16
|
+
expect(parseCommandArgs(" ")).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("handles multiple spaces between args", () => {
|
|
20
|
+
expect(parseCommandArgs("add test")).toEqual(["add", "test"]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("handles leading and trailing spaces", () => {
|
|
24
|
+
expect(parseCommandArgs(" add test ")).toEqual(["add", "test"]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("handles tabs and mixed whitespace", () => {
|
|
28
|
+
expect(parseCommandArgs("add\t\ttest")).toEqual(["add", "test"]);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Double quote handling
|
|
33
|
+
describe("double quote handling", () => {
|
|
34
|
+
it("preserves double-quoted strings with spaces", () => {
|
|
35
|
+
expect(parseCommandArgs('add "hello world"')).toEqual(["add", "hello world"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("handles single quote inside double quotes", () => {
|
|
39
|
+
// THE MAIN BUG - apostrophe in description
|
|
40
|
+
expect(parseCommandArgs('--desc="don\'t do this"')).toEqual(["--desc=don't do this"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("handles empty double-quoted string", () => {
|
|
44
|
+
expect(parseCommandArgs('add ""')).toEqual(["add", ""]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("handles double-quoted string at start", () => {
|
|
48
|
+
expect(parseCommandArgs('"hello world" test')).toEqual(["hello world", "test"]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("handles multiple double-quoted strings", () => {
|
|
52
|
+
expect(parseCommandArgs('"first" "second"')).toEqual(["first", "second"]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Single quote handling
|
|
57
|
+
describe("single quote handling", () => {
|
|
58
|
+
it("preserves single-quoted strings with spaces", () => {
|
|
59
|
+
expect(parseCommandArgs("add 'hello world'")).toEqual(["add", "hello world"]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("handles double quote inside single quotes", () => {
|
|
63
|
+
expect(parseCommandArgs("--desc='say \"hello\"'")).toEqual(['--desc=say "hello"']);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("handles empty single-quoted string", () => {
|
|
67
|
+
expect(parseCommandArgs("add ''")).toEqual(["add", ""]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// --key=value syntax
|
|
72
|
+
describe("--key=value syntax", () => {
|
|
73
|
+
it("handles --key=value without quotes", () => {
|
|
74
|
+
expect(parseCommandArgs("--desc=hello")).toEqual(["--desc=hello"]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('handles --key="value" with quotes stripped from value', () => {
|
|
78
|
+
expect(parseCommandArgs('--desc="hello world"')).toEqual(["--desc=hello world"]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("handles --key='value' with quotes stripped from value", () => {
|
|
82
|
+
expect(parseCommandArgs("--key='hello world'")).toEqual(["--key=hello world"]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("handles --key=value with special characters", () => {
|
|
86
|
+
expect(parseCommandArgs("--desc=hello,world")).toEqual(["--desc=hello,world"]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("preserves = inside quoted value", () => {
|
|
90
|
+
expect(parseCommandArgs('--desc="a=b"')).toEqual(["--desc=a=b"]);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Multiline content (critical for snippet bodies)
|
|
95
|
+
describe("multiline content", () => {
|
|
96
|
+
it("handles multiline content in double quotes", () => {
|
|
97
|
+
expect(parseCommandArgs('add test "line1\nline2\nline3"')).toEqual([
|
|
98
|
+
"add",
|
|
99
|
+
"test",
|
|
100
|
+
"line1\nline2\nline3",
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("handles multiline content in single quotes", () => {
|
|
105
|
+
expect(parseCommandArgs("add test 'line1\nline2'")).toEqual(["add", "test", "line1\nline2"]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("handles multiline content with --key=value syntax", () => {
|
|
109
|
+
expect(parseCommandArgs('--desc="line1\nline2"')).toEqual(["--desc=line1\nline2"]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("preserves indentation in multiline content", () => {
|
|
113
|
+
const input = 'add test "line1\n indented\n more indented"';
|
|
114
|
+
expect(parseCommandArgs(input)).toEqual([
|
|
115
|
+
"add",
|
|
116
|
+
"test",
|
|
117
|
+
"line1\n indented\n more indented",
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Mixed scenarios
|
|
123
|
+
describe("mixed scenarios", () => {
|
|
124
|
+
it("handles mixed quoted and unquoted args", () => {
|
|
125
|
+
expect(parseCommandArgs('add test "hello world" --project')).toEqual([
|
|
126
|
+
"add",
|
|
127
|
+
"test",
|
|
128
|
+
"hello world",
|
|
129
|
+
"--project",
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("handles complex command with all option types", () => {
|
|
134
|
+
const input = 'add mysnippet "content here" --aliases=a,b --desc="don\'t forget" --project';
|
|
135
|
+
expect(parseCommandArgs(input)).toEqual([
|
|
136
|
+
"add",
|
|
137
|
+
"mysnippet",
|
|
138
|
+
"content here",
|
|
139
|
+
"--aliases=a,b",
|
|
140
|
+
"--desc=don't forget",
|
|
141
|
+
"--project",
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("handles --key value syntax (space-separated)", () => {
|
|
146
|
+
expect(parseCommandArgs("--desc hello")).toEqual(["--desc", "hello"]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("handles --key followed by quoted value", () => {
|
|
150
|
+
expect(parseCommandArgs('--desc "hello world"')).toEqual(["--desc", "hello world"]);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Edge cases
|
|
155
|
+
describe("edge cases", () => {
|
|
156
|
+
it("handles unclosed double quote by including rest of string", () => {
|
|
157
|
+
// Graceful handling: treat unclosed quote as extending to end
|
|
158
|
+
expect(parseCommandArgs('add "unclosed')).toEqual(["add", "unclosed"]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("handles unclosed single quote by including rest of string", () => {
|
|
162
|
+
expect(parseCommandArgs("add 'unclosed")).toEqual(["add", "unclosed"]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("handles backslash-escaped quotes inside double quotes", () => {
|
|
166
|
+
expect(parseCommandArgs('--desc="say \\"hello\\""')).toEqual(['--desc=say "hello"']);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("handles backslash-escaped quotes inside single quotes", () => {
|
|
170
|
+
expect(parseCommandArgs("--desc='it\\'s fine'")).toEqual(["--desc=it's fine"]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("handles literal backslash", () => {
|
|
174
|
+
expect(parseCommandArgs('--path="C:\\\\Users"')).toEqual(["--path=C:\\Users"]);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell-like argument parser that handles quoted strings correctly.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Space-separated arguments
|
|
6
|
+
* - Double-quoted strings (preserves spaces, allows single quotes inside)
|
|
7
|
+
* - Single-quoted strings (preserves spaces, allows double quotes inside)
|
|
8
|
+
* - --key=value syntax with quoted values
|
|
9
|
+
* - Multiline content inside quotes
|
|
10
|
+
* - Backslash escapes for quotes inside quoted strings
|
|
11
|
+
*
|
|
12
|
+
* @param input - The raw argument string to parse
|
|
13
|
+
* @returns Array of parsed arguments with quotes stripped
|
|
14
|
+
*/
|
|
15
|
+
export function parseCommandArgs(input: string): string[] {
|
|
16
|
+
const args: string[] = [];
|
|
17
|
+
let current = "";
|
|
18
|
+
let state: "normal" | "double" | "single" = "normal";
|
|
19
|
+
let hasQuotedContent = false; // Track if we've entered a quoted section
|
|
20
|
+
let i = 0;
|
|
21
|
+
|
|
22
|
+
while (i < input.length) {
|
|
23
|
+
const char = input[i];
|
|
24
|
+
|
|
25
|
+
if (state === "normal") {
|
|
26
|
+
if (char === " " || char === "\t") {
|
|
27
|
+
// Whitespace in normal mode: finish current token
|
|
28
|
+
if (current.length > 0 || hasQuotedContent) {
|
|
29
|
+
args.push(current);
|
|
30
|
+
current = "";
|
|
31
|
+
hasQuotedContent = false;
|
|
32
|
+
}
|
|
33
|
+
} else if (char === '"') {
|
|
34
|
+
// Enter double-quote mode
|
|
35
|
+
state = "double";
|
|
36
|
+
hasQuotedContent = true;
|
|
37
|
+
} else if (char === "'") {
|
|
38
|
+
// Enter single-quote mode
|
|
39
|
+
state = "single";
|
|
40
|
+
hasQuotedContent = true;
|
|
41
|
+
} else {
|
|
42
|
+
current += char;
|
|
43
|
+
}
|
|
44
|
+
} else if (state === "double") {
|
|
45
|
+
if (char === "\\") {
|
|
46
|
+
// Check for escape sequences
|
|
47
|
+
const next = input[i + 1];
|
|
48
|
+
if (next === '"' || next === "\\") {
|
|
49
|
+
current += next;
|
|
50
|
+
i++; // Skip the escaped character
|
|
51
|
+
} else {
|
|
52
|
+
current += char;
|
|
53
|
+
}
|
|
54
|
+
} else if (char === '"') {
|
|
55
|
+
// Exit double-quote mode
|
|
56
|
+
state = "normal";
|
|
57
|
+
} else {
|
|
58
|
+
current += char;
|
|
59
|
+
}
|
|
60
|
+
} else if (state === "single") {
|
|
61
|
+
if (char === "\\") {
|
|
62
|
+
// Check for escape sequences
|
|
63
|
+
const next = input[i + 1];
|
|
64
|
+
if (next === "'" || next === "\\") {
|
|
65
|
+
current += next;
|
|
66
|
+
i++; // Skip the escaped character
|
|
67
|
+
} else {
|
|
68
|
+
current += char;
|
|
69
|
+
}
|
|
70
|
+
} else if (char === "'") {
|
|
71
|
+
// Exit single-quote mode
|
|
72
|
+
state = "normal";
|
|
73
|
+
} else {
|
|
74
|
+
current += char;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle any remaining content (including unclosed quotes or empty quoted strings)
|
|
82
|
+
if (current.length > 0 || hasQuotedContent) {
|
|
83
|
+
args.push(current);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return args;
|
|
87
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { parseAddOptions } from "./commands.js";
|
|
3
|
+
|
|
4
|
+
describe("parseAddOptions", () => {
|
|
5
|
+
// Alias variations - all 4 must work per PR #13 requirements
|
|
6
|
+
describe("alias parameter variations", () => {
|
|
7
|
+
it("parses --alias=a,b", () => {
|
|
8
|
+
expect(parseAddOptions(["--alias=a,b"])).toEqual({
|
|
9
|
+
aliases: ["a", "b"],
|
|
10
|
+
description: undefined,
|
|
11
|
+
isProject: false,
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("parses --alias a,b (space-separated)", () => {
|
|
16
|
+
expect(parseAddOptions(["--alias", "a,b"])).toEqual({
|
|
17
|
+
aliases: ["a", "b"],
|
|
18
|
+
description: undefined,
|
|
19
|
+
isProject: false,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("parses --aliases=a,b", () => {
|
|
24
|
+
expect(parseAddOptions(["--aliases=a,b"])).toEqual({
|
|
25
|
+
aliases: ["a", "b"],
|
|
26
|
+
description: undefined,
|
|
27
|
+
isProject: false,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("parses --aliases a,b (space-separated)", () => {
|
|
32
|
+
expect(parseAddOptions(["--aliases", "a,b"])).toEqual({
|
|
33
|
+
aliases: ["a", "b"],
|
|
34
|
+
description: undefined,
|
|
35
|
+
isProject: false,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("parses single alias", () => {
|
|
40
|
+
expect(parseAddOptions(["--alias=foo"])).toEqual({
|
|
41
|
+
aliases: ["foo"],
|
|
42
|
+
description: undefined,
|
|
43
|
+
isProject: false,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("handles multiple alias values with spaces", () => {
|
|
48
|
+
expect(parseAddOptions(["--aliases=hello, world, foo"])).toEqual({
|
|
49
|
+
aliases: ["hello", "world", "foo"],
|
|
50
|
+
description: undefined,
|
|
51
|
+
isProject: false,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Description variations - all must work
|
|
57
|
+
describe("description parameter variations", () => {
|
|
58
|
+
it("parses --desc=value", () => {
|
|
59
|
+
expect(parseAddOptions(["--desc=hello"])).toEqual({
|
|
60
|
+
aliases: [],
|
|
61
|
+
description: "hello",
|
|
62
|
+
isProject: false,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("parses --desc value (space-separated)", () => {
|
|
67
|
+
expect(parseAddOptions(["--desc", "hello"])).toEqual({
|
|
68
|
+
aliases: [],
|
|
69
|
+
description: "hello",
|
|
70
|
+
isProject: false,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("parses --desc with apostrophe (main bug)", () => {
|
|
75
|
+
expect(parseAddOptions(["--desc=don't break"])).toEqual({
|
|
76
|
+
aliases: [],
|
|
77
|
+
description: "don't break",
|
|
78
|
+
isProject: false,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("parses --description=value", () => {
|
|
83
|
+
expect(parseAddOptions(["--description=test"])).toEqual({
|
|
84
|
+
aliases: [],
|
|
85
|
+
description: "test",
|
|
86
|
+
isProject: false,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("parses --description value (space-separated)", () => {
|
|
91
|
+
expect(parseAddOptions(["--description", "test"])).toEqual({
|
|
92
|
+
aliases: [],
|
|
93
|
+
description: "test",
|
|
94
|
+
isProject: false,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("parses --desc with multiline content", () => {
|
|
99
|
+
expect(parseAddOptions(["--desc=line1\nline2"])).toEqual({
|
|
100
|
+
aliases: [],
|
|
101
|
+
description: "line1\nline2",
|
|
102
|
+
isProject: false,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Project flag
|
|
108
|
+
describe("--project flag", () => {
|
|
109
|
+
it("parses --project flag", () => {
|
|
110
|
+
expect(parseAddOptions(["--project"])).toEqual({
|
|
111
|
+
aliases: [],
|
|
112
|
+
description: undefined,
|
|
113
|
+
isProject: true,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("parses --project in any position", () => {
|
|
118
|
+
expect(parseAddOptions(["--desc=test", "--project"])).toEqual({
|
|
119
|
+
aliases: [],
|
|
120
|
+
description: "test",
|
|
121
|
+
isProject: true,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Combined options
|
|
127
|
+
describe("combined options", () => {
|
|
128
|
+
it("parses multiple options together", () => {
|
|
129
|
+
expect(parseAddOptions(["--alias=a,b", "--desc=hello", "--project"])).toEqual({
|
|
130
|
+
aliases: ["a", "b"],
|
|
131
|
+
description: "hello",
|
|
132
|
+
isProject: true,
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("parses all space-separated options together", () => {
|
|
137
|
+
expect(parseAddOptions(["--alias", "a,b", "--desc", "hello", "--project"])).toEqual({
|
|
138
|
+
aliases: ["a", "b"],
|
|
139
|
+
description: "hello",
|
|
140
|
+
isProject: true,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("handles mixed = and space syntax", () => {
|
|
145
|
+
expect(parseAddOptions(["--alias=a,b", "--desc", "hello"])).toEqual({
|
|
146
|
+
aliases: ["a", "b"],
|
|
147
|
+
description: "hello",
|
|
148
|
+
isProject: false,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Edge cases
|
|
154
|
+
describe("edge cases", () => {
|
|
155
|
+
it("returns defaults for empty args", () => {
|
|
156
|
+
expect(parseAddOptions([])).toEqual({
|
|
157
|
+
aliases: [],
|
|
158
|
+
description: undefined,
|
|
159
|
+
isProject: false,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("ignores unknown options", () => {
|
|
164
|
+
expect(parseAddOptions(["--unknown=value", "--desc=hello"])).toEqual({
|
|
165
|
+
aliases: [],
|
|
166
|
+
description: "hello",
|
|
167
|
+
isProject: false,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("ignores positional args (non-option)", () => {
|
|
172
|
+
expect(parseAddOptions(["positional", "--desc=hello"])).toEqual({
|
|
173
|
+
aliases: [],
|
|
174
|
+
description: "hello",
|
|
175
|
+
isProject: false,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("does not consume value after --project", () => {
|
|
180
|
+
// --project is a flag, should not consume next arg
|
|
181
|
+
expect(parseAddOptions(["--project", "--desc=hello"])).toEqual({
|
|
182
|
+
aliases: [],
|
|
183
|
+
description: "hello",
|
|
184
|
+
isProject: true,
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|