Skip to content

Commit 7c71303

Browse files
fix: improve robustness of add-on args parsing (#681)
Co-authored-by: AdrianGonz97 <31664583+AdrianGonz97@users.noreply.github.com>
1 parent c23c827 commit 7c71303

File tree

5 files changed

+85
-16
lines changed

5 files changed

+85
-16
lines changed

.changeset/odd-words-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'sv': patch
3+
---
4+
5+
fix(add): improve robustness of add-on args parsing

packages/cli/commands/add/index.ts

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,25 +59,16 @@ export const add = new Command('add')
5959
process.exit(1);
6060
}
6161

62-
// occurs when an `=` isn't present (e.g. `sv add foo`)
63-
if (optionFlags === undefined) {
64-
prev.push({ id: addonId, options: undefined });
65-
return prev;
66-
}
67-
68-
// validates that the options are relatively well-formed.
69-
// occurs when no <name> or <value> is specified (e.g. `sv add foo=demo`).
70-
if (optionFlags.length > 0 && !/.+:.*/.test(optionFlags)) {
71-
console.error(
72-
`Malformed arguments: An add-on's option in '${value}' is missing it's option name or value (e.g. 'addon=option:value').`
73-
);
62+
try {
63+
const options = common.parseAddonOptions(optionFlags);
64+
prev.push({ id: addonId, options });
65+
} catch (error) {
66+
if (error instanceof Error) {
67+
console.error(error.message);
68+
}
7469
process.exit(1);
7570
}
7671

77-
// parses the option flags into a array of `<name>:<value>` strings
78-
const options: string[] = optionFlags.match(/[^+]*:[^:]*(?=\+|$)/g) ?? [];
79-
80-
prev.push({ id: addonId, options });
8172
return prev;
8273
})
8374
.option('-C, --cwd <path>', 'path to working directory', defaultCwd)

packages/cli/tests/common.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { parseAddonOptions } from '../utils/common.ts';
3+
4+
describe('parseAddonOptions', () => {
5+
it('returns undefined on undefined', () => {
6+
expect(parseAddonOptions(undefined)).toEqual(undefined);
7+
});
8+
it('returns undefined on empty string', () => {
9+
expect(parseAddonOptions('')).toEqual(undefined);
10+
});
11+
it('parses options and values', () => {
12+
expect(parseAddonOptions('option:value')).toEqual(['option:value']);
13+
});
14+
it('parses values with empty strings', () => {
15+
expect(parseAddonOptions('foo:')).toEqual(['foo:']);
16+
expect(parseAddonOptions('foo:+bar:+baz:')).toEqual(['foo:', 'bar:', 'baz:']);
17+
});
18+
it('parses sentences', () => {
19+
expect(parseAddonOptions('foo:the quick brown fox')).toEqual(['foo:the quick brown fox']);
20+
});
21+
it('parses lists', () => {
22+
expect(parseAddonOptions('foo:en,es,de')).toEqual(['foo:en,es,de']);
23+
});
24+
it('parses values with colons', () => {
25+
expect(parseAddonOptions('option:foo:bar:baz')).toEqual(['option:foo:bar:baz']);
26+
});
27+
it('errors on missing value', () => {
28+
expect(() => parseAddonOptions('foo')).toThrowError(
29+
"Malformed arguments: The following add-on options: 'foo' are missing their option name or value (e.g. 'addon=option1:value1+option2:value2')."
30+
);
31+
});
32+
it('errors when one of two options is missing a value', () => {
33+
expect(() => parseAddonOptions('foo:value1+bar')).toThrowError(
34+
"Malformed arguments: The following add-on options: 'bar' are missing their option name or value (e.g. 'addon=option1:value1+option2:value2')."
35+
);
36+
});
37+
it('errors on two missing values', () => {
38+
expect(() => parseAddonOptions('foo+bar')).toThrowError(
39+
"Malformed arguments: The following add-on options: 'foo', 'bar' are missing their option name or value (e.g. 'addon=option1:value1+option2:value2')."
40+
);
41+
});
42+
});

packages/cli/utils/common.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,23 @@ export function forwardExitCode(error: unknown) {
115115
process.exit(1);
116116
}
117117
}
118+
119+
export function parseAddonOptions(optionFlags: string | undefined): string[] | undefined {
120+
// occurs when an `=` isn't present (e.g. `sv add foo`)
121+
if (optionFlags === undefined || optionFlags === '') {
122+
return undefined;
123+
}
124+
125+
// Split on + and validate each option individually
126+
const options = optionFlags.split('+');
127+
128+
// Validate that each individual option follows the name:value pattern
129+
const malformed = options.filter((option) => !/.+:.*/.test(option));
130+
131+
if (malformed.length > 0) {
132+
const message = `Malformed arguments: The following add-on options: ${malformed.map((o) => `'${o}'`).join(', ')} are missing their option name or value (e.g. 'addon=option1:value1+option2:value2').`;
133+
throw new Error(message);
134+
}
135+
136+
return options;
137+
}

packages/cli/vitest.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineProject } from 'vitest/config';
2+
3+
export default defineProject({
4+
test: {
5+
name: 'cli',
6+
include: ['./tests/**/index.ts', './tests/*.ts'],
7+
expect: {
8+
requireAssertions: true
9+
}
10+
}
11+
});

0 commit comments

Comments
 (0)