Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/scripts/monosize-report-artifact-meta.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @ts-check

/**
* Sets the monosize bundle-size report artifact metadata (name + path) as GitHub
* Actions step outputs (`name`, `path`) via `core.setOutput`.
*
* `monosize.config.mjs` is the single source of truth: `monosize-storage-git` reads
* the base report from the workflow artifact whose name matches `reportArtifactName`,
* but it does not control the `actions/upload-artifact` step. Resolving the values
* from the config here guarantees the uploaded artifact name/path can never drift
* from what the storage adapter expects when reading the base report.
*
* Invoked via `actions/github-script` using a dynamic import (the inline `script`
* runs as an async function body, so ESM modules are loaded with `await import`):
* const moduleUrl = pathToFileURL(path.resolve('.github/scripts/monosize-report-artifact-meta.mjs')).href;
* const { default: run } = await import(moduleUrl);
* run({ core });
*/

import { reportArtifactName, reportOutputPath } from '../../monosize.config.mjs';

// Strict allowlists — a report artifact name/path should only ever contain these
// characters. Fail closed on anything else so a malformed config surfaces loudly
// instead of producing a broken/empty artifact name that silently breaks the read side.
const NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
const PATH_PATTERN = /^[A-Za-z0-9._/-]+$/;

/**
* @param {unknown} value
* @param {RegExp} pattern
* @param {string} label
* @returns {string}
*/
function assertSafe(value, pattern, label) {
if (typeof value !== 'string' || value.length === 0 || !pattern.test(value)) {
throw new Error(
`monosize report metadata: "${label}" is missing or contains unsupported characters ` +
`(allowed: ${pattern.source}).`,
);
}
return value;
}

/**
* @param {Pick<import('../../scripts/triage-bot/src/types.ts').GithubScriptsParams, 'core'>} options
*/
export default function main(options) {
const { core } = options;

core.setOutput('name', assertSafe(reportArtifactName, NAME_PATTERN, 'reportArtifactName'));
core.setOutput('path', assertSafe(reportOutputPath, PATH_PATTERN, 'reportOutputPath'));
}
36 changes: 22 additions & 14 deletions .github/workflows/bundle-size-base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
if: ${{ github.repository_owner == 'microsoft' }}
runs-on: macos-14-xlarge
permissions:
id-token: 'write'
contents: 'read'

steps:
- uses: actions/checkout@v6
Expand All @@ -36,22 +36,30 @@ jobs:
- name: Build all packages & create reports (non-PR)
run: yarn nx run-many -t bundle-size --nxBail

- name: Login via Azure CLI
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ secrets.AZURE_BUNDLESIZE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Extract branch name
run: |
echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV

