From b834690534d42f603a5b297f9cbdc6f82bdd5bb2 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 24 Feb 2026 17:04:04 +0000 Subject: [PATCH 01/12] feat(selectBox): add separator option styling and update focus outlines Co-authored-by: Copilot --- src/vs/base/browser/ui/selectBox/selectBox.ts | 2 + .../browser/ui/selectBox/selectBoxCustom.css | 40 ++++++++++++++++++- .../browser/ui/selectBox/selectBoxCustom.ts | 17 +++++--- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 335c2c9c09bdc..e70edcbac5f57 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -51,11 +51,13 @@ export interface ISelectOptionItem { descriptionIsMarkdown?: boolean; readonly descriptionMarkdownActionHandler?: MarkdownActionHandler; isDisabled?: boolean; + isSeparator?: boolean; } export const SeparatorSelectOption: Readonly = Object.freeze({ text: '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true, + isSeparator: true, }); export interface ISelectBoxStyles extends IListStyles { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css index b2665393270ab..117c5a6cb2a72 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css @@ -6,7 +6,7 @@ .monaco-select-box-dropdown-container { display: none; box-sizing: border-box; - border-radius: var(--vscode-cornerRadius-small); + border-radius: var(--vscode-cornerRadius-large); box-shadow: 0 2px 8px var(--vscode-widget-shadow); } @@ -45,6 +45,11 @@ padding: 5px 6px; } +/* Remove focus ring around the list */ +.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list:focus::before { + outline: 0 !important; +} + .monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row { cursor: pointer; padding-left: 2px; @@ -76,6 +81,39 @@ } +/* Separator styling */ +.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-separator { + cursor: default; + border-radius: 0; + padding: 0; +} + +.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-separator > .option-text { + visibility: hidden; + width: 0; + float: none; +} + +.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-separator > .option-detail { + display: none; +} + +.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-separator > .option-decorator-right { + color: var(--vscode-descriptionForeground); + font-size: 12px; +} + +.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-separator::after { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 50%; + height: 1px; + background-color: var(--vscode-menu-separatorBackground); +} + + /* Accepted CSS hiding technique for accessibility reader text */ /* https://webaim.org/techniques/css/invisiblecontent/ */ diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index f6c2ff1cb4fec..21136145c406f 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -71,6 +71,13 @@ class SelectListRenderer implements IListRenderer .select-box-dropdown-list-container .monaco-list .monaco-list-row:not(.option-disabled):not(.focused):hover { background-color: ${this.styles.listHoverBackground} !important; }`); } - // Match quick input outline styles - ignore for disabled options + // Match action widget outline styles - ignore for disabled options if (this.styles.listFocusOutline) { - content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { outline: 1.6px dotted ${this.styles.listFocusOutline} !important; outline-offset: -1.6px !important; }`); + content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { outline: 1px solid ${this.styles.listFocusOutline} !important; outline-offset: -1px !important; }`); } if (this.styles.listHoverOutline) { - content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:not(.option-disabled):not(.focused):hover { outline: 1.6px dashed ${this.styles.listHoverOutline} !important; outline-offset: -1.6px !important; }`); + content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:not(.option-disabled):not(.focused):hover { outline: 1px solid ${this.styles.listHoverOutline} !important; outline-offset: -1px !important; }`); } // Clear list styles on focus and on hover for disabled options @@ -425,11 +432,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi const background = this.styles.selectBackground ?? ''; const listBackground = cssJs.asCssValueWithDefault(this.styles.selectListBackground, background); + this.selectDropDownContainer.style.backgroundColor = listBackground; this.selectDropDownListContainer.style.backgroundColor = listBackground; this.selectionDetailsPane.style.backgroundColor = listBackground; - const optionsBorder = this.styles.focusBorder ?? ''; - this.selectDropDownContainer.style.outlineColor = optionsBorder; - this.selectDropDownContainer.style.outlineOffset = '-1px'; this.selectList.style(this.styles); } From 0f53f0e0ebfdd2b5879ba5108978a285f10630b0 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Tue, 24 Feb 2026 17:06:40 +0000 Subject: [PATCH 02/12] feat(debug): add separator option to select box in StartDebugActionViewItem --- .../workbench/contrib/debug/browser/debugActionViewItems.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 43c4dba2f34b6..4b74c19bf585f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -265,7 +265,11 @@ export class StartDebugActionViewItem extends BaseActionViewItem { }); }); - this.selectBox.setOptions(this.debugOptions.map((data, index): ISelectOptionItem => ({ text: data.label, isDisabled: disabledIdxs.indexOf(index) !== -1 })), this.selected); + this.selectBox.setOptions(this.debugOptions.map((data, index): ISelectOptionItem => ({ + text: data.label, + isDisabled: disabledIdxs.indexOf(index) !== -1, + isSeparator: data.label === SeparatorSelectOption.text, + })), this.selected); } private _setAriaLabel(title: string): void { From 40db213e284678a5ed0da6afca3060956b050aa9 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 25 Feb 2026 15:03:43 +0000 Subject: [PATCH 03/12] feat(selectBox): inherit font size from select button for dropdown consistency --- src/vs/base/browser/ui/selectBox/selectBoxCustom.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 21136145c406f..ec6ce9a51bb17 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -515,6 +515,12 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private renderSelectDropDown(container: HTMLElement, preLayoutPosition?: boolean): IDisposable { container.appendChild(this.selectDropDownContainer); + // Inherit font-size from the select button so the dropdown matches + const computedFontSize = dom.getWindow(this.selectElement).getComputedStyle(this.selectElement).fontSize; + if (computedFontSize) { + this.selectDropDownContainer.style.fontSize = computedFontSize; + } + // Pre-Layout allows us to change position this.layoutSelectDropDown(preLayoutPosition); From 7f9ec73230fb080ea8885aae69e1c277d8e0b72c Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 25 Feb 2026 15:19:32 +0000 Subject: [PATCH 04/12] feat(selectBox): enhance separator handling and update focus ring behavior Co-authored-by: Copilot --- src/vs/base/browser/ui/selectBox/selectBoxCustom.css | 2 +- src/vs/base/browser/ui/selectBox/selectBoxCustom.ts | 5 +++++ .../workbench/contrib/debug/browser/debugActionViewItems.ts | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css index 117c5a6cb2a72..583a1e2b5f761 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css @@ -45,7 +45,7 @@ padding: 5px 6px; } -/* Remove focus ring around the list */ +/* Remove list-level focus ring — individual rows show their own focus indicators */ .monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list:focus::before { outline: 0 !important; } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index ec6ce9a51bb17..ec1ac45cc0572 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -75,6 +75,7 @@ class SelectListRenderer implements IListRenderer { + if (element.isSeparator) { + return localize('selectBoxSeparator', "separator"); + } + let label = element.text; if (element.detail) { label += `. ${element.detail}`; diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 4b74c19bf585f..1028ec8992eb8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -198,12 +198,14 @@ export class StartDebugActionViewItem extends BaseActionViewItem { const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; let lastGroup: string | undefined; const disabledIdxs: number[] = []; + const separatorIdxs: number[] = []; manager.getAllConfigurations().forEach(({ launch, name, presentation }) => { if (lastGroup !== presentation?.group) { lastGroup = presentation?.group; if (this.debugOptions.length) { this.debugOptions.push({ label: SeparatorSelectOption.text, handler: () => Promise.resolve(false) }); disabledIdxs.push(this.debugOptions.length - 1); + separatorIdxs.push(this.debugOptions.length - 1); } } if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { @@ -239,6 +241,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { this.debugOptions.push({ label: SeparatorSelectOption.text, handler: () => Promise.resolve(false) }); disabledIdxs.push(this.debugOptions.length - 1); + separatorIdxs.push(this.debugOptions.length - 1); this.providers.forEach(p => { @@ -268,7 +271,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { this.selectBox.setOptions(this.debugOptions.map((data, index): ISelectOptionItem => ({ text: data.label, isDisabled: disabledIdxs.indexOf(index) !== -1, - isSeparator: data.label === SeparatorSelectOption.text, + isSeparator: separatorIdxs.indexOf(index) !== -1, })), this.selected); } From 5039f6a9db8d93dbc7686a8430e4509303f90bfc Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Wed, 25 Feb 2026 15:29:11 +0000 Subject: [PATCH 05/12] feat(selectBox): enhance option creation to support separators in native select box --- .../browser/ui/selectBox/selectBoxCustom.ts | 47 +++++++++++-------- .../browser/ui/selectBox/selectBoxNative.ts | 9 +++- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index ec1ac45cc0572..d0b20189ec4d3 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -963,22 +963,23 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.hideSelectDropDown(true); } - // List navigation - have to handle a disabled option (jump over) + // List navigation - have to handle disabled options (jump over) private onDownArrow(e: StandardKeyboardEvent): void { if (this.selected < this.options.length - 1) { dom.EventHelper.stop(e, true); - // Skip disabled options - const nextOptionDisabled = this.options[this.selected + 1].isDisabled; + // Skip over all contiguous disabled options + let next = this.selected + 1; + while (next < this.options.length && this.options[next].isDisabled) { + next++; + } - if (nextOptionDisabled && this.options.length > this.selected + 2) { - this.selected += 2; - } else if (nextOptionDisabled) { + if (next >= this.options.length) { return; - } else { - this.selected++; } + this.selected = next; + // Set focus/selection - only fire event when closing drop-down or on blur this.select(this.selected); this.selectList.setFocus([this.selected]); @@ -989,13 +990,19 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private onUpArrow(e: StandardKeyboardEvent): void { if (this.selected > 0) { dom.EventHelper.stop(e, true); - // Skip disabled options - const previousOptionDisabled = this.options[this.selected - 1].isDisabled; - if (previousOptionDisabled && this.selected > 1) { - this.selected -= 2; - } else { - this.selected--; + + // Skip over all contiguous disabled options + let prev = this.selected - 1; + while (prev >= 0 && this.options[prev].isDisabled) { + prev--; } + + if (prev < 0) { + return; + } + + this.selected = prev; + // Set focus/selection - only fire event when closing drop-down or on blur this.select(this.selected); this.selectList.setFocus([this.selected]); @@ -1013,10 +1020,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selected = this.selectList.getFocus()[0]; // Shift selection down if we land on a disabled option - if (this.options[this.selected].isDisabled && this.selected < this.options.length - 1) { + while (this.selected < this.options.length - 1 && this.options[this.selected].isDisabled) { this.selected++; - this.selectList.setFocus([this.selected]); } + this.selectList.setFocus([this.selected]); this.selectList.reveal(this.selected); this.select(this.selected); }, 1); @@ -1032,10 +1039,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selected = this.selectList.getFocus()[0]; // Shift selection up if we land on a disabled option - if (this.options[this.selected].isDisabled && this.selected > 0) { + while (this.selected > 0 && this.options[this.selected].isDisabled) { this.selected--; - this.selectList.setFocus([this.selected]); } + this.selectList.setFocus([this.selected]); this.selectList.reveal(this.selected); this.select(this.selected); }, 1); @@ -1048,7 +1055,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi return; } this.selected = 0; - if (this.options[this.selected].isDisabled && this.selected > 1) { + while (this.selected < this.options.length - 1 && this.options[this.selected].isDisabled) { this.selected++; } this.selectList.setFocus([this.selected]); @@ -1063,7 +1070,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi return; } this.selected = this.options.length - 1; - if (this.options[this.selected].isDisabled && this.selected > 1) { + while (this.selected > 0 && this.options[this.selected].isDisabled) { this.selected--; } this.selectList.setFocus([this.selected]); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index 9eebae7dbb138..ad772fad93ce2 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -98,7 +98,7 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { this.selectElement.options.length = 0; this.options.forEach((option, index) => { - this.selectElement.add(this.createOption(option.text, index, option.isDisabled)); + this.selectElement.add(this.createOption(option.text, index, option.isDisabled, option.isSeparator)); }); } @@ -179,12 +179,17 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { } - private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement { + private createOption(value: string, index: number, disabled?: boolean, isSeparator?: boolean): HTMLOptionElement { const option = document.createElement('option'); option.value = value; option.text = value; option.disabled = !!disabled; + if (isSeparator) { + option.disabled = true; + option.setAttribute('role', 'separator'); + } + return option; } } From 911b82a84b7afef36679ad59f3742b44621840a1 Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 11:21:09 +0000 Subject: [PATCH 06/12] fix(selectBox): improve handling of disabled options and separators --- .../browser/ui/selectBox/selectBoxCustom.css | 1 - .../browser/ui/selectBox/selectBoxCustom.ts | 40 +++++++++++++------ .../browser/ui/selectBox/selectBoxNative.ts | 3 +- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css index 583a1e2b5f761..62f4224062975 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css @@ -113,7 +113,6 @@ background-color: var(--vscode-menu-separatorBackground); } - /* Accepted CSS hiding technique for accessibility reader text */ /* https://webaim.org/techniques/css/invisiblecontent/ */ diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index d0b20189ec4d3..62ec845a1b848 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -1017,12 +1017,16 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // Allow scrolling to settle setTimeout(() => { - this.selected = this.selectList.getFocus()[0]; + let candidate = this.selectList.getFocus()[0]; // Shift selection down if we land on a disabled option - while (this.selected < this.options.length - 1 && this.options[this.selected].isDisabled) { - this.selected++; + while (candidate < this.options.length - 1 && this.options[candidate].isDisabled) { + candidate++; } + if (this.options[candidate].isDisabled) { + return; + } + this.selected = candidate; this.selectList.setFocus([this.selected]); this.selectList.reveal(this.selected); this.select(this.selected); @@ -1036,12 +1040,16 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // Allow scrolling to settle setTimeout(() => { - this.selected = this.selectList.getFocus()[0]; + let candidate = this.selectList.getFocus()[0]; // Shift selection up if we land on a disabled option - while (this.selected > 0 && this.options[this.selected].isDisabled) { - this.selected--; + while (candidate > 0 && this.options[candidate].isDisabled) { + candidate--; + } + if (this.options[candidate].isDisabled) { + return; } + this.selected = candidate; this.selectList.setFocus([this.selected]); this.selectList.reveal(this.selected); this.select(this.selected); @@ -1054,10 +1062,14 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi if (this.options.length < 2) { return; } - this.selected = 0; - while (this.selected < this.options.length - 1 && this.options[this.selected].isDisabled) { - this.selected++; + let candidate = 0; + while (candidate < this.options.length - 1 && this.options[candidate].isDisabled) { + candidate++; } + if (this.options[candidate].isDisabled) { + return; + } + this.selected = candidate; this.selectList.setFocus([this.selected]); this.selectList.reveal(this.selected); this.select(this.selected); @@ -1069,10 +1081,14 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi if (this.options.length < 2) { return; } - this.selected = this.options.length - 1; - while (this.selected > 0 && this.options[this.selected].isDisabled) { - this.selected--; + let candidate = this.options.length - 1; + while (candidate > 0 && this.options[candidate].isDisabled) { + candidate--; + } + if (this.options[candidate].isDisabled) { + return; } + this.selected = candidate; this.selectList.setFocus([this.selected]); this.selectList.reveal(this.selected); this.select(this.selected); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index ad772fad93ce2..0c7ac5bcb35a3 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -183,10 +183,9 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { const option = document.createElement('option'); option.value = value; option.text = value; - option.disabled = !!disabled; + option.disabled = !!disabled || !!isSeparator; if (isSeparator) { - option.disabled = true; option.setAttribute('role', 'separator'); } From aed186d12ea6d1fe569f70d665220db25861820a Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 11:27:10 +0000 Subject: [PATCH 07/12] fix(selectBox): enhance handling of disabled options in selection navigation Co-authored-by: Copilot --- .../browser/ui/selectBox/selectBoxCustom.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 62ec845a1b848..b7cbca1525069 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -788,7 +788,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // SetUp list mouse controller - control navigation, disabled items, focus this._register(dom.addDisposableListener(this.selectList.getHTMLElement(), dom.EventType.POINTER_UP, e => this.onPointerUp(e))); - this._register(this.selectList.onMouseOver(e => typeof e.index !== 'undefined' && this.selectList.setFocus([e.index]))); + this._register(this.selectList.onMouseOver(e => typeof e.index !== 'undefined' && !this.options[e.index]?.isDisabled && this.selectList.setFocus([e.index]))); this._register(this.selectList.onDidChangeFocus(e => this.onListFocus(e))); this._register(dom.addDisposableListener(this.selectDropDownContainer, dom.EventType.FOCUS_OUT, e => { @@ -948,6 +948,12 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private onEnter(e: StandardKeyboardEvent): void { dom.EventHelper.stop(e); + // Ignore if current selection is disabled (e.g. separator) + if (this.options[this.selected]?.isDisabled) { + this.hideSelectDropDown(true); + return; + } + // Only fire if selection change if (this.selected !== this._currentSelection) { this._currentSelection = this.selected; @@ -1019,9 +1025,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi setTimeout(() => { let candidate = this.selectList.getFocus()[0]; - // Shift selection down if we land on a disabled option - while (candidate < this.options.length - 1 && this.options[candidate].isDisabled) { - candidate++; + // Shift selection up if we land on a disabled option + while (candidate > 0 && this.options[candidate].isDisabled) { + candidate--; } if (this.options[candidate].isDisabled) { return; @@ -1042,9 +1048,9 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi setTimeout(() => { let candidate = this.selectList.getFocus()[0]; - // Shift selection up if we land on a disabled option - while (candidate > 0 && this.options[candidate].isDisabled) { - candidate--; + // Shift selection down if we land on a disabled option + while (candidate < this.options.length - 1 && this.options[candidate].isDisabled) { + candidate++; } if (this.options[candidate].isDisabled) { return; From a6bcce17ede062097d21484fb8665e8e8a51125a Mon Sep 17 00:00:00 2001 From: David Dossett <25163139+daviddossett@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:04:34 -0800 Subject: [PATCH 08/12] Align custom menu styling with chat picker visuals --- extensions/theme-2026/themes/styles.css | 6 ++++++ src/vs/base/browser/ui/menu/menu.ts | 10 +++++----- src/vs/platform/theme/browser/defaultStyles.ts | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index 14e1bb6cb1020..17685dec3b858 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -287,11 +287,17 @@ box-shadow: var(--shadow-lg); border: none; border-radius: var(--radius-lg); + background: color-mix(in srgb, var(--vscode-menu-background) 60%, transparent) !important; + backdrop-filter: var(--backdrop-blur-md); + -webkit-backdrop-filter: var(--backdrop-blur-md); } .monaco-workbench .monaco-menu-container > .monaco-scrollable-element { border-radius: var(--radius-lg) !important; box-shadow: var(--shadow-lg) !important; + background: color-mix(in srgb, var(--vscode-menu-background) 60%, transparent) !important; + backdrop-filter: var(--backdrop-blur-md); + -webkit-backdrop-filter: var(--backdrop-blur-md); } .monaco-workbench .action-widget { diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index c747ea1cd87da..bd6298d0485a0 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -321,7 +321,7 @@ export class Menu extends ActionBar { const fgColor = style.foregroundColor ?? ''; const bgColor = style.backgroundColor ?? ''; const border = style.borderColor ? `1px solid ${style.borderColor}` : ''; - const borderRadius = '5px'; + const borderRadius = 'var(--vscode-cornerRadius-large)'; const shadow = style.shadowColor ? `0 2px 8px ${style.shadowColor}` : ''; scrollElement.style.outline = border; @@ -1022,7 +1022,7 @@ export function getMenuWidgetCSS(style: IMenuStyles, isForShadowDom: boolean): s let result = /* css */` .monaco-menu { font-size: 13px; - border-radius: 5px; + border-radius: var(--vscode-cornerRadius-large); min-width: 160px; } @@ -1137,11 +1137,11 @@ ${formatRule(Codicon.menuSubmenu)} .monaco-menu .monaco-action-bar.vertical .action-menu-item { flex: 1 1 auto; display: flex; - height: 2em; + height: 24px; align-items: center; position: relative; margin: 0 4px; - border-radius: 4px; + border-radius: var(--vscode-cornerRadius-medium); } .monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .keybinding, @@ -1270,7 +1270,7 @@ ${formatRule(Codicon.menuSubmenu)} } .monaco-menu .monaco-action-bar.vertical .action-menu-item { - height: 2em; + height: 24px; } .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), diff --git a/src/vs/platform/theme/browser/defaultStyles.ts b/src/vs/platform/theme/browser/defaultStyles.ts index 49e08fbf8dab6..dc1b16855e20f 100644 --- a/src/vs/platform/theme/browser/defaultStyles.ts +++ b/src/vs/platform/theme/browser/defaultStyles.ts @@ -244,8 +244,8 @@ export const defaultMenuStyles: IMenuStyles = { borderColor: asCssVariable(menuBorder), foregroundColor: asCssVariable(menuForeground), backgroundColor: asCssVariable(menuBackground), - selectionForegroundColor: asCssVariable(menuSelectionForeground), - selectionBackgroundColor: asCssVariable(menuSelectionBackground), + selectionForegroundColor: asCssVariable(listHoverForeground), + selectionBackgroundColor: asCssVariable(listHoverBackground), selectionBorderColor: asCssVariable(menuSelectionBorder), separatorColor: asCssVariable(menuSeparatorBackground), scrollbarShadow: asCssVariable(scrollbarShadow), From b6843d221e205215f9161df8a832ae3555ef243d Mon Sep 17 00:00:00 2001 From: mrleemurray Date: Thu, 26 Feb 2026 18:09:48 +0000 Subject: [PATCH 09/12] fix(imports): reorder imports for better readability --- src/vs/platform/theme/browser/defaultStyles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/theme/browser/defaultStyles.ts b/src/vs/platform/theme/browser/defaultStyles.ts index dc1b16855e20f..bd8147af0d4ac 100644 --- a/src/vs/platform/theme/browser/defaultStyles.ts +++ b/src/vs/platform/theme/browser/defaultStyles.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IButtonStyles } from '../../../base/browser/ui/button/button.js'; import { IKeybindingLabelStyles } from '../../../base/browser/ui/keybindingLabel/keybindingLabel.js'; -import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropOverBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listDropBetweenBackground, radioActiveBackground, radioActiveForeground, radioInactiveBackground, radioInactiveForeground, radioInactiveBorder, radioInactiveHoverBackground, radioActiveBorder, checkboxDisabledBackground, checkboxDisabledForeground, widgetBorder } from '../common/colorRegistry.js'; +import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssVariable, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputActiveOptionBackground, editorWidgetBackground, editorWidgetForeground, contrastBorder, checkboxBorder, checkboxBackground, checkboxForeground, problemsErrorIconForeground, problemsWarningIconForeground, problemsInfoIconForeground, inputBackground, inputForeground, inputBorder, textLinkForeground, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFilterWidgetShadow, badgeBackground, badgeForeground, breadcrumbsBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, activeContrastBorder, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropOverBackground, listFocusAndSelectionOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, tableColumnsBorder, tableOddRowsBackgroundColor, treeIndentGuidesStroke, asCssVariableWithDefault, editorWidgetBorder, focusBorder, pickerGroupForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, selectBackground, selectBorder, selectForeground, selectListBackground, treeInactiveIndentGuidesStroke, menuBorder, menuForeground, menuBackground, menuSelectionBorder, menuSeparatorBackground, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listDropBetweenBackground, radioActiveBackground, radioActiveForeground, radioInactiveBackground, radioInactiveForeground, radioInactiveBorder, radioInactiveHoverBackground, radioActiveBorder, checkboxDisabledBackground, checkboxDisabledForeground, widgetBorder } from '../common/colorRegistry.js'; import { IProgressBarStyles } from '../../../base/browser/ui/progressbar/progressbar.js'; import { ICheckboxStyles, IToggleStyles } from '../../../base/browser/ui/toggle/toggle.js'; import { IDialogStyles } from '../../../base/browser/ui/dialog/dialog.js'; From d2f1f7ce97967101dd73f72df52dc6f1733c2961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Sun, 1 Mar 2026 08:48:08 +0100 Subject: [PATCH 10/12] Inline Compile & CompileCLI stages into platform specific jobs (#296006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: ralph loop checkpoint - 2026-02-17 12:48 * fix shell script * Inline compile stage into platform jobs Remove the standalone Compile stage from the ADO pipeline. Each platform job (Windows, Linux, macOS, Alpine, Web) now compiles TypeScript itself instead of downloading a shared Compilation artifact. - Add VSCODE_RUN_CHECKS parameter to Linux jobs for hygiene/lint/CG - Add VSCODE_RUN_COMPILE_EXTRAS parameter to macOS jobs for telemetry extraction and sourcemap upload - Remove VSCODE_COMPILE_ONLY parameter entirely - Delete product-compile.yml (no longer referenced) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Always run telemetry extraction on macOS builds The macOS Universal app merge requires both x64 and arm64 builds to have identical file sets. Telemetry extraction was only running on arm64 (via VSCODE_RUN_COMPILE_EXTRAS), causing the universal merge to fail due to missing telemetry-core.json and telemetry-extensions.json in the x64 build. Move telemetry extraction outside the VSCODE_RUN_COMPILE_EXTRAS gate so it runs on all macOS builds. Sourcemap upload remains gated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Run telemetry extraction on all client builds All client builds (Linux, Windows, macOS) need telemetry-core.json and telemetry-extensions.json. Previously only macOS arm64 ran extract-telemetry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Only run telemetry extraction on Linux x64 (client build) Linux arm64 and armhf are server-only builds, no need for telemetry files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove .ralph scaffolding files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix telemetry extraction on Windows with native PowerShell The bash extract-telemetry.sh script fails on Windows because the Unix bin shim gets interpreted as Node.js code. Use a native PowerShell implementation that calls the extractor JS entry point directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Handle missing telemetry files gracefully on Windows The telemetry extractor may skip emitting declarations-resolved.json when no events are found. Handle this case with a warning instead of failing the build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use deterministic build date from git commit timestamp When each platform compiles independently, the build date embedded in cli.js (via INSERT_PRODUCT_CONFIGURATION) differs between machines because each uses new Date().toISOString(). This causes the macOS Universal app merge to fail since cli.js SHA differs between x64/arm64. Fix: use the git commit date (git log -1 --format=%cI HEAD) instead of the current wall-clock time. This ensures all independent builds on different machines produce identical timestamps. Updated in: - build/lib/date.ts: writeISODate() uses git commit date - build/next/index.ts: bundle(), transpile, and readISODate fallback all use git commit date Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove redundant extensions-ci task from CI pipelines core-ci (esbuild path) already includes cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, and compileExtensionMediaBuildTask. Running extensions-ci in parallel caused a race condition where core-ci's rimraf of .build/extensions clashed with extensions-ci writing to the same directory. Also removes dead code: - extensions-ci and extensions-ci-pr task definitions (fully subsumed) - core-ci-old task (useEsbuildTranspile is always true) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * remove VSCODE_RUN_COMPILE_EXTRAS * address PR feedback regarding code duplication of `getGitCommitDate()` function by exporting a single helper function from `build/lib/date.ts` and importing it in `build/next/index.ts` to ensure consistent behavior and improve code maintainability. * update readISODate function to return git commit date instead of current date * add telemetry sorting script and integrate into build process for consistent output * refactor telemetry extraction process: replace shell script with TypeScript implementation * update skill * update telemetry-extractor dependency to version 1.19.0 * fix build * fix more duplicate telemetry definition issues * cleanup * refactor: consolidate validation checks into quality checks and remove obsolete tasks * bust the cache * undo cache bust * fix expression * fix * fix: update Azure storage account name in quality checks * fix: initialize stages set with 'Quality' * fix: add VSCODE_BUILD_TYPE parameter with options for Product and CI builds * fix: update Azure Pipeline CLI to use parameters instead of variables for queueing builds * fix: update VSCODE_BUILD_TYPE parameter values for clarity * fix: update default value for VSCODE_BUILD_TYPE parameter to 'Product' * leaner * even leaner * only npm ci in build * :lipstick: * run entire npm ci * fix * fix * fix it * Inline CompileCLI into platform stages - Remove centralized CompileCLI stage - Move CLI jobs into Windows, Linux, macOS stages as independent jobs - CLI jobs now compile, publish unsigned mid-job, sign, and publish signed - Platform compile jobs use deemon + waitForArtifacts for async CLI download - Delete separate CLI sign jobs (now merged into CLI compile jobs) - Remove CompileCLI from publish.ts stage tracking * fix: macOS CLI signing - use proper directory structure for ESRP * fix: add BUILDS_API_URL to Windows and Linux job templates * fix: label Linux CLI jobs clearly --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: João Moreno <22350+joaomoreno@users.noreply.github.com> --- .github/copilot-instructions.md | 1 + .github/skills/azure-pipelines/SKILL.md | 144 ++++++++++---- .../skills/azure-pipelines/azure-pipeline.ts | 119 ++++++------ .github/workflows/pr.yml | 2 +- .../alpine/product-build-alpine.yml | 14 +- .../common/extract-telemetry.sh | 19 -- .../common/extract-telemetry.ts | 95 ++++++++++ build/azure-pipelines/common/publish.ts | 10 +- .../darwin/product-build-darwin-cli-sign.yml | 86 --------- .../darwin/product-build-darwin-cli.yml | 67 ++++++- .../steps/product-build-darwin-cli-sign.yml | 53 ------ .../steps/product-build-darwin-compile.yml | 36 +++- .../linux/product-build-linux-ci.yml | 4 + .../linux/product-build-linux-cli.yml | 2 +- .../linux/product-build-linux.yml | 5 + .../steps/product-build-linux-compile.yml | 40 +++- build/azure-pipelines/product-build-macos.yml | 12 +- build/azure-pipelines/product-build.yml | 179 +++++++----------- ...compile.yml => product-quality-checks.yml} | 130 +++++++------ .../product-validation-checks.yml | 40 ---- .../azure-pipelines/web/product-build-web.yml | 14 +- .../win32/product-build-win32-cli-sign.yml | 83 -------- .../win32/product-build-win32-cli.yml | 66 ++++++- .../win32/product-build-win32.yml | 1 + .../steps/product-build-win32-cli-sign.yml | 61 ------ .../steps/product-build-win32-compile.yml | 40 ++-- build/darwin/create-universal-app.ts | 2 +- build/gulpfile.extensions.ts | 12 -- build/gulpfile.vscode.ts | 12 +- build/lib/date.ts | 16 +- build/next/index.ts | 24 ++- build/next/working.md | 4 +- extensions/git/src/cloneManager.ts | 12 +- package-lock.json | 140 +++++++------- package.json | 2 +- .../common/extensionGalleryService.ts | 2 +- .../browser/agentSessionsWelcome.ts | 4 +- .../themes/browser/workbenchThemeService.ts | 2 +- 38 files changed, 771 insertions(+), 784 deletions(-) delete mode 100755 build/azure-pipelines/common/extract-telemetry.sh create mode 100644 build/azure-pipelines/common/extract-telemetry.ts delete mode 100644 build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml delete mode 100644 build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml rename build/azure-pipelines/{product-compile.yml => product-quality-checks.yml} (64%) delete mode 100644 build/azure-pipelines/product-validation-checks.yml delete mode 100644 build/azure-pipelines/win32/product-build-win32-cli-sign.yml delete mode 100644 build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 62d002fc4564b..06005d9424ba3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -58,6 +58,7 @@ MANDATORY: Always check the `VS Code - Build` watch task output via #runTasks/ge - Monitor the `VS Code - Build` task outputs for real-time compilation errors as you make changes - This task runs `Core - Build` and `Ext - Build` to incrementally compile VS Code TypeScript sources and built-in extensions - Start the task if it's not already running in the background +- For TypeScript changes in the `build` folder, you can simply run `npm run typecheck` in the `build` folder. ### TypeScript validation steps - Use the run test tool if you need to run tests. If that tool is not available, then you can use `scripts/test.sh` (or `scripts\test.bat` on Windows) for unit tests (add `--grep ` to filter tests) or `scripts/test-integration.sh` (or `scripts\test-integration.bat` on Windows) for integration tests (integration tests end with .integrationTest.ts or are in /extensions/). diff --git a/.github/skills/azure-pipelines/SKILL.md b/.github/skills/azure-pipelines/SKILL.md index b7b2e164e038d..9790401995258 100644 --- a/.github/skills/azure-pipelines/SKILL.md +++ b/.github/skills/azure-pipelines/SKILL.md @@ -66,21 +66,24 @@ Use the [queue command](./azure-pipeline.ts) to queue a validation build: ```bash # Queue a build on the current branch -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue +node .github/skills/azure-pipelines/azure-pipeline.ts queue # Queue with a specific source branch -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue --branch my-feature-branch +node .github/skills/azure-pipelines/azure-pipeline.ts queue --branch my-feature-branch -# Queue with custom variables (e.g., to skip certain stages) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue --variables "SKIP_TESTS=true" +# Queue with custom parameters +node .github/skills/azure-pipelines/azure-pipeline.ts queue --parameter "VSCODE_BUILD_WEB=false" --parameter "VSCODE_PUBLISH=false" + +# Parameter value with spaces +node .github/skills/azure-pipelines/azure-pipeline.ts queue --parameter "VSCODE_BUILD_TYPE=Product Build" ``` > **Important**: Before queueing a new build, cancel any previous builds on the same branch that you no longer need. This frees up build agents and reduces resource waste: > ```bash > # Find the build ID from status, then cancel it -> node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status -> node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id -> node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue +> node .github/skills/azure-pipelines/azure-pipeline.ts status +> node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id +> node .github/skills/azure-pipelines/azure-pipeline.ts queue > ``` ### Script Options @@ -89,9 +92,43 @@ node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts |--------|-------------| | `--branch ` | Source branch to build (default: current git branch) | | `--definition ` | Pipeline definition ID (default: 111) | -| `--variables ` | Pipeline variables in `KEY=value` format, space-separated | +| `--parameter ` | Pipeline parameter in `KEY=value` format (repeatable) | +| `--parameters ` | Space-separated parameters in `KEY=value KEY2=value2` format | | `--dry-run` | Print the command without executing | +### Product Build Queue Parameters (`build/azure-pipelines/product-build.yml`) + +| Name | Type | Default | Allowed Values | Description | +|------|------|---------|----------------|-------------| +| `VSCODE_QUALITY` | string | `insider` | `exploration`, `insider`, `stable` | Build quality channel | +| `VSCODE_BUILD_TYPE` | string | `Product Build` | `Product`, `CI` | Build mode for Product vs CI | +| `NPM_REGISTRY` | string | `https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/` | any URL | Custom npm registry | +| `CARGO_REGISTRY` | string | `sparse+https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/Cargo/index/` | any URL | Custom Cargo registry | +| `VSCODE_BUILD_WIN32` | boolean | `true` | `true`, `false` | Build Windows x64 | +| `VSCODE_BUILD_WIN32_ARM64` | boolean | `true` | `true`, `false` | Build Windows arm64 | +| `VSCODE_BUILD_LINUX` | boolean | `true` | `true`, `false` | Build Linux x64 | +| `VSCODE_BUILD_LINUX_SNAP` | boolean | `true` | `true`, `false` | Build Linux x64 Snap | +| `VSCODE_BUILD_LINUX_ARM64` | boolean | `true` | `true`, `false` | Build Linux arm64 | +| `VSCODE_BUILD_LINUX_ARMHF` | boolean | `true` | `true`, `false` | Build Linux armhf | +| `VSCODE_BUILD_ALPINE` | boolean | `true` | `true`, `false` | Build Alpine x64 | +| `VSCODE_BUILD_ALPINE_ARM64` | boolean | `true` | `true`, `false` | Build Alpine arm64 | +| `VSCODE_BUILD_MACOS` | boolean | `true` | `true`, `false` | Build macOS x64 | +| `VSCODE_BUILD_MACOS_ARM64` | boolean | `true` | `true`, `false` | Build macOS arm64 | +| `VSCODE_BUILD_MACOS_UNIVERSAL` | boolean | `true` | `true`, `false` | Build macOS universal (requires both macOS arches) | +| `VSCODE_BUILD_WEB` | boolean | `true` | `true`, `false` | Build Web artifacts | +| `VSCODE_PUBLISH` | boolean | `true` | `true`, `false` | Publish to builds.code.visualstudio.com | +| `VSCODE_RELEASE` | boolean | `false` | `true`, `false` | Trigger release flow if successful | +| `VSCODE_STEP_ON_IT` | boolean | `false` | `true`, `false` | Skip tests | + +Example: run a quick CI-oriented validation with minimal publish/release side effects: + +```bash +node .github/skills/azure-pipelines/azure-pipeline.ts queue \ + --parameter "VSCODE_BUILD_TYPE=CI Build" \ + --parameter "VSCODE_PUBLISH=false" \ + --parameter "VSCODE_RELEASE=false" +``` + --- ## Checking Build Status @@ -99,17 +136,17 @@ node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts Use the [status command](./azure-pipeline.ts) to monitor a running build: ```bash -# Get status of the most recent build on your branch -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status +# Get status of the most recent builds +node .github/skills/azure-pipelines/azure-pipeline.ts status # Get overview of a specific build by ID -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 # Watch build status (refreshes every 30 seconds) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --watch +node .github/skills/azure-pipelines/azure-pipeline.ts status --watch # Watch with custom interval (60 seconds) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --watch 60 +node .github/skills/azure-pipelines/azure-pipeline.ts status --watch 60 ``` ### Script Options @@ -133,10 +170,10 @@ Use the [cancel command](./azure-pipeline.ts) to stop a running build: ```bash # Cancel a build by ID (use status command to find IDs) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 # Dry run (show what would be cancelled) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 --dry-run +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 --dry-run ``` ### Script Options @@ -149,6 +186,44 @@ node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts --- +## Testing Pipeline Changes + +When the user asks to **test changes in an Azure Pipelines build**, follow this workflow: + +1. **Queue a new build** on the current branch +2. **Poll for completion** by periodically checking the build status until it finishes + +### Polling for Build Completion + +Use a shell loop with `sleep` to poll the build status. The `sleep` command works on all major operating systems: + +```bash +# Queue the build and note the build ID from output (e.g., 123456) +node .github/skills/azure-pipelines/azure-pipeline.ts queue + +# Poll every 60 seconds until complete (works on macOS, Linux, and Windows with Git Bash/WSL) +# Replace with the actual build ID from the queue command +while true; do + node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id --json 2>/dev/null | grep -q '"status": "completed"' && break + sleep 60 +done + +# Check final result +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id +``` + +Alternatively, use the built-in `--watch` flag which handles polling automatically: + +```bash +node .github/skills/azure-pipelines/azure-pipeline.ts queue +# Use the build ID returned by the queue command +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id --watch +``` + +> **Note**: The `--watch` flag polls every 30 seconds by default. Use `--watch 60` for a 60-second interval to reduce API calls. + +--- + ## Common Workflows ### 1. Quick Pipeline Validation @@ -159,45 +234,50 @@ git add -A && git commit -m "test: pipeline changes" git push origin HEAD # Check for any previous builds on this branch and cancel if needed -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id # if there's an active build +node .github/skills/azure-pipelines/azure-pipeline.ts status +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id # if there's an active build # Queue and watch the new build -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --watch +node .github/skills/azure-pipelines/azure-pipeline.ts queue +node .github/skills/azure-pipelines/azure-pipeline.ts status --watch ``` ### 2. Investigate a Build ```bash # Get overview of a build (shows stages, artifacts, and log IDs) -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 # Download a specific log for deeper inspection -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 --download-log 5 +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 --download-log 5 # Download an artifact -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 --download-artifact unsigned_vscode_cli_win32_x64_cli +node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id 123456 --download-artifact unsigned_vscode_cli_win32_x64_cli ``` -### 3. Test with Modified Variables +### 3. Test with Modified Parameters ```bash -# Skip expensive stages during validation -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue --variables "VSCODE_BUILD_SKIP_INTEGRATION_TESTS=true" +# Customize build matrix for quicker validation +node .github/skills/azure-pipelines/azure-pipeline.ts queue \ + --parameter "VSCODE_BUILD_TYPE=CI Build" \ + --parameter "VSCODE_BUILD_WEB=false" \ + --parameter "VSCODE_BUILD_ALPINE=false" \ + --parameter "VSCODE_BUILD_ALPINE_ARM64=false" \ + --parameter "VSCODE_PUBLISH=false" ``` ### 4. Cancel a Running Build ```bash # First, find the build ID -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status +node .github/skills/azure-pipelines/azure-pipeline.ts status # Cancel a specific build by ID -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 # Dry run to see what would be cancelled -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 --dry-run +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id 123456 --dry-run ``` ### 5. Iterate on Pipeline Changes @@ -210,12 +290,12 @@ git add -A && git commit --amend --no-edit git push --force-with-lease origin HEAD # Find the outdated build ID and cancel it -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id +node .github/skills/azure-pipelines/azure-pipeline.ts status +node .github/skills/azure-pipelines/azure-pipeline.ts cancel --build-id # Queue a fresh build and monitor -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue -node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --watch +node .github/skills/azure-pipelines/azure-pipeline.ts queue +node .github/skills/azure-pipelines/azure-pipeline.ts status --watch ``` --- diff --git a/.github/skills/azure-pipelines/azure-pipeline.ts b/.github/skills/azure-pipelines/azure-pipeline.ts index 7fad554050bb3..fbb74b5dd4aa0 100644 --- a/.github/skills/azure-pipelines/azure-pipeline.ts +++ b/.github/skills/azure-pipelines/azure-pipeline.ts @@ -9,7 +9,7 @@ * A unified command-line tool for managing Azure Pipeline builds. * * Usage: - * node --experimental-strip-types azure-pipeline.ts [options] + * node azure-pipeline.ts [options] * * Commands: * queue - Queue a new pipeline build @@ -38,8 +38,8 @@ const NUMERIC_ID_PATTERN = /^\d+$/; const MAX_ID_LENGTH = 15; const BRANCH_PATTERN = /^[a-zA-Z0-9_\-./]+$/; const MAX_BRANCH_LENGTH = 256; -const VARIABLE_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*=[a-zA-Z0-9_\-./: ]*$/; -const MAX_VARIABLE_LENGTH = 256; +const PARAMETER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*=[a-zA-Z0-9_\-./: +]*$/; +const MAX_PARAMETER_LENGTH = 256; const ARTIFACT_NAME_PATTERN = /^[a-zA-Z0-9_\-.]+$/; const MAX_ARTIFACT_NAME_LENGTH = 256; const MIN_WATCH_INTERVAL = 5; @@ -88,7 +88,7 @@ interface Artifact { interface QueueArgs { branch: string; definitionId: string; - variables: string; + parameters: string[]; dryRun: boolean; help: boolean; } @@ -159,19 +159,18 @@ function validateBranch(value: string): void { } } -function validateVariables(value: string): void { - if (!value) { +function validateParameters(values: string[]): void { + if (!values.length) { return; } - const vars = value.split(' ').filter(v => v.length > 0); - for (const v of vars) { - if (v.length > MAX_VARIABLE_LENGTH) { - console.error(colors.red(`Error: Variable '${v.substring(0, 20)}...' is too long (max ${MAX_VARIABLE_LENGTH} characters)`)); + for (const parameter of values) { + if (parameter.length > MAX_PARAMETER_LENGTH) { + console.error(colors.red(`Error: Parameter '${parameter.substring(0, 20)}...' is too long (max ${MAX_PARAMETER_LENGTH} characters)`)); process.exit(1); } - if (!VARIABLE_PATTERN.test(v)) { - console.error(colors.red(`Error: Invalid variable format '${v}'`)); - console.log('Expected format: KEY=value (alphanumeric, underscores, hyphens, dots, slashes, colons, spaces in value)'); + if (!PARAMETER_PATTERN.test(parameter)) { + console.error(colors.red(`Error: Invalid parameter format '${parameter}'`)); + console.log('Expected format: KEY=value (alphanumeric, underscores, hyphens, dots, slashes, colons, plus signs, spaces in value)'); process.exit(1); } } @@ -612,7 +611,7 @@ class AzureDevOpsClient { return JSON.parse(result); } - async queueBuild(definitionId: string, branch: string, variables?: string): Promise { + async queueBuild(definitionId: string, branch: string, parameters: string[] = []): Promise { const args = [ 'pipelines', 'run', '--organization', this.organization, @@ -621,8 +620,8 @@ class AzureDevOpsClient { '--branch', branch, ]; - if (variables) { - args.push('--variables', ...variables.split(' ')); + if (parameters.length > 0) { + args.push('--parameters', ...parameters); } args.push('--output', 'json'); @@ -771,7 +770,7 @@ class AzureDevOpsClient { // ============================================================================ function printQueueUsage(): void { - const scriptName = 'node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue'; + const scriptName = 'node .github/skills/azure-pipelines/azure-pipeline.ts queue'; console.log(`Usage: ${scriptName} [options]`); console.log(''); console.log('Queue an Azure DevOps pipeline build for VS Code.'); @@ -779,21 +778,23 @@ function printQueueUsage(): void { console.log('Options:'); console.log(' --branch Source branch to build (default: current git branch)'); console.log(' --definition Pipeline definition ID (default: 111)'); - console.log(' --variables Pipeline variables in "KEY=value KEY2=value2" format'); + console.log(' --parameter Pipeline parameter in "KEY=value" format (repeatable)'); + console.log(' --parameters Space-separated parameter list in "KEY=value KEY2=value2" format'); console.log(' --dry-run Print the command without executing'); console.log(' --help Show this help message'); console.log(''); console.log('Examples:'); console.log(` ${scriptName} # Queue build on current branch`); console.log(` ${scriptName} --branch my-feature # Queue build on specific branch`); - console.log(` ${scriptName} --variables "SKIP_TESTS=true" # Queue with custom variables`); + console.log(` ${scriptName} --parameter "VSCODE_BUILD_WEB=false" --parameter "VSCODE_PUBLISH=false"`); + console.log(` ${scriptName} --parameter "VSCODE_BUILD_TYPE=Product Build" # Parameter values with spaces`); } function parseQueueArgs(args: string[]): QueueArgs { const result: QueueArgs = { branch: '', definitionId: DEFAULT_DEFINITION_ID, - variables: '', + parameters: [], dryRun: false, help: false, }; @@ -807,8 +808,15 @@ function parseQueueArgs(args: string[]): QueueArgs { case '--definition': result.definitionId = args[++i] || DEFAULT_DEFINITION_ID; break; - case '--variables': - result.variables = args[++i] || ''; + case '--parameter': { + const parameter = args[++i] || ''; + if (parameter) { + result.parameters.push(parameter); + } + break; + } + case '--parameters': + result.parameters.push(...(args[++i] || '').split(' ').filter(v => v.length > 0)); break; case '--dry-run': result.dryRun = true; @@ -829,7 +837,7 @@ function parseQueueArgs(args: string[]): QueueArgs { function validateQueueArgs(args: QueueArgs): void { validateNumericId(args.definitionId, '--definition'); validateBranch(args.branch); - validateVariables(args.variables); + validateParameters(args.parameters); } async function runQueueCommand(args: string[]): Promise { @@ -860,8 +868,8 @@ async function runQueueCommand(args: string[]): Promise { console.log(`Project: ${colors.green(PROJECT)}`); console.log(`Definition: ${colors.green(parsedArgs.definitionId)}`); console.log(`Branch: ${colors.green(branch)}`); - if (parsedArgs.variables) { - console.log(`Variables: ${colors.green(parsedArgs.variables)}`); + if (parsedArgs.parameters.length > 0) { + console.log(`Parameters: ${colors.green(parsedArgs.parameters.join(' '))}`); } console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); @@ -875,8 +883,8 @@ async function runQueueCommand(args: string[]): Promise { '--id', parsedArgs.definitionId, '--branch', branch, ]; - if (parsedArgs.variables) { - cmdArgs.push('--variables', ...parsedArgs.variables.split(' ')); + if (parsedArgs.parameters.length > 0) { + cmdArgs.push('--parameters', ...parsedArgs.parameters); } cmdArgs.push('--output', 'json'); console.log(`az ${cmdArgs.join(' ')}`); @@ -887,7 +895,7 @@ async function runQueueCommand(args: string[]): Promise { try { const client = new AzureDevOpsClient(ORGANIZATION, PROJECT); - const data = await client.queueBuild(parsedArgs.definitionId, branch, parsedArgs.variables); + const data = await client.queueBuild(parsedArgs.definitionId, branch, parsedArgs.parameters); const buildId = data.id; const buildNumber = data.buildNumber; @@ -904,10 +912,10 @@ async function runQueueCommand(args: string[]): Promise { console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); console.log('To check status, run:'); - console.log(` node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId}`); + console.log(` node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId}`); console.log(''); console.log('To watch progress:'); - console.log(` node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId} --watch`); + console.log(` node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId} --watch`); } catch (e) { const error = e instanceof Error ? e : new Error(String(e)); console.error(colors.red('Error queuing build:')); @@ -921,7 +929,7 @@ async function runQueueCommand(args: string[]): Promise { // ============================================================================ function printStatusUsage(): void { - const scriptName = 'node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status'; + const scriptName = 'node .github/skills/azure-pipelines/azure-pipeline.ts status'; console.log(`Usage: ${scriptName} [options]`); console.log(''); console.log('Get status and logs of an Azure DevOps pipeline build.'); @@ -1068,7 +1076,7 @@ async function runStatusCommand(args: string[]): Promise { if (!buildId) { console.error(colors.red(`Error: No builds found for branch '${branch}'.`)); - console.log('You can queue a new build with: node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts queue'); + console.log('You can queue a new build with: node .github/skills/azure-pipelines/azure-pipeline.ts queue'); process.exit(1); } } @@ -1162,7 +1170,7 @@ async function runStatusCommand(args: string[]): Promise { // ============================================================================ function printCancelUsage(): void { - const scriptName = 'node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts cancel'; + const scriptName = 'node .github/skills/azure-pipelines/azure-pipeline.ts cancel'; console.log(`Usage: ${scriptName} --build-id [options]`); console.log(''); console.log('Cancel a running Azure DevOps pipeline build.'); @@ -1233,7 +1241,7 @@ async function runCancelCommand(args: string[]): Promise { console.error(colors.red('Error: --build-id is required.')); console.log(''); console.log('To find build IDs, run:'); - console.log(' node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status'); + console.log(' node .github/skills/azure-pipelines/azure-pipeline.ts status'); process.exit(1); } @@ -1287,7 +1295,7 @@ async function runCancelCommand(args: string[]): Promise { console.log(''); console.log('The build will transition to "cancelling" state and then "canceled".'); console.log('Check status with:'); - console.log(` node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId}`); + console.log(` node .github/skills/azure-pipelines/azure-pipeline.ts status --build-id ${buildId}`); } catch (e) { const error = e instanceof Error ? e : new Error(String(e)); console.error(''); @@ -1390,15 +1398,15 @@ async function runAllTests(): Promise { validateBranch(''); }); - it('validateVariables accepts valid variable formats', () => { - validateVariables('KEY=value'); - validateVariables('MY_VAR=some-value'); - validateVariables('A=1 B=2 C=3'); - validateVariables('PATH=/usr/bin:path'); + it('validateParameters accepts valid parameter formats', () => { + validateParameters(['KEY=value']); + validateParameters(['MY_VAR=some-value']); + validateParameters(['A=1', 'B=2', 'C=3']); + validateParameters(['PATH=/usr/bin:path']); }); - it('validateVariables accepts empty string', () => { - validateVariables(''); + it('validateParameters accepts empty list', () => { + validateParameters([]); }); it('validateArtifactName accepts valid artifact names', () => { @@ -1429,9 +1437,14 @@ async function runAllTests(): Promise { assert.strictEqual(args.definitionId, '222'); }); - it('parseQueueArgs parses --variables correctly', () => { - const args = parseQueueArgs(['--variables', 'KEY=value']); - assert.strictEqual(args.variables, 'KEY=value'); + it('parseQueueArgs parses --parameters correctly', () => { + const args = parseQueueArgs(['--parameters', 'KEY=value']); + assert.deepStrictEqual(args.parameters, ['KEY=value']); + }); + + it('parseQueueArgs parses repeated --parameter correctly', () => { + const args = parseQueueArgs(['--parameter', 'A=1', '--parameter', 'B=two words']); + assert.deepStrictEqual(args.parameters, ['A=1', 'B=two words']); }); it('parseQueueArgs parses --dry-run correctly', () => { @@ -1440,10 +1453,10 @@ async function runAllTests(): Promise { }); it('parseQueueArgs parses combined arguments', () => { - const args = parseQueueArgs(['--branch', 'main', '--definition', '333', '--variables', 'A=1 B=2', '--dry-run']); + const args = parseQueueArgs(['--branch', 'main', '--definition', '333', '--parameters', 'A=1 B=2', '--dry-run']); assert.strictEqual(args.branch, 'main'); assert.strictEqual(args.definitionId, '333'); - assert.strictEqual(args.variables, 'A=1 B=2'); + assert.deepStrictEqual(args.parameters, ['A=1', 'B=2']); assert.strictEqual(args.dryRun, true); }); @@ -1516,12 +1529,12 @@ async function runAllTests(): Promise { assert.ok(cmd.includes('json')); }); - it('queueBuild includes variables when provided', async () => { + it('queueBuild includes parameters when provided', async () => { const client = new TestableAzureDevOpsClient(ORGANIZATION, PROJECT); - await client.queueBuild('111', 'main', 'KEY=value OTHER=test'); + await client.queueBuild('111', 'main', ['KEY=value', 'OTHER=test']); const cmd = client.capturedCommands[0]; - assert.ok(cmd.includes('--variables')); + assert.ok(cmd.includes('--parameters')); assert.ok(cmd.includes('KEY=value')); assert.ok(cmd.includes('OTHER=test')); }); @@ -1718,7 +1731,7 @@ async function runAllTests(): Promise { describe('Integration Tests', () => { it('full queue command flow constructs correct az commands', async () => { const client = new TestableAzureDevOpsClient(ORGANIZATION, PROJECT); - await client.queueBuild('111', 'feature/test', 'DEBUG=true'); + await client.queueBuild('111', 'feature/test', ['DEBUG=true']); assert.strictEqual(client.capturedCommands.length, 1); const cmd = client.capturedCommands[0]; @@ -1733,7 +1746,7 @@ async function runAllTests(): Promise { assert.ok(cmd.includes('111')); assert.ok(cmd.includes('--branch')); assert.ok(cmd.includes('feature/test')); - assert.ok(cmd.includes('--variables')); + assert.ok(cmd.includes('--parameters')); assert.ok(cmd.includes('DEBUG=true')); }); @@ -1797,7 +1810,7 @@ async function runAllTests(): Promise { // ============================================================================ function printMainUsage(): void { - const scriptName = 'node --experimental-strip-types .github/skills/azure-pipelines/azure-pipeline.ts'; + const scriptName = 'node .github/skills/azure-pipelines/azure-pipeline.ts'; console.log(`Usage: ${scriptName} [options]`); console.log(''); console.log('Azure DevOps Pipeline CLI for VS Code builds.'); diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b988d19f49ea6..bb87ac077bce2 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -77,7 +77,7 @@ jobs: working-directory: build - name: Compile & Hygiene - run: npm exec -- npm-run-all2 -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts + run: npm exec -- npm-run-all2 -lp core-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index 5c5714e9d5b12..a9a1b0d1292ba 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -64,15 +64,6 @@ jobs: KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - script: node build/setup-npm-registry.ts $NPM_REGISTRY condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Registry @@ -173,6 +164,11 @@ jobs: - template: ../common/install-builtin-extensions.yml@self + - script: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + - script: | set -e TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") # TODO@joaomoreno diff --git a/build/azure-pipelines/common/extract-telemetry.sh b/build/azure-pipelines/common/extract-telemetry.sh deleted file mode 100755 index 9cebe22bfd189..0000000000000 --- a/build/azure-pipelines/common/extract-telemetry.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -set -e - -cd $BUILD_STAGINGDIRECTORY -mkdir extraction -cd extraction -git clone --depth 1 https://github.com/microsoft/vscode-extension-telemetry.git -git clone --depth 1 https://github.com/microsoft/vscode-chrome-debug-core.git -git clone --depth 1 https://github.com/microsoft/vscode-node-debug2.git -git clone --depth 1 https://github.com/microsoft/vscode-node-debug.git -git clone --depth 1 https://github.com/microsoft/vscode-html-languageservice.git -git clone --depth 1 https://github.com/microsoft/vscode-json-languageservice.git -node $BUILD_SOURCESDIRECTORY/node_modules/.bin/vscode-telemetry-extractor --sourceDir $BUILD_SOURCESDIRECTORY --excludedDir $BUILD_SOURCESDIRECTORY/extensions --outputDir . --applyEndpoints -node $BUILD_SOURCESDIRECTORY/node_modules/.bin/vscode-telemetry-extractor --config $BUILD_SOURCESDIRECTORY/build/azure-pipelines/common/telemetry-config.json -o . -mkdir -p $BUILD_SOURCESDIRECTORY/.build/telemetry -mv declarations-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-core.json -mv config-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-extensions.json -cd .. -rm -rf extraction diff --git a/build/azure-pipelines/common/extract-telemetry.ts b/build/azure-pipelines/common/extract-telemetry.ts new file mode 100644 index 0000000000000..a5fafac71d5f8 --- /dev/null +++ b/build/azure-pipelines/common/extract-telemetry.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import cp from 'child_process'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +const BUILD_STAGINGDIRECTORY = process.env.BUILD_STAGINGDIRECTORY ?? fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-telemetry-')); +const BUILD_SOURCESDIRECTORY = process.env.BUILD_SOURCESDIRECTORY ?? path.resolve(import.meta.dirname, '..', '..', '..'); + +const extractionDir = path.join(BUILD_STAGINGDIRECTORY, 'extraction'); +fs.mkdirSync(extractionDir, { recursive: true }); + +const repos = [ + 'https://github.com/microsoft/vscode-extension-telemetry.git', + 'https://github.com/microsoft/vscode-chrome-debug-core.git', + 'https://github.com/microsoft/vscode-node-debug2.git', + 'https://github.com/microsoft/vscode-node-debug.git', + 'https://github.com/microsoft/vscode-html-languageservice.git', + 'https://github.com/microsoft/vscode-json-languageservice.git', +]; + +for (const repo of repos) { + cp.execSync(`git clone --depth 1 ${repo}`, { cwd: extractionDir, stdio: 'inherit' }); +} + +const extractor = path.join(BUILD_SOURCESDIRECTORY, 'node_modules', '@vscode', 'telemetry-extractor', 'out', 'extractor.js'); +const telemetryConfig = path.join(BUILD_SOURCESDIRECTORY, 'build', 'azure-pipelines', 'common', 'telemetry-config.json'); + +interface ITelemetryConfigEntry { + eventPrefix: string; + sourceDirs: string[]; + excludedDirs: string[]; + applyEndpoints: boolean; + patchDebugEvents?: boolean; +} + +const pipelineExtensionsPathPrefix = '../../s/extensions/'; + +const telemetryConfigEntries = JSON.parse(fs.readFileSync(telemetryConfig, 'utf8')) as ITelemetryConfigEntry[]; +let hasLocalConfigOverrides = false; + +const resolvedTelemetryConfigEntries = telemetryConfigEntries.map(entry => { + const sourceDirs = entry.sourceDirs.map(sourceDir => { + if (!sourceDir.startsWith(pipelineExtensionsPathPrefix)) { + return sourceDir; + } + + const sourceDirInExtractionDir = path.resolve(extractionDir, sourceDir); + if (fs.existsSync(sourceDirInExtractionDir)) { + return sourceDir; + } + + const extensionRelativePath = sourceDir.slice(pipelineExtensionsPathPrefix.length); + const sourceDirInWorkspace = path.join(BUILD_SOURCESDIRECTORY, 'extensions', extensionRelativePath); + if (fs.existsSync(sourceDirInWorkspace)) { + hasLocalConfigOverrides = true; + return sourceDirInWorkspace; + } + + return sourceDir; + }); + + return { + ...entry, + sourceDirs, + }; +}); + +const telemetryConfigForExtraction = hasLocalConfigOverrides + ? path.join(extractionDir, 'telemetry-config.local.json') + : telemetryConfig; + +if (hasLocalConfigOverrides) { + fs.writeFileSync(telemetryConfigForExtraction, JSON.stringify(resolvedTelemetryConfigEntries, null, '\t')); +} + +try { + cp.execSync(`node "${extractor}" --sourceDir "${BUILD_SOURCESDIRECTORY}" --excludedDir "${path.join(BUILD_SOURCESDIRECTORY, 'extensions')}" --outputDir . --applyEndpoints`, { cwd: extractionDir, stdio: 'inherit' }); + cp.execSync(`node "${extractor}" --config "${telemetryConfigForExtraction}" -o .`, { cwd: extractionDir, stdio: 'inherit' }); +} catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`Telemetry extraction failed: ${message}`); + process.exit(1); +} + +const telemetryDir = path.join(BUILD_SOURCESDIRECTORY, '.build', 'telemetry'); +fs.mkdirSync(telemetryDir, { recursive: true }); +fs.renameSync(path.join(extractionDir, 'declarations-resolved.json'), path.join(telemetryDir, 'telemetry-core.json')); +fs.renameSync(path.join(extractionDir, 'config-resolved.json'), path.join(telemetryDir, 'telemetry-extensions.json')); + +fs.rmSync(extractionDir, { recursive: true, force: true }); diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 572efa57bf998..fd621e4224021 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -970,15 +970,7 @@ async function main() { console.log(`\u2705 ${name}`); } - const stages = new Set(['Compile']); - - if ( - e('VSCODE_BUILD_STAGE_LINUX') === 'True' || - e('VSCODE_BUILD_STAGE_MACOS') === 'True' || - e('VSCODE_BUILD_STAGE_WINDOWS') === 'True' - ) { - stages.add('CompileCLI'); - } + const stages = new Set(['Quality']); if (e('VSCODE_BUILD_STAGE_WINDOWS') === 'True') { stages.add('Windows'); } if (e('VSCODE_BUILD_STAGE_LINUX') === 'True') { stages.add('Linux'); } diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml deleted file mode 100644 index 94eee5e476c2a..0000000000000 --- a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml +++ /dev/null @@ -1,86 +0,0 @@ -parameters: - - name: VSCODE_BUILD_MACOS - type: boolean - - name: VSCODE_BUILD_MACOS_ARM64 - type: boolean - -jobs: - - job: macOSCLISign - timeoutInMinutes: 90 - templateContext: - outputParentDirectory: $(Build.ArtifactStagingDirectory)/out - outputs: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_x64_cli/vscode_cli_darwin_x64_cli.zip - artifactName: vscode_cli_darwin_x64_cli - displayName: Publish signed artifact with ID vscode_cli_darwin_x64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_x64_cli - sbomPackageName: "VS Code macOS x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_arm64_cli/vscode_cli_darwin_arm64_cli.zip - artifactName: vscode_cli_darwin_arm64_cli - displayName: Publish signed artifact with ID vscode_cli_darwin_arm64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_arm64_cli - sbomPackageName: "VS Code macOS arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - script: node build/setup-npm-registry.ts $NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - template: ./steps/product-build-darwin-cli-sign.yml@self - parameters: - VSCODE_CLI_ARTIFACTS: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - unsigned_vscode_cli_darwin_x64_cli - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - unsigned_vscode_cli_darwin_arm64_cli diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli.yml b/build/azure-pipelines/darwin/product-build-darwin-cli.yml index dc5a5d79c1457..1b6ea51bd146f 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-cli.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-cli.yml @@ -9,8 +9,8 @@ parameters: jobs: - job: macOSCLI_${{ parameters.VSCODE_ARCH }} - displayName: macOS (${{ upper(parameters.VSCODE_ARCH) }}) - timeoutInMinutes: 60 + displayName: macOS CLI (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 90 pool: name: AcesShared os: macOS @@ -24,11 +24,12 @@ jobs: outputs: - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip - artifactName: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli - displayName: Publish unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli artifact - sbomEnabled: false - isProduction: false + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_$(VSCODE_ARCH)_cli/vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + artifactName: vscode_cli_darwin_$(VSCODE_ARCH)_cli + displayName: Publish vscode_cli_darwin_$(VSCODE_ARCH)_cli artifact + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + sbomPackageName: "VS Code macOS $(VSCODE_ARCH) CLI" + sbomPackageVersion: $(Build.SourceVersion) steps: - template: ../common/checkout.yml@self @@ -83,3 +84,55 @@ jobs: VSCODE_CLI_ENV: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/include + + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - template: ../common/publish-artifact.yml@self + parameters: + targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + artifactName: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + displayName: Publish unsigned CLI + sbomEnabled: false + + - script: | + set -e + mkdir -p $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + cp $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + displayName: Prepare CLI for signing + + - task: ExtractFiles@1 + displayName: Extract unsigned CLI (for SBOM) + inputs: + archiveFilePatterns: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + destinationFolder: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli "*.zip" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign + + - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli "*.zip" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Notarize + + - script: | + set -e + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_$(VSCODE_ARCH)_cli + mv $(Build.ArtifactStagingDirectory)/pkg/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_$(VSCODE_ARCH)_cli/vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + displayName: Rename signed artifact diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml deleted file mode 100644 index 1cd0fe2a8245f..0000000000000 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml +++ /dev/null @@ -1,53 +0,0 @@ -parameters: - - name: VSCODE_CLI_ARTIFACTS - type: object - default: [] - -steps: - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - task: DownloadPipelineArtifact@2 - displayName: Download ${{ target }} - inputs: - artifact: ${{ target }} - path: $(Build.ArtifactStagingDirectory)/pkg/${{ target }} - - - task: ExtractFiles@1 - displayName: Extract artifact - inputs: - archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip - destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - - - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign - - - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Notarize - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - script: | - set -e - ASSET_ID=$(echo "${{ target }}" | sed "s/unsigned_//") - mkdir -p $(Build.ArtifactStagingDirectory)/out/$ASSET_ID - mv $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/${{ target }}.zip $(Build.ArtifactStagingDirectory)/out/$ASSET_ID/$ASSET_ID.zip - echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" - displayName: Set asset id variable diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index 64b91f714016f..cd5f6c287c01c 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -30,15 +30,6 @@ steps: KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - script: node build/setup-npm-registry.ts $NPM_REGISTRY condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Registry @@ -112,11 +103,33 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --detach --wait -- node build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact (background) + - script: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality - template: ../../common/install-builtin-extensions.yml@self + - script: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + + - script: node build/azure-pipelines/common/extract-telemetry.ts + displayName: Generate lists of telemetry events + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - script: | + set -e + npm run compile --prefix test/smoke + npm run compile --prefix test/integration/browser + displayName: Compile test suites (non-OSS) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - script: npm run copy-policy-dto --prefix build && node build/lib/policies/policyGenerator.ts build/lib/policies/policyData.jsonc darwin displayName: Generate policy definitions @@ -147,6 +160,11 @@ steps: displayName: Build server (web) - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --attach -- node build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact + - task: DownloadPipelineArtifact@2 inputs: artifact: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli diff --git a/build/azure-pipelines/linux/product-build-linux-ci.yml b/build/azure-pipelines/linux/product-build-linux-ci.yml index 6c6b102891a7e..619aff676407e 100644 --- a/build/azure-pipelines/linux/product-build-linux-ci.yml +++ b/build/azure-pipelines/linux/product-build-linux-ci.yml @@ -5,6 +5,9 @@ parameters: type: string - name: VSCODE_TEST_SUITE type: string + - name: VSCODE_RUN_CHECKS + type: boolean + default: false jobs: - job: Linux${{ parameters.VSCODE_TEST_SUITE }} @@ -43,6 +46,7 @@ jobs: VSCODE_ARCH: x64 VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_RUN_CHECKS: ${{ parameters.VSCODE_RUN_CHECKS }} ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Electron') }}: VSCODE_RUN_ELECTRON_TESTS: true ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Browser') }}: diff --git a/build/azure-pipelines/linux/product-build-linux-cli.yml b/build/azure-pipelines/linux/product-build-linux-cli.yml index ef160c2cc3849..a9107129b73b5 100644 --- a/build/azure-pipelines/linux/product-build-linux-cli.yml +++ b/build/azure-pipelines/linux/product-build-linux-cli.yml @@ -9,7 +9,7 @@ parameters: jobs: - job: LinuxCLI_${{ parameters.VSCODE_ARCH }} - displayName: Linux (${{ upper(parameters.VSCODE_ARCH) }}) + displayName: Linux CLI (${{ upper(parameters.VSCODE_ARCH) }}) timeoutInMinutes: 60 pool: name: 1es-ubuntu-22.04-x64 diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 31eb7c3d46668..00ffd0aaab07e 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -19,6 +19,9 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false + - name: VSCODE_RUN_CHECKS + type: boolean + default: false jobs: - job: Linux_${{ parameters.VSCODE_ARCH }} @@ -26,6 +29,7 @@ jobs: timeoutInMinutes: 90 variables: DISPLAY: ":10" + BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ NPM_ARCH: ${{ parameters.NPM_ARCH }} VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} templateContext: @@ -110,3 +114,4 @@ jobs: VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} + VSCODE_RUN_CHECKS: ${{ parameters.VSCODE_RUN_CHECKS }} diff --git a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml index 89199ebbbb14c..82e1a19107d20 100644 --- a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml +++ b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml @@ -17,6 +17,9 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false + - name: VSCODE_RUN_CHECKS + type: boolean + default: false steps: - template: ../../common/checkout.yml@self @@ -35,15 +38,6 @@ steps: KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - script: | set -e # Start X server @@ -165,11 +159,34 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --detach --wait -- node build/azure-pipelines/common/waitForArtifacts.ts $(ARTIFACT_PREFIX)vscode_cli_linux_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact (background) + - script: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality - template: ../../common/install-builtin-extensions.yml@self + - script: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - script: node build/azure-pipelines/common/extract-telemetry.ts + displayName: Generate lists of telemetry events + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - script: | + set -e + npm run compile --prefix test/smoke + npm run compile --prefix test/integration/browser + displayName: Compile test suites (non-OSS) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - script: npm run copy-policy-dto --prefix build && node build/lib/policies/policyGenerator.ts build/lib/policies/policyData.jsonc linux displayName: Generate policy definitions @@ -187,6 +204,11 @@ steps: displayName: Build client - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --attach -- node build/azure-pipelines/common/waitForArtifacts.ts $(ARTIFACT_PREFIX)vscode_cli_linux_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact + - task: DownloadPipelineArtifact@2 inputs: artifact: $(ARTIFACT_PREFIX)vscode_cli_linux_$(VSCODE_ARCH)_cli diff --git a/build/azure-pipelines/product-build-macos.yml b/build/azure-pipelines/product-build-macos.yml index cc563953b0071..1330f83567dcf 100644 --- a/build/azure-pipelines/product-build-macos.yml +++ b/build/azure-pipelines/product-build-macos.yml @@ -72,18 +72,8 @@ extends: ubuntu-2004-arm64: image: onebranch.azurecr.io/linux/ubuntu-2004-arm64:latest stages: - - stage: Compile - pool: - name: AcesShared - os: macOS - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - jobs: - - template: build/azure-pipelines/product-compile.yml@self - - stage: macOS - dependsOn: - - Compile + dependsOn: [] pool: name: AcesShared os: macOS diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 77c3dd0665f9e..41dbdbe178c6b 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -26,6 +26,13 @@ parameters: - exploration - insider - stable + - name: VSCODE_BUILD_TYPE + displayName: Build Type + type: string + default: Product + values: + - Product + - CI - name: NPM_REGISTRY displayName: "Custom NPM Registry" type: string @@ -90,10 +97,6 @@ parameters: displayName: "Release build if successful" type: boolean default: false - - name: VSCODE_COMPILE_ONLY - displayName: "Run Compile stage exclusively" - type: boolean - default: false - name: VSCODE_STEP_ON_IT displayName: "Skip tests" type: boolean @@ -119,9 +122,9 @@ variables: - name: VSCODE_BUILD_STAGE_WEB value: ${{ eq(parameters.VSCODE_BUILD_WEB, true) }} - name: VSCODE_CIBUILD - value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} + value: ${{ or(in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'), eq(parameters.VSCODE_BUILD_TYPE, 'CI')) }} - name: VSCODE_PUBLISH - value: ${{ and(eq(parameters.VSCODE_PUBLISH, true), eq(variables.VSCODE_CIBUILD, false), eq(parameters.VSCODE_COMPILE_ONLY, false)) }} + value: ${{ and(eq(parameters.VSCODE_PUBLISH, true), eq(variables.VSCODE_CIBUILD, false)) }} - name: VSCODE_SCHEDULEDBUILD value: ${{ eq(variables['Build.Reason'], 'Schedule') }} - name: VSCODE_STEP_ON_IT @@ -190,74 +193,16 @@ extends: ubuntu-2004-arm64: image: onebranch.azurecr.io/linux/ubuntu-2004-arm64:latest stages: - - stage: Compile + + - stage: Quality + dependsOn: [] pool: - name: AcesShared - os: macOS - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia + name: 1es-ubuntu-22.04-x64 + os: linux jobs: - - template: build/azure-pipelines/product-compile.yml@self - - - ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}: - - stage: ValidationChecks - dependsOn: [] - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - jobs: - - template: build/azure-pipelines/product-validation-checks.yml@self - - - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - stage: CompileCLI - dependsOn: [] - jobs: - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + - template: build/azure-pipelines/product-quality-checks.yml@self - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self - parameters: - VSCODE_ARCH: armhf - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], true), eq(parameters.VSCODE_COMPILE_ONLY, false)) }}: + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - stage: node_modules dependsOn: [] jobs: @@ -283,7 +228,7 @@ extends: VSCODE_ARCH: x64 - template: build/azure-pipelines/web/product-build-web-node-modules.yml@self - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false)) }}: + - ${{ if eq(variables['VSCODE_CIBUILD'], false) }}: - stage: APIScan dependsOn: [] pool: @@ -297,16 +242,26 @@ extends: VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true)) }}: + - ${{ if eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true) }}: - stage: Windows - dependsOn: - - Compile - - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - CompileCLI + dependsOn: [] pool: name: 1es-windows-2022-x64 os: windows jobs: + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - template: build/azure-pipelines/win32/product-build-win32-ci.yml@self parameters: @@ -341,22 +296,32 @@ extends: VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true))) }}: - - template: build/azure-pipelines/win32/product-build-win32-cli-sign.yml@self - parameters: - VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} - VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} - - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_LINUX'], true)) }}: + - ${{ if eq(variables['VSCODE_BUILD_STAGE_LINUX'], true) }}: - stage: Linux - dependsOn: - - Compile - - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - CompileCLI + dependsOn: [] pool: name: 1es-ubuntu-22.04-x64 os: linux jobs: + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: armhf + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - template: build/azure-pipelines/linux/product-build-linux-ci.yml@self parameters: @@ -402,10 +367,9 @@ extends: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_ALPINE'], true)) }}: + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_STAGE_ALPINE'], true)) }}: - stage: Alpine - dependsOn: - - Compile + dependsOn: [] jobs: - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - template: build/azure-pipelines/alpine/product-build-alpine.yml@self @@ -424,12 +388,9 @@ extends: VSCODE_ARCH: arm64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: + - ${{ if eq(variables['VSCODE_BUILD_STAGE_MACOS'], true) }}: - stage: macOS - dependsOn: - - Compile - - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - CompileCLI + dependsOn: [] pool: name: AcesShared os: macOS @@ -438,6 +399,19 @@ extends: variables: BUILDSECMON_OPT_IN: true jobs: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: + - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self parameters: @@ -470,20 +444,13 @@ extends: - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true)) }}: - template: build/azure-pipelines/darwin/product-build-darwin-universal.yml@self - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true))) }}: - - template: build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml@self - parameters: - VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} - VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WEB'], true)) }}: + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_STAGE_WEB'], true)) }}: - stage: Web - dependsOn: - - Compile + dependsOn: [] jobs: - template: build/azure-pipelines/web/product-build-web.yml@self - - ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}: + - ${{ if eq(variables['VSCODE_PUBLISH'], true) }}: - stage: Publish dependsOn: [] jobs: diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-quality-checks.yml similarity index 64% rename from build/azure-pipelines/product-compile.yml rename to build/azure-pipelines/product-quality-checks.yml index bc13d980df2dd..5692a55965a8f 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-quality-checks.yml @@ -1,14 +1,12 @@ jobs: - - job: Compile - timeoutInMinutes: 60 - templateContext: - outputs: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz - artifactName: Compilation - displayName: Publish compilation artifact - isProduction: false - sbomEnabled: false + - job: Quality + displayName: Quality Checks + timeoutInMinutes: 20 + variables: + - name: skipComponentGovernanceDetection + value: true + - name: Codeql.SkipTaskAutoInjection + value: true steps: - template: ./common/checkout.yml@self @@ -30,7 +28,7 @@ jobs: condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Registry - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts compile $(node -p process.arch) > .build/packagelockhash + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts quality $(node -p process.arch) > .build/packagelockhash displayName: Prepare node_modules cache key - task: Cache@2 @@ -46,9 +44,6 @@ jobs: - script: | set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file npm config set registry "$NPM_REGISTRY" echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) @@ -71,7 +66,38 @@ jobs: fi echo "Npm install failed $i, trying again..." done + workingDirectory: build env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0 + SYSROOT_ARCH="amd64" VSCODE_SYSROOT_PREFIX="-glibc-2.28-gcc-8.5.0" node -e 'import { getVSCodeSysroot } from "./build/linux/debian/install-sysroot.ts"; (async () => { await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' + env: + VSCODE_ARCH: x64 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download vscode sysroots + + - script: | + set -e + + source ./build/azure-pipelines/linux/setup-env.sh + node build/npm/preinstall.ts + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: x64 + VSCODE_ARCH: x64 ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" @@ -93,43 +119,37 @@ jobs: - script: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality - - template: common/install-builtin-extensions.yml@self - - - script: npm exec -- npm-run-all2 -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts + - script: node build/azure-pipelines/common/checkDistroCommit.ts + displayName: Check distro commit env: GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Compile & Hygiene - - - script: | - set -e - - [ -d "out-build" ] || { echo "ERROR: out-build folder is missing" >&2; exit 1; } - [ -n "$(find out-build -mindepth 1 2>/dev/null | head -1)" ] || { echo "ERROR: out-build folder is empty" >&2; exit 1; } - echo "out-build exists and is not empty" + BUILD_SOURCEBRANCH: "$(Build.SourceBranch)" + continueOnError: true + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - ls -d out-vscode-* >/dev/null 2>&1 || { echo "ERROR: No out-vscode-* folders found" >&2; exit 1; } - for folder in out-vscode-*; do - [ -d "$folder" ] || { echo "ERROR: $folder is missing" >&2; exit 1; } - [ -n "$(find "$folder" -mindepth 1 2>/dev/null | head -1)" ] || { echo "ERROR: $folder is empty" >&2; exit 1; } - echo "$folder exists and is not empty" - done + - script: node build/azure-pipelines/common/checkCopilotChatCompatibility.ts --warn-only + displayName: Check Copilot Chat compatibility + continueOnError: true + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - echo "All required compilation folders checked." - displayName: Validate compilation folders + - script: npm exec -- npm-run-all2 -lp hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile & Hygiene - - script: | - set -e - npm run compile - displayName: Compile smoke test suites (non-OSS) - workingDirectory: test/smoke - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: npm run download-builtin-extensions-cg + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download component details of built-in extensions + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - - script: | - set -e - npm run compile - displayName: Compile integration test suites (non-OSS) - workingDirectory: test/integration/browser - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: "Component Detection" + inputs: + sourceScanPath: $(Build.SourcesDirectory) + alertWarningLevel: Medium + continueOnError: true + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - task: AzureCLI@2 displayName: Fetch secrets @@ -142,6 +162,7 @@ jobs: Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) - script: | set -e @@ -151,21 +172,4 @@ jobs: AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ node build/azure-pipelines/upload-sourcemaps.ts displayName: Upload sourcemaps to Azure - - - script: ./build/azure-pipelines/common/extract-telemetry.sh - displayName: Generate lists of telemetry events - - - script: tar -cz --exclude='.build/node_modules_cache' --exclude='.build/node_modules_list.txt' --exclude='.build/distro' -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz $(ls -d .build out-* test/integration/browser/out test/smoke/out test/automation/out 2>/dev/null) - displayName: Compress compilation artifact - - - script: npm run download-builtin-extensions-cg - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Download component details of built-in extensions - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: "Component Detection" - inputs: - sourceScanPath: $(Build.SourcesDirectory) - alertWarningLevel: Medium - continueOnError: true + condition: and(succeeded(), eq(lower(variables['VSCODE_PUBLISH']), 'true')) diff --git a/build/azure-pipelines/product-validation-checks.yml b/build/azure-pipelines/product-validation-checks.yml deleted file mode 100644 index adf61f33c428c..0000000000000 --- a/build/azure-pipelines/product-validation-checks.yml +++ /dev/null @@ -1,40 +0,0 @@ -jobs: - - job: ValidationChecks - displayName: Distro and Extension Validation - timeoutInMinutes: 15 - steps: - - template: ./common/checkout.yml@self - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - template: ./distro/download-distro.yml@self - - - script: node build/azure-pipelines/distro/mixin-quality.ts - displayName: Mixin distro quality - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - script: npm ci - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - script: node build/azure-pipelines/common/checkDistroCommit.ts - displayName: Check distro commit - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - BUILD_SOURCEBRANCH: "$(Build.SourceBranch)" - continueOnError: true - - - script: node build/azure-pipelines/common/checkCopilotChatCompatibility.ts --warn-only - displayName: Check Copilot Chat compatibility - continueOnError: true diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 71932745be7fb..c9916acded34d 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -33,15 +33,6 @@ jobs: KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - script: node build/setup-npm-registry.ts $NPM_REGISTRY condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Registry @@ -118,6 +109,11 @@ jobs: - template: ../common/install-builtin-extensions.yml@self + - script: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + - script: | set -e npm run gulp vscode-web-min-ci diff --git a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml deleted file mode 100644 index fa1328d99e27f..0000000000000 --- a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml +++ /dev/null @@ -1,83 +0,0 @@ -parameters: - - name: VSCODE_BUILD_WIN32 - type: boolean - - name: VSCODE_BUILD_WIN32_ARM64 - type: boolean - -jobs: - - job: WindowsCLISign - timeoutInMinutes: 90 - templateContext: - outputParentDirectory: $(Build.ArtifactStagingDirectory)/out - outputs: - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_x64_cli.zip - artifactName: vscode_cli_win32_x64_cli - displayName: Publish signed artifact with ID vscode_cli_win32_x64_cli - sbomBuildDropPath: $(Build.BinariesDirectory)/sign/unsigned_vscode_cli_win32_x64_cli - sbomPackageName: "VS Code Windows x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_arm64_cli.zip - artifactName: vscode_cli_win32_arm64_cli - displayName: Publish signed artifact with ID vscode_cli_win32_arm64_cli - sbomBuildDropPath: $(Build.BinariesDirectory)/sign/unsigned_vscode_cli_win32_arm64_cli - sbomPackageName: "VS Code Windows arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - steps: - - template: ../common/checkout.yml@self - - - task: NodeTool@0 - displayName: "Use Node.js" - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - powershell: node build/setup-npm-registry.ts $env:NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - exec { npm config set registry "$env:NPM_REGISTRY" } - $NpmrcPath = (npm config get userconfig) - echo "##vso[task.setvariable variable=NPMRC_PATH]$NpmrcPath" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - powershell: | - . azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm ci } - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - retryCountOnTaskFailure: 5 - displayName: Install build dependencies - - - template: ./steps/product-build-win32-cli-sign.yml@self - parameters: - VSCODE_CLI_ARTIFACTS: - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - unsigned_vscode_cli_win32_x64_cli - - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - - unsigned_vscode_cli_win32_arm64_cli diff --git a/build/azure-pipelines/win32/product-build-win32-cli.yml b/build/azure-pipelines/win32/product-build-win32-cli.yml index 5dd69c3b50de3..78461a959eda3 100644 --- a/build/azure-pipelines/win32/product-build-win32-cli.yml +++ b/build/azure-pipelines/win32/product-build-win32-cli.yml @@ -9,22 +9,23 @@ parameters: jobs: - job: WindowsCLI_${{ upper(parameters.VSCODE_ARCH) }} - displayName: Windows (${{ upper(parameters.VSCODE_ARCH) }}) + displayName: Windows CLI (${{ upper(parameters.VSCODE_ARCH) }}) pool: name: 1es-windows-2022-x64 os: windows - timeoutInMinutes: 30 + timeoutInMinutes: 90 variables: VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} templateContext: outputs: - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - output: pipelineArtifact - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli.zip - artifactName: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli - displayName: Publish unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli artifact - sbomEnabled: false - isProduction: false + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_$(VSCODE_ARCH)_cli.zip + artifactName: vscode_cli_win32_$(VSCODE_ARCH)_cli + displayName: Publish vscode_cli_win32_$(VSCODE_ARCH)_cli artifact + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign + sbomPackageName: "VS Code Windows $(VSCODE_ARCH) CLI" + sbomPackageVersion: $(Build.SourceVersion) steps: - template: ../common/checkout.yml@self @@ -75,3 +76,54 @@ jobs: ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: RUSTFLAGS: "-Ctarget-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT:NO" CFLAGS: "/guard:cf /Qspectre" + + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - template: ../common/publish-artifact.yml@self + parameters: + targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli.zip + artifactName: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + displayName: Publish unsigned CLI + sbomEnabled: false + + - task: ExtractFiles@1 + displayName: Extract unsigned CLI + inputs: + archiveFilePatterns: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli.zip + destinationFolder: $(Build.ArtifactStagingDirectory)/sign + + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName + $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" + displayName: Find ESRP CLI + + - powershell: node build\azure-pipelines\common\sign.ts $env:EsrpCliDllPath sign-windows $(Build.ArtifactStagingDirectory)/sign "*.exe" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign + + - task: ArchiveFiles@2 + displayName: Archive signed CLI + inputs: + rootFolderOrFile: $(Build.ArtifactStagingDirectory)/sign + includeRootFolder: false + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_$(VSCODE_ARCH)_cli.zip diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 3a91d3cdd97db..9b4c4e27070ab 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -21,6 +21,7 @@ jobs: timeoutInMinutes: 90 variables: VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ templateContext: outputParentDirectory: $(Build.ArtifactStagingDirectory)/out outputs: diff --git a/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml deleted file mode 100644 index 0caba3d1a2b88..0000000000000 --- a/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml +++ /dev/null @@ -1,61 +0,0 @@ -parameters: - - name: VSCODE_CLI_ARTIFACTS - type: object - default: [] - -steps: - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName - $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" - displayName: Find ESRP CLI - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - task: DownloadPipelineArtifact@2 - displayName: Download artifact - inputs: - artifact: ${{ target }} - path: $(Build.BinariesDirectory)/pkg/${{ target }} - - - task: ExtractFiles@1 - displayName: Extract artifact - inputs: - archiveFilePatterns: $(Build.BinariesDirectory)/pkg/${{ target }}/*.zip - destinationFolder: $(Build.BinariesDirectory)/sign/${{ target }} - - - powershell: node build\azure-pipelines\common\sign.ts $env:EsrpCliDllPath sign-windows $(Build.BinariesDirectory)/sign "*.exe" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - powershell: | - $ASSET_ID = "${{ target }}".replace("unsigned_", ""); - echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" - displayName: Set asset id variable - - - task: ArchiveFiles@2 - displayName: Archive signed files - inputs: - rootFolderOrFile: $(Build.BinariesDirectory)/sign/${{ target }} - includeRootFolder: false - archiveType: zip - archiveFile: $(Build.ArtifactStagingDirectory)/out/$(ASSET_ID).zip diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml index d6412c2342090..3cb6413480af8 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -37,18 +37,6 @@ steps: KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - task: ExtractFiles@1 - displayName: Extract compilation output - inputs: - archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz" - cleanDestinationFolder: false - - powershell: node build/setup-npm-registry.ts $env:NPM_REGISTRY condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Registry @@ -114,11 +102,34 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - pwsh: npx deemon --detach --wait -- node build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact (background) + - powershell: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality - template: ../../common/install-builtin-extensions.yml@self + - powershell: npm run gulp core-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile + + - script: node build/azure-pipelines/common/extract-telemetry.ts + displayName: Generate lists of telemetry events + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm run compile --prefix test/smoke } + exec { npm run compile --prefix test/integration/browser } + displayName: Compile test suites (non-OSS) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - powershell: | npm run copy-policy-dto --prefix build @@ -181,6 +192,11 @@ steps: displayName: Build server (web) - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - pwsh: npx deemon --attach -- node build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for CLI artifact + - task: DownloadPipelineArtifact@2 inputs: artifact: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 26aead0ca19dd..9e90e31491f58 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -28,7 +28,7 @@ async function main(buildDir?: string) { const filesToSkip = [ '**/CodeResources', '**/Credits.rtf', - '**/policies/{*.mobileconfig,**/*.plist}', + '**/policies/{*.mobileconfig,**/*.plist}' ]; await makeUniversalApp({ diff --git a/build/gulpfile.extensions.ts b/build/gulpfile.extensions.ts index e8ee8fa80f477..8f9ac9b2b210b 100644 --- a/build/gulpfile.extensions.ts +++ b/build/gulpfile.extensions.ts @@ -290,19 +290,7 @@ export const compileAllExtensionsBuildTask = task.define('compile-extensions-bui )); gulp.task(compileAllExtensionsBuildTask); -// This task is run in the compilation stage of the CI pipeline. We only compile the non-native extensions since those can be fully built regardless of platform. -// This defers the native extensions to the platform specific stage of the CI pipeline. -gulp.task(task.define('extensions-ci', task.series(compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask))); -const compileExtensionsBuildPullRequestTask = task.define('compile-extensions-build-pr', task.series( - cleanExtensionsBuildTask, - bundleMarketplaceExtensionsBuildTask, - task.define('bundle-extensions-build-pr', () => ext.packageAllLocalExtensionsStream(false, true).pipe(gulp.dest('.build'))), -)); -gulp.task(compileExtensionsBuildPullRequestTask); - -// This task is run in the compilation stage of the PR pipeline. We compile all extensions in it to verify compilation. -gulp.task(task.define('extensions-ci-pr', task.series(compileExtensionsBuildPullRequestTask, compileExtensionMediaBuildTask))); //#endregion diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index 25a360094868e..0dfb90f264b76 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -244,17 +244,16 @@ const minifyVSCodeTask = task.define('minify-vscode', task.series( )); gulp.task(minifyVSCodeTask); -const coreCIOld = task.define('core-ci-old', task.series( +gulp.task(task.define('core-ci-old', task.series( gulp.task('compile-build-with-mangling') as task.Task, task.parallel( gulp.task('minify-vscode') as task.Task, gulp.task('minify-vscode-reh') as task.Task, gulp.task('minify-vscode-reh-web') as task.Task, ) -)); -gulp.task(coreCIOld); +))); -const coreCIEsbuild = task.define('core-ci-esbuild', task.series( +gulp.task(task.define('core-ci', task.series( copyCodiconsTask, compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask, @@ -269,10 +268,7 @@ const coreCIEsbuild = task.define('core-ci-esbuild', task.series( task.define('esbuild-vscode-reh-min', () => runEsbuildBundle('out-vscode-reh-min', true, true, 'server', `${sourceMappingURLBase}/core`)), task.define('esbuild-vscode-reh-web-min', () => runEsbuildBundle('out-vscode-reh-web-min', true, true, 'server-web', `${sourceMappingURLBase}/core`)), ) -)); -gulp.task(coreCIEsbuild); - -gulp.task(task.define('core-ci', useEsbuildTranspile ? coreCIEsbuild : coreCIOld)); +))); const coreCIPR = task.define('core-ci-pr', task.series( gulp.task('compile-build-without-mangling') as task.Task, diff --git a/build/lib/date.ts b/build/lib/date.ts index 68d52521349ed..99ba91a5282df 100644 --- a/build/lib/date.ts +++ b/build/lib/date.ts @@ -5,9 +5,23 @@ import path from 'path'; import fs from 'fs'; +import { execSync } from 'child_process'; const root = path.join(import.meta.dirname, '..', '..'); +/** + * Get the ISO date for the build. Uses the git commit date of HEAD + * so that independent builds on different machines produce the same + * timestamp (required for deterministic builds, e.g. macOS Universal). + */ +export function getGitCommitDate(): string { + try { + return execSync('git log -1 --format=%cI HEAD', { cwd: root, encoding: 'utf8' }).trim(); + } catch { + return new Date().toISOString(); + } +} + /** * Writes a `outDir/date` file with the contents of the build * so that other tasks during the build process can use it and @@ -18,7 +32,7 @@ export function writeISODate(outDir: string) { const outDirectory = path.join(root, outDir); fs.mkdirSync(outDirectory, { recursive: true }); - const date = new Date().toISOString(); + const date = getGitCommitDate(); fs.writeFileSync(path.join(outDirectory, 'date'), date, 'utf8'); resolve(); diff --git a/build/next/index.ts b/build/next/index.ts index b0120837efa26..77886ad43a989 100644 --- a/build/next/index.ts +++ b/build/next/index.ts @@ -7,11 +7,13 @@ import * as esbuild from 'esbuild'; import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; + import glob from 'glob'; import gulpWatch from '../lib/watch/index.ts'; import { nlsPlugin, createNLSCollector, finalizeNLS, postProcessNLS } from './nls-plugin.ts'; import { convertPrivateFields, adjustSourceMap, type ConvertPrivateFieldsResult } from './private-to-property.ts'; import { getVersion } from '../lib/getVersion.ts'; +import { getGitCommitDate } from '../lib/date.ts'; import product from '../../product.json' with { type: 'json' }; import packageJson from '../../package.json' with { type: 'json' }; import { useEsbuildTranspile } from '../buildConfig.ts'; @@ -72,7 +74,8 @@ const extensionHostEntryPoints = [ ]; function isExtensionHostBundle(filePath: string): boolean { - return extensionHostEntryPoints.some(ep => filePath.endsWith(`${ep}.js`)); + const normalized = filePath.replaceAll('\\', '/'); + return extensionHostEntryPoints.some(ep => normalized.endsWith(`${ep}.js`)); } // Workers - shared between targets @@ -419,13 +422,13 @@ function scanBuiltinExtensions(extensionsRoot: string): Array { async function bundle(outDir: string, doMinify: boolean, doNls: boolean, doManglePrivates: boolean, target: BuildTarget, sourceMapBaseUrl?: string): Promise { await cleanDir(outDir); - // Write build date file (used by packaging to embed in product.json) + // Write build date file (used by packaging to embed in product.json). + // Reuse the date from out-build/date if it exists (written by the gulp + // writeISODate task) so that all parallel bundle outputs share the same + // timestamp - this is required for deterministic builds (e.g. macOS Universal). const outDirPath = path.join(REPO_ROOT, outDir); await fs.promises.mkdir(outDirPath, { recursive: true }); - await fs.promises.writeFile(path.join(outDirPath, 'date'), new Date().toISOString(), 'utf8'); + let buildDate: string; + try { + buildDate = await fs.promises.readFile(path.join(REPO_ROOT, 'out-build', 'date'), 'utf8'); + } catch { + buildDate = getGitCommitDate(); + } + await fs.promises.writeFile(path.join(outDirPath, 'date'), buildDate, 'utf8'); console.log(`[bundle] ${SRC_DIR} → ${outDir} (target: ${target})${doMinify ? ' (minify)' : ''}${doNls ? ' (nls)' : ''}${doManglePrivates ? ' (mangle-privates)' : ''}`); const t1 = Date.now(); @@ -1128,7 +1140,7 @@ async function main(): Promise { // Write build date file (used by packaging to embed in product.json) const outDirPath = path.join(REPO_ROOT, outDir); await fs.promises.mkdir(outDirPath, { recursive: true }); - await fs.promises.writeFile(path.join(outDirPath, 'date'), new Date().toISOString(), 'utf8'); + await fs.promises.writeFile(path.join(outDirPath, 'date'), getGitCommitDate(), 'utf8'); console.log(`[transpile] ${SRC_DIR} → ${outDir}${options.excludeTests ? ' (excluding tests)' : ''}`); const t1 = Date.now(); diff --git a/build/next/working.md b/build/next/working.md index b59b347611dbd..298d1fb8cbd48 100644 --- a/build/next/working.md +++ b/build/next/working.md @@ -37,7 +37,7 @@ In [gulpfile.vscode.ts](../gulpfile.vscode.ts#L228-L242), the `core-ci` task use - `runEsbuildTranspile()` → transpile command - `runEsbuildBundle()` → bundle command -Old gulp-based bundling renamed to `core-ci-OLD`. +Old gulp-based bundling renamed to `core-ci-old`. --- @@ -134,7 +134,7 @@ npm run gulp vscode-reh-web-darwin-arm64-min 1. **`BUILD_INSERT_PACKAGE_CONFIGURATION`** - Server bootstrap files ([bootstrap-meta.ts](../../src/bootstrap-meta.ts)) have this marker for package.json injection. Currently handled by [inlineMeta.ts](../lib/inlineMeta.ts) in the old build's packaging step. -2. **Mangling** - The new build doesn't do TypeScript-based mangling yet. Old `core-ci` with mangling is now `core-ci-OLD`. +2. **Mangling** - The new build doesn't do TypeScript-based mangling yet. Old `core-ci` with mangling is now `core-ci-old`. 3. **Entry point duplication** - Entry points are duplicated between [buildfile.ts](../buildfile.ts) and [index.ts](index.ts). Consider consolidating. diff --git a/extensions/git/src/cloneManager.ts b/extensions/git/src/cloneManager.ts index 49d57d8763c63..cee231dda779c 100644 --- a/extensions/git/src/cloneManager.ts +++ b/extensions/git/src/cloneManager.ts @@ -39,7 +39,8 @@ export class CloneManager { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); @@ -74,7 +75,8 @@ export class CloneManager { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); @@ -105,7 +107,8 @@ export class CloneManager { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); @@ -115,7 +118,8 @@ export class CloneManager { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); diff --git a/package-lock.json b/package-lock.json index ef5b2106cd61e..34be077fa1f8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,7 +88,7 @@ "@vscode/component-explorer-cli": "^0.1.1-12", "@vscode/gulp-electron": "1.40.0", "@vscode/l10n-dev": "0.0.35", - "@vscode/telemetry-extractor": "^1.10.2", + "@vscode/telemetry-extractor": "^1.20.2", "@vscode/test-cli": "^0.0.6", "@vscode/test-electron": "^2.4.0", "@vscode/test-web": "^0.0.76", @@ -2291,14 +2291,14 @@ } }, "node_modules/@ts-morph/common": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.20.0.tgz", - "integrity": "sha512-7uKjByfbPpwuzkstL3L5MQyuXPSKdoNG93Fmi2JoDcTf3pEP731JdRFAduRVkOs8oqxPsXKA+ScrWkdQ8t/I+Q==", + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.26.1.tgz", + "integrity": "sha512-Sn28TGl/4cFpcM+jwsH1wLncYq3FtN/BIpem+HOygfBWPT5pAeS5dB4VFVzV8FbnOKHpDLZmvAl4AjPEev5idA==", "dev": true, + "license": "MIT", "dependencies": { - "fast-glob": "^3.2.12", - "minimatch": "^7.4.3", - "mkdirp": "^2.1.6", + "fast-glob": "^3.3.2", + "minimatch": "^9.0.4", "path-browserify": "^1.0.1" } }, @@ -2313,30 +2313,16 @@ } }, "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@ts-morph/common/node_modules/mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", - "dev": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3597,17 +3583,21 @@ "license": "MIT" }, "node_modules/@vscode/telemetry-extractor": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@vscode/telemetry-extractor/-/telemetry-extractor-1.10.2.tgz", - "integrity": "sha512-hn+KDSwIRj7LzDSFd9HALkc80UY1g16nQgWztHml+nxAZU3Hw/EoWEEDxOncvDYq9YcV+tX/cVHrVjbNL2Dg0g==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@vscode/telemetry-extractor/-/telemetry-extractor-1.20.2.tgz", + "integrity": "sha512-NWiWaJrOK8TPATXzy+YKdMOR/N2onupiaorbseERtbovXTtdF8DwKLPqY9PVYdxdO1MwLuXKvKssyH1/5BzbiQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vscode/ripgrep": "^1.15.9", - "command-line-args": "^5.2.1", - "ts-morph": "^19.0.0" + "@vscode/ripgrep": "^1.15.10", + "command-line-args": "^6.0.1", + "ts-morph": "^25.0.1" }, "bin": { "vscode-telemetry-extractor": "out/extractor.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/@vscode/test-cli": { @@ -4500,12 +4490,13 @@ } }, "node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=12.17" } }, "node_modules/array-differ": { @@ -5720,10 +5711,11 @@ } }, "node_modules/code-block-writer": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", - "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", - "dev": true + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" }, "node_modules/code-point-at": { "version": "1.1.0", @@ -5816,18 +5808,27 @@ } }, "node_modules/command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.1.tgz", + "integrity": "sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==", "dev": true, + "license": "MIT", "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", + "array-back": "^6.2.2", + "find-replace": "^5.0.2", "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" + "typical": "^7.2.0" }, "engines": { - "node": ">=4.0.0" + "node": ">=12.20" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } } }, "node_modules/commander": { @@ -8026,15 +8027,21 @@ "dev": true }, "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz", + "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==", "dev": true, - "dependencies": { - "array-back": "^3.0.1" - }, + "license": "MIT", "engines": { - "node": ">=4.0.0" + "node": ">=14" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } } }, "node_modules/find-up": { @@ -12241,8 +12248,9 @@ "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY= sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" }, "node_modules/lodash.clone": { "version": "4.5.0", @@ -17726,13 +17734,14 @@ } }, "node_modules/ts-morph": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-19.0.0.tgz", - "integrity": "sha512-D6qcpiJdn46tUqV45vr5UGM2dnIEuTGNxVhg0sk5NX11orcouwj6i1bMqZIz2mZTZB1Hcgy7C3oEVhAT+f6mbQ==", + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-25.0.1.tgz", + "integrity": "sha512-QJEiTdnz1YjrB3JFhd626gX4rKHDLSjSVMvGGG4v7ONc3RBwa0Eei98G9AT9uNFDMtV54JyuXsFeC+OH0n6bXQ==", "dev": true, + "license": "MIT", "dependencies": { - "@ts-morph/common": "~0.20.0", - "code-block-writer": "^12.0.0" + "@ts-morph/common": "~0.26.0", + "code-block-writer": "^13.0.3" } }, "node_modules/tsec": { @@ -17949,12 +17958,13 @@ } }, "node_modules/typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12.17" } }, "node_modules/uc.micro": { diff --git a/package.json b/package.json index d7f05095ed7c5..175cd7368eea4 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "@vscode/component-explorer-cli": "^0.1.1-12", "@vscode/gulp-electron": "1.40.0", "@vscode/l10n-dev": "0.0.35", - "@vscode/telemetry-extractor": "^1.10.2", + "@vscode/telemetry-extractor": "^1.20.2", "@vscode/test-cli": "^0.0.6", "@vscode/test-electron": "^2.4.0", "@vscode/test-web": "^0.0.76", diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 40349546ec845..5b52e9acec7dd 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -825,7 +825,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' }; preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' }; compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' }; - errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code' }; + errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code or reason' }; }>('galleryService:fallbacktoquery', { extension: extensionInfo.id, preRelease: !!extensionInfo.preRelease, diff --git a/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts b/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts index 70191eea31ab4..9e6ca135e0840 100644 --- a/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts +++ b/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts @@ -293,7 +293,7 @@ export class AgentSessionsWelcomePage extends EditorPane { button.appendChild(document.createTextNode(entry.label)); button.onclick = () => { this.telemetryService.publicLog2( - 'gettingStarted.ActionExecuted', + 'agentSessionsWelcome.ActionExecuted', { welcomeKind: 'agentSessionsWelcomePage', action: 'executeCommand', actionId: entry.command } ); this.commandService.executeCommand(entry.command); @@ -652,7 +652,7 @@ export class AgentSessionsWelcomePage extends EditorPane { card.onclick = () => { const walkthrough = activeWalkthroughs[currentIndex]; this.telemetryService.publicLog2( - 'gettingStarted.ActionExecuted', + 'agentSessionsWelcome.ActionExecuted', { welcomeKind: 'agentSessionsWelcomePage', action: 'openWalkthrough', actionId: walkthrough.id } ); // Open walkthrough with returnToCommand so back button returns to agent sessions welcome diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 1a4648762e31f..a7911a941611f 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -525,7 +525,7 @@ export class WorkbenchThemeService extends Disposable implements IWorkbenchTheme publisherDisplayName: string; themeId: string; }; - this.telemetryService.publicLog2('activatePlugin', { + this.telemetryService.publicLog2('activateThemeExtension', { id: themeData.extensionId, name: themeData.extensionName, isBuiltin: themeData.extensionIsBuiltin, From 12bb8a5b9692bd72c7024ad88c1d0aa4fa72b48f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 1 Mar 2026 09:29:52 +0100 Subject: [PATCH 11/12] Engineering - delete macOS test pipeline (#298539) --- build/azure-pipelines/product-build-macos.yml | 96 ------------------- 1 file changed, 96 deletions(-) delete mode 100644 build/azure-pipelines/product-build-macos.yml diff --git a/build/azure-pipelines/product-build-macos.yml b/build/azure-pipelines/product-build-macos.yml deleted file mode 100644 index 1330f83567dcf..0000000000000 --- a/build/azure-pipelines/product-build-macos.yml +++ /dev/null @@ -1,96 +0,0 @@ -pr: none - -trigger: none - -parameters: - - name: VSCODE_QUALITY - displayName: Quality - type: string - default: insider - - name: NPM_REGISTRY - displayName: "Custom NPM Registry" - type: string - default: 'https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/' - - name: CARGO_REGISTRY - displayName: "Custom Cargo Registry" - type: string - default: 'sparse+https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/Cargo/index/' - -variables: - - name: NPM_REGISTRY - ${{ if in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }}: # disable terrapin when in VSCODE_CIBUILD - value: none - ${{ else }}: - value: ${{ parameters.NPM_REGISTRY }} - - name: CARGO_REGISTRY - value: ${{ parameters.CARGO_REGISTRY }} - - name: VSCODE_QUALITY - value: ${{ parameters.VSCODE_QUALITY }} - - name: VSCODE_CIBUILD - value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - - name: VSCODE_STEP_ON_IT - value: false - - name: skipComponentGovernanceDetection - value: true - - name: ComponentDetection.Timeout - value: 600 - - name: Codeql.SkipTaskAutoInjection - value: true - - name: ARTIFACT_PREFIX - value: '' - -name: "$(Date:yyyyMMdd).$(Rev:r) (${{ parameters.VSCODE_QUALITY }})" - -resources: - repositories: - - repository: 1esPipelines - type: git - name: 1ESPipelineTemplates/1ESPipelineTemplates - ref: refs/tags/release - -extends: - template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines - parameters: - sdl: - tsa: - enabled: true - configFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/tsaoptions.json - codeql: - runSourceLanguagesInSourceAnalysis: true - compiled: - enabled: false - justificationForDisabling: "CodeQL breaks ESRP CodeSign on macOS (ICM #520035761, githubcustomers/microsoft-codeql-support#198)" - credscan: - suppressionsFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/CredScanSuppressions.json - eslint: - enabled: true - enableExclusions: true - exclusionsFilePath: $(Build.SourcesDirectory)/.eslint-ignore - sourceAnalysisPool: 1es-windows-2022-x64 - createAdoIssuesForJustificationsForDisablement: false - containers: - ubuntu-2004-arm64: - image: onebranch.azurecr.io/linux/ubuntu-2004-arm64:latest - stages: - - stage: macOS - dependsOn: [] - pool: - name: AcesShared - os: macOS - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - variables: - BUILDSECMON_OPT_IN: true - jobs: - - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self - parameters: - VSCODE_CIBUILD: true - VSCODE_TEST_SUITE: Electron - - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self - parameters: - VSCODE_CIBUILD: true - VSCODE_TEST_SUITE: Browser - - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self - parameters: - VSCODE_CIBUILD: true - VSCODE_TEST_SUITE: Remote From 25edeb0b203036556af39c69a282915e6f78fd46 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Sun, 1 Mar 2026 02:13:31 -0800 Subject: [PATCH 12/12] Allow singleton editors to split into a new editor (#297609) * Add CanCreateForSplit capability for singleton editors Introduce a new EditorInputCapabilities.CanCreateForSplit capability and EditorInput.createForSplit() method that allow singleton editors to participate in split operations by providing a new editor input. Apply this capability to ChatEditorInput so that splitting a chat editor opens a fresh chat session in the new group. * Update src/vs/workbench/common/editor/editorInput.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/common/editor/editorInput.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * unwind * unwind * change * . * rename * . * . * . * . --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Benjamin Pasero Co-authored-by: Benjamin Pasero --- src/vs/workbench/common/editor.ts | 8 +++++++- .../workbench/common/editor/sideBySideEditorInput.ts | 5 +++++ .../browserView/electron-browser/browserEditorInput.ts | 2 +- .../chat/browser/widgetHosts/editor/chatEditorInput.ts | 7 ++++++- .../contrib/terminal/browser/terminalEditorInput.ts | 2 +- .../services/editor/common/editorGroupFinder.ts | 6 +++--- .../services/editor/test/browser/editorService.test.ts | 10 +++++----- src/vs/workbench/test/browser/contributions.test.ts | 4 ++-- src/vs/workbench/test/browser/workbenchTestServices.ts | 4 ++-- 9 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 2e535074002fa..e02204c16c429 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -849,7 +849,13 @@ export const enum EditorInputCapabilities { * Signals that the editor cannot be in a dirty state * and may still have unsaved changes */ - Scratchpad = 1 << 9 + Scratchpad = 1 << 9, + + /** + * Signals that the editor should be revealed when being + * opened if it is already opened in any editor group. + */ + ForceReveal = 1 << 10 } export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceMultiDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput; diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts index 3a4cb208b3048..27175bf8714ff 100644 --- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts +++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts @@ -43,6 +43,11 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi capabilities |= EditorInputCapabilities.Singleton; } + // ForceReveal: should be considered for both sides + if (this.secondary.hasCapability(EditorInputCapabilities.ForceReveal)) { + capabilities |= EditorInputCapabilities.ForceReveal; + } + // Indicate we show more than one editor capabilities |= EditorInputCapabilities.MultipleEditors; diff --git a/src/vs/workbench/contrib/browserView/electron-browser/browserEditorInput.ts b/src/vs/workbench/contrib/browserView/electron-browser/browserEditorInput.ts index 8fdd335c19441..9a7f53e85ed27 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/browserEditorInput.ts +++ b/src/vs/workbench/contrib/browserView/electron-browser/browserEditorInput.ts @@ -126,7 +126,7 @@ export class BrowserEditorInput extends EditorInput { } override get capabilities(): EditorInputCapabilities { - return EditorInputCapabilities.Singleton | EditorInputCapabilities.Readonly; + return EditorInputCapabilities.ForceReveal | EditorInputCapabilities.Readonly; } override get resource(): URI { diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditorInput.ts index 47214b2d68d4e..29fcbcf2d1051 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/editor/chatEditorInput.ts @@ -61,6 +61,7 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler @IChatService private readonly chatService: IChatService, @IDialogService private readonly dialogService: IDialogService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -107,7 +108,11 @@ export class ChatEditorInput extends EditorInput implements IEditorCloseHandler } override get capabilities(): EditorInputCapabilities { - return super.capabilities | EditorInputCapabilities.Singleton | EditorInputCapabilities.CanDropIntoEditor; + return super.capabilities | EditorInputCapabilities.ForceReveal | EditorInputCapabilities.CanDropIntoEditor; + } + + override copy(): EditorInput { + return this.instantiationService.createInstance(ChatEditorInput, ChatEditorInput.getNewEditorUri(), {}); } override matches(otherInput: EditorInput | IUntypedEditorInput): boolean { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts index d0c3a794561d2..f7ce1dab62b91 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -60,7 +60,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand } override get capabilities(): EditorInputCapabilities { - return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton | EditorInputCapabilities.CanDropIntoEditor | EditorInputCapabilities.ForceDescription; + return EditorInputCapabilities.Readonly | EditorInputCapabilities.ForceReveal | EditorInputCapabilities.CanDropIntoEditor | EditorInputCapabilities.ForceDescription; } setTerminalInstance(instance: ITerminalInstance): void { diff --git a/src/vs/workbench/services/editor/common/editorGroupFinder.ts b/src/vs/workbench/services/editor/common/editorGroupFinder.ts index 96f6c8d112fc3..e7f6ae7e6bce7 100644 --- a/src/vs/workbench/services/editor/common/editorGroupFinder.ts +++ b/src/vs/workbench/services/editor/common/editorGroupFinder.ts @@ -146,10 +146,10 @@ function doFindGroup(input: EditorInputWithOptions | IUntypedEditorInput, prefer // Respect option to reveal an editor if it is open (not necessarily visible) // Still prefer to reveal an editor in a group where the editor is active though. - // We also try to reveal an editor if it has the `Singleton` capability which - // indicates that the same editor cannot be opened across groups. + // We also try to reveal an editor if it has the `ForceReveal` or `Singleton` + // capability which indicates that editor prefers to be revealed. if (!group) { - if (options?.revealIfOpened || configurationService.getValue('workbench.editor.revealIfOpen') || (isEditorInput(editor) && editor.hasCapability(EditorInputCapabilities.Singleton))) { + if (options?.revealIfOpened || configurationService.getValue('workbench.editor.revealIfOpen') || (isEditorInput(editor) && (editor.hasCapability(EditorInputCapabilities.ForceReveal) || editor.hasCapability(EditorInputCapabilities.Singleton)))) { let groupWithInputActive: IEditorGroup | undefined = undefined; let groupWithInputOpened: IEditorGroup | undefined = undefined; diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 1300308ab628c..f11930103f879 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -8,7 +8,7 @@ import { EditorActivation, IResourceEditorInput } from '../../../../../platform/ import { URI } from '../../../../../base/common/uri.js'; import { Event } from '../../../../../base/common/event.js'; import { DEFAULT_EDITOR_ASSOCIATION, EditorCloseContext, EditorsOrder, IEditorCloseEvent, EditorInputWithOptions, IEditorPane, IResourceDiffEditorInput, isEditorInputWithOptions, IUntitledTextResourceEditorInput, IUntypedEditorInput, SideBySideEditor, isEditorInput, EditorInputCapabilities } from '../../../../common/editor.js'; -import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput, ITestInstantiationService, registerTestResourceEditor, registerTestSideBySideEditor, createEditorPart, registerTestFileEditor, TestTextFileEditor, TestSingletonFileEditorInput, workbenchTeardown } from '../../../../test/browser/workbenchTestServices.js'; +import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput, ITestInstantiationService, registerTestResourceEditor, registerTestSideBySideEditor, createEditorPart, registerTestFileEditor, TestTextFileEditor, TestForceRevealFileEditorInput, workbenchTeardown } from '../../../../test/browser/workbenchTestServices.js'; import { EditorService } from '../../browser/editorService.js'; import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } from '../../common/editorGroupsService.js'; import { EditorPart } from '../../../../browser/parts/editor/editorPart.js'; @@ -40,7 +40,7 @@ suite('EditorService', () => { let testLocalInstantiationService: ITestInstantiationService | undefined = undefined; setup(() => { - disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput), new SyncDescriptor(TestSingletonFileEditorInput)], TEST_EDITOR_INPUT_ID)); + disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput), new SyncDescriptor(TestForceRevealFileEditorInput)], TEST_EDITOR_INPUT_ID)); disposables.add(registerTestResourceEditor()); disposables.add(registerTestSideBySideEditor()); }); @@ -264,11 +264,11 @@ suite('EditorService', () => { assert.strictEqual(editor2?.input, input); }); - test('openEditor() - singleton typed editors reveal instead of split', async () => { + test('openEditor() - force-reveal typed editors reveal instead of split', async () => { const [part, service] = await createEditorService(); - const input1 = disposables.add(new TestSingletonFileEditorInput(URI.parse('my://resource-basics1'), TEST_EDITOR_INPUT_ID)); - const input2 = disposables.add(new TestSingletonFileEditorInput(URI.parse('my://resource-basics2'), TEST_EDITOR_INPUT_ID)); + const input1 = disposables.add(new TestForceRevealFileEditorInput(URI.parse('my://resource-basics1'), TEST_EDITOR_INPUT_ID)); + const input2 = disposables.add(new TestForceRevealFileEditorInput(URI.parse('my://resource-basics2'), TEST_EDITOR_INPUT_ID)); const input1Group = (await service.openEditor(input1, { pinned: true }))?.group; const input2Group = (await service.openEditor(input2, { pinned: true }, SIDE_GROUP))?.group; diff --git a/src/vs/workbench/test/browser/contributions.test.ts b/src/vs/workbench/test/browser/contributions.test.ts index 6eb3087bbbac4..cbd2984e56a2b 100644 --- a/src/vs/workbench/test/browser/contributions.test.ts +++ b/src/vs/workbench/test/browser/contributions.test.ts @@ -17,7 +17,7 @@ import { EditorService } from '../../services/editor/browser/editorService.js'; import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; import { IEditorService, SIDE_GROUP } from '../../services/editor/common/editorService.js'; import { LifecyclePhase } from '../../services/lifecycle/common/lifecycle.js'; -import { ITestInstantiationService, TestFileEditorInput, TestServiceAccessor, TestSingletonFileEditorInput, createEditorPart, registerTestEditor, workbenchInstantiationService } from './workbenchTestServices.js'; +import { ITestInstantiationService, TestFileEditorInput, TestServiceAccessor, TestForceRevealFileEditorInput, createEditorPart, registerTestEditor, workbenchInstantiationService } from './workbenchTestServices.js'; suite('Contributions', () => { const disposables = new DisposableStore(); @@ -48,7 +48,7 @@ suite('Contributions', () => { bCreated = false; bCreatedPromise = new DeferredPromise(); - disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput), new SyncDescriptor(TestSingletonFileEditorInput)], TEST_EDITOR_INPUT_ID)); + disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput), new SyncDescriptor(TestForceRevealFileEditorInput)], TEST_EDITOR_INPUT_ID)); }); teardown(async () => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 529c27d720d98..3762453a62c7f 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1639,9 +1639,9 @@ export class TestFileEditorInput extends EditorInput implements IFileEditorInput } } -export class TestSingletonFileEditorInput extends TestFileEditorInput { +export class TestForceRevealFileEditorInput extends TestFileEditorInput { - override get capabilities(): EditorInputCapabilities { return EditorInputCapabilities.Singleton; } + override get capabilities(): EditorInputCapabilities { return EditorInputCapabilities.ForceReveal; } } export class TestEditorPart extends MainEditorPart implements IEditorGroupsService {