Skip to content

Commit 6519694

Browse files
committed
feat(cli): align object type with rollup (#8598)
This PR adds usage for `key:value` for object types to align with rollup's object-like type syntax. It also keeps `key=value` with a warning message for the backward compability. closes #8593
1 parent a3c7162 commit 6519694

File tree

6 files changed

+66
-26
lines changed

6 files changed

+66
-26
lines changed

docs/apis/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export default defineConfig({
108108

109109
## Command Line Flags
110110

111-
Flags can be passed as `--foo`, `--foo <value>`, or `--foo=<value>`. Boolean flags like `--minify` don't need a value, while key-value options like `--define` use comma-separated syntax: `--define key=value,key2=value2`. Many flags have short aliases (e.g., `-m` for `--minify`, `-f` for `--format`).
111+
Flags can be passed as `--foo`, `--foo <value>`, or `--foo=<value>`. Boolean flags like `--minify` don't need a value, while key-value options like `--transform.define` use comma-separated syntax: `--transform.define key:value,key2:value2`. Many flags have short aliases (e.g., `-m` for `--minify`, `-f` for `--format`).
112112

113113
::: info Integration into other tools
114114

meta/design/cli.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ bin/cli.mjs
2222
→ rawArgs snapshot
2323
→ remove unknown keys
2424
→ type coercion (duplicate filtering + array wrapping)
25-
→ object option parsing (key=val,key=val)
25+
→ object option parsing (key:val,key:val)
2626
→ arguments/normalize.ts
2727
→ validateCliOptions() via valibot
2828
→ split into input/output based on schema keys
@@ -141,7 +141,7 @@ cli.parse(process.argv, { run: true });
141141
5. Snapshot `rawArgs` (includes unknown keys)
142142
6. Remove unknown keys from `parsedOptions`
143143
7. Type coercion — duplicate filtering + array wrapping (single merged loop)
144-
8. Object option parsing (`key=val,key=val`)
144+
8. Object option parsing (`key:val,key:val`)
145145
9. `normalizeCliOptions()` — valibot validation + input/output splitting
146146

147147
## Implementation Notes

packages/rolldown/src/cli/arguments/index.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ export function parseCliArguments(): NormalizedCliOptions & {
189189
}
190190
}
191191

192-
// Object option parsing — parse "key=val,key=val" strings
192+
// Object option parsing — parse "key:val,key:val" strings (Rollup-compatible)
193+
// Also supports deprecated "key=val,key=val" syntax with a warning
193194
for (const [schemaKey, info] of Object.entries(schemaInfo)) {
194195
if (info.type !== 'object') continue;
195196

@@ -205,15 +206,35 @@ export function parseCliArguments(): NormalizedCliOptions & {
205206
const values = Array.isArray(value) ? value : [value];
206207
if (typeof values[0] !== 'string') continue;
207208

209+
let usedDeprecatedSyntax = false;
208210
const result: Record<string, string> = {};
209211
for (const v of values) {
210212
for (const pair of String(v).split(',')) {
211-
const [k, ...rest] = pair.split('=');
212-
if (k && rest.length > 0) {
213-
result[k] = rest.join('=');
213+
// Prefer `:` only if it appears before `=` (or `=` doesn't exist)
214+
// This ensures `key=value:with:colon` (deprecated) is parsed correctly
215+
const colonIdx = pair.indexOf(':');
216+
const eqIdx = pair.indexOf('=');
217+
let k: string;
218+
let val: string;
219+
if (colonIdx > 0 && (eqIdx === -1 || colonIdx < eqIdx)) {
220+
k = pair.slice(0, colonIdx);
221+
val = pair.slice(colonIdx + 1);
222+
} else if (eqIdx > 0) {
223+
k = pair.slice(0, eqIdx);
224+
val = pair.slice(eqIdx + 1);
225+
usedDeprecatedSyntax = true;
226+
} else {
227+
continue;
214228
}
229+
result[k] = val;
215230
}
216231
}
232+
if (usedDeprecatedSyntax) {
233+
const optionName = camelCaseToKebabCase(schemaKey);
234+
logger.warn(
235+
`Using \`key=value\` syntax for \`--${optionName}\` is deprecated. Use \`key:value\` instead.`,
236+
);
237+
}
217238
parent[leafKey] = result;
218239
}
219240

packages/rolldown/src/utils/validator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ const TransformOptionsSchema = v.object({
245245
),
246246
define: v.pipe(
247247
v.optional(v.record(v.string(), v.string())),
248-
v.description('Define global variables (syntax: key=value,key2=value2)'),
248+
v.description('Define global variables (syntax: key:value,key2:value2)'),
249249
),
250250
inject: v.pipe(
251251
v.optional(v.record(v.string(), v.union([v.string(), v.tuple([v.string(), v.string()])]))),
@@ -879,7 +879,7 @@ const OutputOptionsSchema = v.strictObject({
879879
name: v.pipe(v.optional(v.string()), v.description('Name for UMD / IIFE format outputs')),
880880
globals: v.pipe(
881881
v.optional(v.union([v.record(v.string(), v.string()), GlobalsFunctionSchema])),
882-
v.description('Global variable of UMD / IIFE dependencies (syntax: `key=value`)'),
882+
v.description('Global variable of UMD / IIFE dependencies (syntax: `key:value`)'),
883883
),
884884
paths: v.pipe(
885885
v.optional(v.union([v.record(v.string(), v.string()), PathsFunctionSchema])),
@@ -995,7 +995,7 @@ const OutputCliOverrideSchema = v.strictObject({
995995
),
996996
globals: v.pipe(
997997
v.optional(v.record(v.string(), v.string())),
998-
v.description('Global variable of UMD / IIFE dependencies (syntax: `key=value`)'),
998+
v.description('Global variable of UMD / IIFE dependencies (syntax: `key:value`)'),
999999
),
10001000
codeSplitting: v.pipe(
10011001
v.optional(

packages/rolldown/tests/cli/__snapshots__/cli-e2e.test.ts.snap

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ OPTIONS
1111
--dir -d, <dir> Output directory, defaults to \`dist\` if \`file\` is not set.
1212
--external -e, <external> Comma-separated list of module ids to exclude from the bundle \`<module-id>,...\`.
1313
--format -f, <format> Output format of the generated bundle (supports esm, cjs, and iife).
14-
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key=value\`).
14+
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key:value\`).
1515
--help -h, Show help.
1616
--minify -m, Minify the bundled file.
1717
--name -n, <name> Name for UMD / IIFE format outputs.
@@ -97,7 +97,7 @@ OPTIONS
9797
--transform.assumptions.set-public-class-fields .
9898
--transform.decorator.emit-decorator-metadata .
9999
--transform.decorator.legacy .
100-
--transform.define <transform.define>Define global variables (syntax: key=value,key2=value2).
100+
--transform.define <transform.define>Define global variables (syntax: key:value,key2:value2).
101101
--transform.drop-labels <transform.drop-labels>Remove labeled statements with these label names.
102102
--transform.helpers.mode <transform.helpers.mode>.
103103
--transform.inject <transform.inject>Inject import statements on demand.
@@ -151,7 +151,7 @@ OPTIONS
151151
--dir -d, <dir> Output directory, defaults to \`dist\` if \`file\` is not set.
152152
--external -e, <external> Comma-separated list of module ids to exclude from the bundle \`<module-id>,...\`.
153153
--format -f, <format> Output format of the generated bundle (supports esm, cjs, and iife).
154-
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key=value\`).
154+
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key:value\`).
155155
--help -h, Show help.
156156
--minify -m, Minify the bundled file.
157157
--name -n, <name> Name for UMD / IIFE format outputs.
@@ -237,7 +237,7 @@ OPTIONS
237237
--transform.assumptions.set-public-class-fields .
238238
--transform.decorator.emit-decorator-metadata .
239239
--transform.decorator.legacy .
240-
--transform.define <transform.define>Define global variables (syntax: key=value,key2=value2).
240+
--transform.define <transform.define>Define global variables (syntax: key:value,key2:value2).
241241
--transform.drop-labels <transform.drop-labels>Remove labeled statements with these label names.
242242
--transform.helpers.mode <transform.helpers.mode>.
243243
--transform.inject <transform.inject>Inject import statements on demand.
@@ -291,7 +291,7 @@ OPTIONS
291291
--dir -d, <dir> Output directory, defaults to \`dist\` if \`file\` is not set.
292292
--external -e, <external> Comma-separated list of module ids to exclude from the bundle \`<module-id>,...\`.
293293
--format -f, <format> Output format of the generated bundle (supports esm, cjs, and iife).
294-
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key=value\`).
294+
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key:value\`).
295295
--help -h, Show help.
296296
--minify -m, Minify the bundled file.
297297
--name -n, <name> Name for UMD / IIFE format outputs.
@@ -377,7 +377,7 @@ OPTIONS
377377
--transform.assumptions.set-public-class-fields .
378378
--transform.decorator.emit-decorator-metadata .
379379
--transform.decorator.legacy .
380-
--transform.define <transform.define>Define global variables (syntax: key=value,key2=value2).
380+
--transform.define <transform.define>Define global variables (syntax: key:value,key2:value2).
381381
--transform.drop-labels <transform.drop-labels>Remove labeled statements with these label names.
382382
--transform.helpers.mode <transform.helpers.mode>.
383383
--transform.inject <transform.inject>Inject import statements on demand.
@@ -431,7 +431,7 @@ OPTIONS
431431
--dir -d, <dir> Output directory, defaults to \`dist\` if \`file\` is not set.
432432
--external -e, <external> Comma-separated list of module ids to exclude from the bundle \`<module-id>,...\`.
433433
--format -f, <format> Output format of the generated bundle (supports esm, cjs, and iife).
434-
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key=value\`).
434+
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key:value\`).
435435
--help -h, Show help.
436436
--minify -m, Minify the bundled file.
437437
--name -n, <name> Name for UMD / IIFE format outputs.
@@ -517,7 +517,7 @@ OPTIONS
517517
--transform.assumptions.set-public-class-fields .
518518
--transform.decorator.emit-decorator-metadata .
519519
--transform.decorator.legacy .
520-
--transform.define <transform.define>Define global variables (syntax: key=value,key2=value2).
520+
--transform.define <transform.define>Define global variables (syntax: key:value,key2:value2).
521521
--transform.drop-labels <transform.drop-labels>Remove labeled statements with these label names.
522522
--transform.helpers.mode <transform.helpers.mode>.
523523
--transform.inject <transform.inject>Inject import statements on demand.
@@ -571,7 +571,7 @@ OPTIONS
571571
--dir -d, <dir> Output directory, defaults to \`dist\` if \`file\` is not set.
572572
--external -e, <external> Comma-separated list of module ids to exclude from the bundle \`<module-id>,...\`.
573573
--format -f, <format> Output format of the generated bundle (supports esm, cjs, and iife).
574-
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key=value\`).
574+
--globals -g, <globals> Global variable of UMD / IIFE dependencies (syntax: \`key:value\`).
575575
--help -h, Show help.
576576
--minify -m, Minify the bundled file.
577577
--name -n, <name> Name for UMD / IIFE format outputs.
@@ -657,7 +657,7 @@ OPTIONS
657657
--transform.assumptions.set-public-class-fields .
658658
--transform.decorator.emit-decorator-metadata .
659659
--transform.decorator.legacy .
660-
--transform.define <transform.define>Define global variables (syntax: key=value,key2=value2).
660+
--transform.define <transform.define>Define global variables (syntax: key:value,key2:value2).
661661
--transform.drop-labels <transform.drop-labels>Remove labeled statements with these label names.
662662
--transform.helpers.mode <transform.helpers.mode>.
663663
--transform.inject <transform.inject>Inject import statements on demand.
@@ -765,6 +765,13 @@ exports[`cli options for bundling > should handle comma-separated object options
765765
"
766766
`;
767767

768+
exports[`cli options for bundling > should handle deprecated key=value syntax with warning 1`] = `
769+
"Using \`key=value\` syntax for \`--module-types\` is deprecated. Use \`key:value\` instead.
770+
<DIR>/index.js chunk │ size: 0.09 kB
771+
772+
"
773+
`;
774+
768775
exports[`cli options for bundling > should handle multiple define arguments with comma-separated syntax 1`] = `
769776
"<DIR>/index.js chunk │ size: 0.10 kB
770777

packages/rolldown/tests/cli/cli-e2e.test.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ describe('cli options for bundling', () => {
123123
const cwd = cliFixturesDir('cli-option-object');
124124
const status = await $({
125125
cwd,
126-
})`rolldown index.ts --module-types .123=text --module-types notjson=json --module-types .b64=base64 -d dist`;
126+
})`rolldown index.ts --module-types .123:text --module-types notjson:json --module-types .b64:base64 -d dist`;
127127
expect(status.exitCode).toBe(0);
128128
expect(cleanStdout(status.stdout)).toMatchSnapshot();
129129
});
@@ -132,7 +132,7 @@ describe('cli options for bundling', () => {
132132
const cwd = cliFixturesDir('cli-option-object');
133133
const status = await $({
134134
cwd,
135-
})`rolldown index.ts --module-types .123=text,notjson=json,.b64=base64 -d dist`;
135+
})`rolldown index.ts --module-types .123:text,notjson:json,.b64:base64 -d dist`;
136136
expect(status.exitCode).toBe(0);
137137
expect(cleanStdout(status.stdout)).toMatchSnapshot();
138138
});
@@ -141,9 +141,21 @@ describe('cli options for bundling', () => {
141141
const cwd = cliFixturesDir('cli-option-object');
142142
const status = await $({
143143
cwd,
144-
})`rolldown index.ts --module-types .123=text,notjson=json --module-types .b64=base64 -d dist`;
144+
})`rolldown index.ts --module-types .123:text,notjson:json --module-types .b64:base64 -d dist`;
145+
expect(status.exitCode).toBe(0);
146+
expect(cleanStdout(status.stdout)).toMatchSnapshot();
147+
});
148+
149+
it('should handle deprecated key=value syntax with warning', async () => {
150+
const cwd = cliFixturesDir('cli-option-object');
151+
const status = await $({
152+
cwd,
153+
})`rolldown index.ts --module-types .123=text,notjson=json,.b64=base64 -d dist`;
145154
expect(status.exitCode).toBe(0);
146155
expect(cleanStdout(status.stdout)).toMatchSnapshot();
156+
expect(status.stdout).toContain(
157+
'Using `key=value` syntax for `--module-types` is deprecated. Use `key:value` instead.',
158+
);
147159
});
148160

149161
it('should handle negative boolean options', async () => {
@@ -166,7 +178,7 @@ describe('cli options for bundling', () => {
166178
const cwd = cliFixturesDir('cli-option-nested');
167179
const status = await $({
168180
cwd,
169-
})`rolldown index.js --transform.define __DEFINE__=defined`;
181+
})`rolldown index.js --transform.define __DEFINE__:defined`;
170182
expect(status.exitCode).toBe(0);
171183
expect(cleanStdout(status.stdout)).toMatchSnapshot();
172184
});
@@ -175,7 +187,7 @@ describe('cli options for bundling', () => {
175187
const cwd = cliFixturesDir('cli-option-multiple-define');
176188
const status = await $({
177189
cwd,
178-
})`rolldown index.js --transform.define __A__=A,__B__=B,__C__=C -d dist`;
190+
})`rolldown index.js --transform.define __A__:A,__B__:B,__C__:C -d dist`;
179191
expect(status.exitCode).toBe(0);
180192
expect(cleanStdout(status.stdout)).toMatchSnapshot();
181193
const file = path.resolve(cwd, 'dist/index.js');
@@ -265,7 +277,7 @@ describe('cli options for bundling', () => {
265277
const cwd = cliFixturesDir('cli-option-object');
266278
const status = await $({
267279
cwd,
268-
})`rolldown index.ts --moduleTypes .123=text,notjson=json,.b64=base64 -d dist`;
280+
})`rolldown index.ts --moduleTypes .123:text,notjson:json,.b64:base64 -d dist`;
269281
expect(status.exitCode).toBe(0);
270282
expect(cleanStdout(status.stdout)).toMatchSnapshot();
271283
});

0 commit comments

Comments
 (0)