- name: Upload report
- name: Prepare report
run: |
yarn monosize upload-report --branch=$BRANCH_NAME --commit-sha ${{ github.sha }}
env:
BUNDLESIZE_ACCOUNT_NAME: ${{ secrets.BUNDLESIZE_ACCOUNT_NAME }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_SERVICE_CONNECTION_ID: ${{ secrets.AZURE_BUNDLESIZE_SERVICE_CONNECTION_ID }}

# Resolve the artifact name/path from monosize.config.mjs so they cannot drift
# from the value monosize-storage-git uses to read the base report.
- name: Resolve report artifact metadata
id: report
uses: actions/github-script@v8
with:
script: |
const path = require('node:path');
const { pathToFileURL } = require('node:url');
const moduleUrl = pathToFileURL(path.resolve('.github/scripts/monosize-report-artifact-meta.mjs')).href;
const { default: run } = await import(moduleUrl);
run({ core });

- name: Upload report artifact
uses: actions/upload-artifact@v6
with:
name: ${{ steps.report.outputs.name }}
if-no-files-found: error
path: ${{ steps.report.outputs.path }}
8 changes: 5 additions & 3 deletions .github/workflows/bundle-size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ jobs:
- name: Build packages & create reports
run: yarn nx affected -t bundle-size --nxBail

# NOTE: monosize-storage-azure only stores base reports for the default branch (master),
# so compare-reports will fail for PRs targeting other branches.
# NOTE: base bundle size reports are only produced by the "Bundle size Base" workflow on the
# default branch (master), so compare-reports will fail for PRs targeting other branches.
- name: Skip bundle size comparison notice
if: ${{ github.event.pull_request.base.ref != 'master' }}
run: echo "::warning::Bundle size comparison skipped — monosize-storage-azure only stores base reports for the default branch (master). PR targets '${{ github.event.pull_request.base.ref }}'."
run: echo "::warning::Bundle size comparison skipped — base reports are only produced for the default branch (master). PR targets '${{ github.event.pull_request.base.ref }}'."

- name: Compare bundle size with base
if: ${{ github.event.pull_request.base.ref == 'master' }}
run: npx monosize compare-reports --branch=${{ github.event.pull_request.base.ref }} --output=markdown --quiet > ./monosize-report.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Save PR number
if: ${{ github.event.pull_request.base.ref == 'master' }}
Expand Down
130 changes: 130 additions & 0 deletions azure-pipelines.release-headless-experimental.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
pr: none
trigger: none

parameters:
- name: dryRun
displayName: Dry Run Mode
type: boolean
default: true

# Customize build number to include package prefix
# Example: headless_experimental_20201022.1
name: 'headless_experimental_$(Date:yyyyMMdd)$(Rev:.r)'

variables:
- group: 'Github and NPM secrets'
- template: .devops/templates/variables.yml
parameters:
skipComponentGovernanceDetection: false
- name: tags
value: production,externalfacing

resources:
repositories:
- repository: 1esPipelines
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
parameters:
pool:
name: Azure-Pipelines-1ESPT-ExDShared
image: windows-latest
os: windows # We need windows because compliance task only run on windows.
stages:
- stage: main
jobs:
- job: Release
pool:
name: '1ES-Host-Ubuntu'
image: '1ES-PT-Ubuntu-20.04'
os: linux
workspace:
clean: all
templateContext:
outputs:
- output: pipelineArtifact
targetPath: $(System.DefaultWorkingDirectory)
artifactName: output
steps:
- template: .devops/templates/tools.yml@self
parameters:
dryRun: ${{ parameters.dryRun }}

- script: |
git config user.name "Fluent UI Build"
git config user.email "fluentui-internal@service.microsoft.com"
displayName: Configure git user (used by beachball)

- task: Bash@3
name: validation
inputs:
targetType: 'inline'
script: |
BRANCH="$(Build.SourceBranch)"
if [[ ! $BRANCH =~ refs/heads/experimental/ ]]; then
echo "##vso[task.logissue type=error]Branch '$BRANCH' must start with 'refs/heads/experimental/'"
exit 1
fi
BRANCH_PATH=${BRANCH#refs/heads/}
FEATURE_NAME=${BRANCH#refs/heads/experimental/}
echo "##vso[task.setvariable variable=branchPath;isOutput=true]$BRANCH_PATH"
echo "##vso[task.setvariable variable=featureName;isOutput=true]$FEATURE_NAME"
echo "Branch path: $BRANCH_PATH"
echo "Feature name: $FEATURE_NAME"
displayName: Validate branch and extract feature name

- script: |
yarn install --frozen-lockfile
displayName: Install dependencies

# Deletes all existing changefiles so that only the experimental bump happens
- script: |
rm -f change/*
displayName: 'Delete existing changefiles'

# Bumps headless packages to a x.x.x-experimental.<feature>.<date>-<hash> version and checks in change files.
# x.x.x is derived from @fluentui/react-headless-components-preview so it stays on its own 0.x line.
- script: |
FEATURE_NAME=$(validation.featureName)
DATE=$(date +"%Y%m%d")
HASH=$(git rev-parse --short HEAD)
BASE_VERSION=$(node -p "require('./packages/react-components/react-headless-components-preview/library/package.json').version")
NEW_VERSION="${BASE_VERSION}-experimental.${FEATURE_NAME}.${DATE}-${HASH}"

echo "Target version: ${NEW_VERSION}"

yarn nx g @fluentui/workspace-plugin:version-bump --name react-headless-components-preview --explicitVersion "${NEW_VERSION}"
git add .
git commit -m "bump experimental headless versions to ${NEW_VERSION}"
yarn change --type prerelease --message "Release ${NEW_VERSION}" --dependent-change-type "prerelease"
displayName: 'Bump and commit experimental headless versions'

- script: |
echo "Following packages will be published (if they contain changes):"
yarn nx show projects -p 'tag:react-headless,!tag:npm:private' --exclude 'apps/**'
displayName: Show packages

- script: |
FLUENT_PROD_BUILD=true yarn nx run-many -t build -p 'tag:react-headless,!tag:npm:private,!tag:type:stories' --exclude 'apps/**' --nxBail
displayName: build

- script: |
FLUENT_PROD_BUILD=true yarn nx run-many -t lint -p 'tag:react-headless,!tag:npm:private,!tag:type:stories' --exclude 'apps/**' --nxBail
displayName: lint

- script: |
FLUENT_PROD_BUILD=true yarn nx run-many -t test -p 'tag:react-headless,!tag:npm:private,!tag:type:stories' --exclude 'apps/**' --nxBail
displayName: test

- script: |
yarn beachball publish -b origin/$(validation.branchPath) --access public -y -n $(npmToken) --no-push --tag experimental --config scripts/beachball/src/release-headless.config.js
git reset --hard origin/$(validation.branchPath)
displayName: Publish changes and bump versions
condition: and(succeeded(), not(${{ parameters.dryRun }}))

- template: .devops/templates/cleanup.yml@self
parameters:
checkForModifiedFiles: false
6 changes: 3 additions & 3 deletions azure-pipelines.release-vnext-experimental.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ extends:
displayName: 'Bump and commit experimental versions'

- script: |
FLUENT_PROD_BUILD=true yarn nx run-many -t build -p "tag:vNext" --exclude "tag:npm:private,tag:tools,tag:charting" --nxBail
FLUENT_PROD_BUILD=true yarn nx run-many -t build -p "tag:vNext" --exclude "tag:npm:private,tag:tools,tag:charting,tag:react-headless" --nxBail
displayName: build

- script: |
FLUENT_PROD_BUILD=true yarn nx run-many -t lint -p "tag:vNext" --exclude "tag:npm:private,tag:tools,tag:charting" --nxBail
FLUENT_PROD_BUILD=true yarn nx run-many -t lint -p "tag:vNext" --exclude "tag:npm:private,tag:tools,tag:charting,tag:react-headless" --nxBail
displayName: lint

- script: |
FLUENT_PROD_BUILD=true yarn nx run-many -t test -p "tag:vNext" --exclude "tag:npm:private,tag:tools,tag:charting" --nxBail
FLUENT_PROD_BUILD=true yarn nx run-many -t test -p "tag:vNext" --exclude "tag:npm:private,tag:tools,tag:charting,tag:react-headless" --nxBail
displayName: test

- script: |
Expand Down
23 changes: 18 additions & 5 deletions monosize.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@
import fs from 'node:fs';
import path from 'node:path';
import webpackBundler from 'monosize-bundler-webpack';
import createAzureStorage from 'monosize-storage-azure';
import gitStorage from 'monosize-storage-git';

/**
* Single source of truth for the bundle-size report artifact name and path.
*
* `monosize-storage-git` reads the base report from a GitHub Actions artifact by
* matching this exact name, but it does NOT control the `actions/upload-artifact`
* step that produces it — so these values must be consumed by the workflow instead
* of being duplicated there. See `.github/scripts/monosize-report-artifact-meta.mjs`.
*/
export const reportArtifactName = 'monosize-bundle-size-report';
export const reportOutputPath = 'dist/bundle-size-report.json';

/** @type {import('monosize').MonoSizeConfig} */
const config = {
repository: 'https://github.com/microsoft/fluentui',
storage: createAzureStorage({
authType: 'DefaultAzureCredential',
endpoint: 'https://fluent-bundlesize.azurewebsites.net/api/fluentuilatest',
tableName: 'fluentuilatest',
storage: gitStorage({
owner: 'microsoft',
repo: 'fluentui',
workflowFileName: 'bundle-size-base.yml',
outputPath: reportOutputPath,
artifactName: reportArtifactName,
}),
bundler: webpackBundler(config => {
config.externals = config.externals ?? {};
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,9 @@
"markdown-table": "2.0.0",
"memfs": "3.5.3",
"micromatch": "4.0.8",
"monosize": "0.6.3",
"monosize-bundler-webpack": "0.1.6",
"monosize-storage-azure": "0.0.16",
"monosize": "0.9.0",
"monosize-bundler-webpack": "0.4.0",
"monosize-storage-git": "0.3.4",
"nano-staged": "0.9.0",
"node-plop": "0.25.0",
"nx": "21.6.10",
Expand Down
Loading
Loading