From c0fefbe13103a49e7372505cfebe13d9b8298195 Mon Sep 17 00:00:00 2001 From: rk Date: Tue, 20 Oct 2026 15:40:08 +0500 Subject: [PATCH 1/3] fix(@angular/cli): keep schematic positional arguments in index order sort checked `positional` for truthiness, so argument at index 0 was ordered like plain option and help showed it after index 1 when that argument has `x-prompt` both are optional for yargs and values go to wrong argumnets, now positionals are sorted by index --- .../command-builder/utilities/json-schema.ts | 8 ++-- .../utilities/json-schema_spec.ts | 42 +++++++++++++++++++ tests/e2e/tests/generate/help-output.ts | 34 +++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema.ts b/packages/angular/cli/src/command-builder/utilities/json-schema.ts index 0a4215be8eed..78218465cf02 100644 --- a/packages/angular/cli/src/command-builder/utilities/json-schema.ts +++ b/packages/angular/cli/src/command-builder/utilities/json-schema.ts @@ -390,10 +390,10 @@ export async function parseJsonSchemaToOptions( // Sort by positional and name. return options.sort((a, b) => { - if (a.positional) { - return b.positional ? a.positional - b.positional : a.name.localeCompare(b.name); - } else if (b.positional) { - return -1; + if (a.positional !== undefined) { + return b.positional !== undefined ? a.positional - b.positional : -1; + } else if (b.positional !== undefined) { + return 1; } return a.name.localeCompare(b.name); diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts index 496a2094b62c..fa5d5ba3a1b5 100644 --- a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts +++ b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts @@ -346,4 +346,46 @@ describe('parseJsonSchemaToOptions', () => { ); }); }); + + describe('with positional arguments not in alphabetical order', () => { + it('sorts positional arguments by their index', async () => { + const jsonSchema = { + '$id': 'FakeSchema', + 'title': 'Fake Schema', + 'type': 'object', + 'required': ['preset'], + 'properties': { + 'preset': { + 'type': 'string', + 'enum': ['form', 'table'], + 'description': 'preset.', + '$default': { + '$source': 'argv', + 'index': 0, + }, + 'x-prompt': 'Which preset?', + }, + 'name': { + 'type': 'string', + 'description': 'name.', + '$default': { + '$source': 'argv', + 'index': 1, + }, + }, + 'path': { + 'type': 'string', + 'description': 'path.', + }, + }, + }; + const registry = new schema.CoreSchemaRegistry(); + const options = await parseJsonSchemaToOptions(registry, jsonSchema, /* interactive= */ true); + + expect(options.filter((opt) => opt.positional !== undefined)).toEqual([ + jasmine.objectContaining({ name: 'preset', positional: 0, required: false }), + jasmine.objectContaining({ name: 'name', positional: 1, required: false }), + ]); + }); + }); }); diff --git a/tests/e2e/tests/generate/help-output.ts b/tests/e2e/tests/generate/help-output.ts index 86fc3b988b38..6c87b9e8a2ee 100644 --- a/tests/e2e/tests/generate/help-output.ts +++ b/tests/e2e/tests/generate/help-output.ts @@ -25,6 +25,11 @@ export default function () { "description": "Fake schematic", "schema": "./fake-schema.json" }, + "fake-reversed": { + "factory": "./fake", + "description": "Fake schematic", + "schema": "./fake-reversed-schema.json" + }, } }`, [join(genRoot, 'fake-schema.json')]: ` @@ -63,6 +68,31 @@ export default function () { "description": "optB" } } + }`, + [join(genRoot, 'fake-reversed-schema.json')]: ` + { + "$id": "FakeReversedSchema", + "title": "Fake Reversed Schema", + "type": "object", + "required": ["b"], + "properties": { + "b": { + "type": "string", + "description": "b.", + "$default": { + "$source": "argv", + "index": 0 + } + }, + "a": { + "type": "string", + "description": "a.", + "$default": { + "$source": "argv", + "index": 1 + } + } + } }`, [join(genRoot, 'fake.js')]: ` function def(options) { @@ -79,6 +109,10 @@ export default function () { assert.match(stdout, /ng generate fake-schematics:fake \[b\]/); assert.match(stdout, /opt-a[\s\S]*opt-b[\s\S]*opt-c/); }) + .then(() => ng('generate', 'fake-schematics:fake-reversed', '--help')) + .then(({ stdout }) => { + assert.match(stdout, /ng generate fake-schematics:fake-reversed \[a\]/); + }) // set up default collection. .then(() => updateJsonFile('angular.json', (json) => { From d4d30b3ed9438013e30dad3ac349a87abd770521 Mon Sep 17 00:00:00 2001 From: rk Date: Tue, 20 Oct 2026 18:35:23 +0500 Subject: [PATCH 2/3] fixup! fix(@angular/cli): keep schematic positional arguments in index order --- .../cli/src/command-builder/utilities/json-schema_spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts index fa5d5ba3a1b5..117b325f26b1 100644 --- a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts +++ b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts @@ -382,9 +382,10 @@ describe('parseJsonSchemaToOptions', () => { const registry = new schema.CoreSchemaRegistry(); const options = await parseJsonSchemaToOptions(registry, jsonSchema, /* interactive= */ true); - expect(options.filter((opt) => opt.positional !== undefined)).toEqual([ + expect(options).toEqual([ jasmine.objectContaining({ name: 'preset', positional: 0, required: false }), jasmine.objectContaining({ name: 'name', positional: 1, required: false }), + jasmine.objectContaining({ name: 'path', positional: undefined, required: false }), ]); }); }); From 882ba2d35bba203b849ee0fe11779fdb745b21b4 Mon Sep 17 00:00:00 2001 From: rk Date: Tue, 20 Oct 2026 18:42:58 +0500 Subject: [PATCH 3/3] fixup! fix(@angular/cli): keep schematic positional arguments in index order --- tests/e2e/tests/generate/help-output.ts | 44 +++---------------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/tests/e2e/tests/generate/help-output.ts b/tests/e2e/tests/generate/help-output.ts index 6c87b9e8a2ee..bd5c71a7d9d1 100644 --- a/tests/e2e/tests/generate/help-output.ts +++ b/tests/e2e/tests/generate/help-output.ts @@ -25,11 +25,6 @@ export default function () { "description": "Fake schematic", "schema": "./fake-schema.json" }, - "fake-reversed": { - "factory": "./fake", - "description": "Fake schematic", - "schema": "./fake-reversed-schema.json" - }, } }`, [join(genRoot, 'fake-schema.json')]: ` @@ -37,14 +32,14 @@ export default function () { "$id": "FakeSchema", "title": "Fake Schema", "type": "object", - "required": ["a"], + "required": ["b"], "properties": { "b": { "type": "string", "description": "b.", "$default": { "$source": "argv", - "index": 1 + "index": 0 } }, "a": { @@ -52,7 +47,7 @@ export default function () { "description": "a.", "$default": { "$source": "argv", - "index": 0 + "index": 1 } }, "optC": { @@ -68,31 +63,6 @@ export default function () { "description": "optB" } } - }`, - [join(genRoot, 'fake-reversed-schema.json')]: ` - { - "$id": "FakeReversedSchema", - "title": "Fake Reversed Schema", - "type": "object", - "required": ["b"], - "properties": { - "b": { - "type": "string", - "description": "b.", - "$default": { - "$source": "argv", - "index": 0 - } - }, - "a": { - "type": "string", - "description": "a.", - "$default": { - "$source": "argv", - "index": 1 - } - } - } }`, [join(genRoot, 'fake.js')]: ` function def(options) { @@ -106,13 +76,9 @@ export default function () { ) .then(() => ng('generate', 'fake-schematics:fake', '--help')) .then(({ stdout }) => { - assert.match(stdout, /ng generate fake-schematics:fake \[b\]/); + assert.match(stdout, /ng generate fake-schematics:fake \[a\]/); assert.match(stdout, /opt-a[\s\S]*opt-b[\s\S]*opt-c/); }) - .then(() => ng('generate', 'fake-schematics:fake-reversed', '--help')) - .then(({ stdout }) => { - assert.match(stdout, /ng generate fake-schematics:fake-reversed \[a\]/); - }) // set up default collection. .then(() => updateJsonFile('angular.json', (json) => { @@ -123,7 +89,7 @@ export default function () { .then(() => ng('generate', 'fake', '--help')) // verify same output .then(({ stdout }) => { - assert.match(stdout, /ng generate fake \[b\]/); + assert.match(stdout, /ng generate fake \[a\]/); assert.match(stdout, /opt-a[\s\S]*opt-b[\s\S]*opt-c/); })