Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(scripts-*): speed up all isConverged, getAllPackageInfo calls/usage by ~95% #31008

Merged
Merged
20 changes: 7 additions & 13 deletions scripts/beachball/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AllPackageInfo, getAllPackageInfo, isConvergedPackage } from '@fluentui/scripts-monorepo';
import { getAllPackageInfo, isConvergedPackage } from '@fluentui/scripts-monorepo';

/**
* Reads package info from the monorepo and generates the scopes for beachball bump and release.
Expand All @@ -17,8 +17,7 @@ export function getConfig({ version }: { version: 'vNext' }): {
};
};
export function getConfig({ version }: { version: 'v8' | 'vNext' }) {
const allPackageInfo = getAllPackageInfo();
const vNextPackagePaths = getVNextPackagePaths(allPackageInfo);
const vNextPackagePaths = getVNextPackagePaths();

if (version === 'vNext') {
return {
Expand All @@ -32,22 +31,17 @@ export function getConfig({ version }: { version: 'v8' | 'vNext' }) {
}

if (version === 'v8') {
const ignoreVNextScope = vNextPackagePaths.map(path => `!${path}`);
const ignoreVNextScope = vNextPackagePaths.map(pkgPath => `!${pkgPath}`);

return { scope: [...ignoreVNextScope] };
}

throw new Error('Unsupported version scopes acquisition');
}

function getVNextPackagePaths(allPackageInfo: AllPackageInfo) {
return Object.values(allPackageInfo)
.map(packageInfo => {
if (isConvergedPackage({ packagePathOrJson: packageInfo.packageJson })) {
return packageInfo.packagePath;
}
function getVNextPackagePaths() {
const allProjects = getAllPackageInfo(isConvergedPackage);
const values = Object.values(allProjects);

return false;
})
.filter(Boolean) as string[];
return values.map(project => project.packagePath);
}
4 changes: 2 additions & 2 deletions scripts/executors/runPublished.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ function getLageArgs(options) {
// (which must be built and uploaded with each release). This is similar to "--scope \"!packages/fluentui/*\""
// in the root package.json's publishing-related scripts and will need to be updated if --scope changes.
const beachballPackageScopes = Object.values(workspacePackagesMetadata)
.filter(({ packageJson, packagePath }) => {
.filter(({ packageJson, projectConfig, packagePath }) => {
const isNorthstar = /[\\/]fluentui[\\/]/.test(packagePath);
const isWebComponents = packageJson.name === '@fluentui/web-components';

if (isNorthstar || isWebComponents) {
return false;
}

const isConverged = isConvergedPackage({ packagePathOrJson: packageJson });
const isConverged = isConvergedPackage({ packageJson, project: projectConfig });
if (releaseScope === 'v9' && isConverged) {
return packageJson.private !== true;
}
Expand Down
8 changes: 8 additions & 0 deletions scripts/executors/runPublished.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,26 @@ describe(`#runPublished`, () => {
'@fluentui/web-components': {
packagePath: 'packages/web-components',
packageJson: { name: '@fluentui/web-components', version: '1.0.0', main: 'lib/index.js', private: false },
projectConfig: { name: '@fluentui/web-components', root: 'packages/web-components', tags: [] },
},
'@fluentui/react': {
packagePath: 'packages/react',
packageJson: { name: '@fluentui/react', version: '8.0.0', main: 'lib/index.js', private: false },
projectConfig: { name: '@fluentui/react', root: 'packages/react', tags: [] },
},
'@fluentui/react-northstar': {
packagePath: 'packages/fluentui/react-northstar',
packageJson: { name: '@fluentui/react-northstar', version: '0.1.0', main: 'lib/index.js', private: false },
projectConfig: { name: '@fluentui/react-northstar', root: 'packages/fluentui/react-northstar', tags: [] },
},
'@fluentui/react-components': {
packagePath: 'packages/react-components/react-components',
packageJson: { name: '@fluentui/react-components', version: '9.0.0', main: 'lib/index.js', private: false },
projectConfig: {
name: '@fluentui/react-components',
root: 'packages/react-components/react-components',
tags: [],
},
},
};

Expand Down
10 changes: 6 additions & 4 deletions scripts/executors/tag-react-components.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { execSync } from 'child_process';
import * as semver from 'semver';
Hotell marked this conversation as resolved.
Show resolved Hide resolved
import yargs from 'yargs';

import { AllPackageInfo, getAllPackageInfo, isConvergedPackage } from '@fluentui/scripts-monorepo';
import * as semver from 'semver';
import yargs from 'yargs';

function tagPackages(npmToken: string) {
const packagesToTag = getPackagesToTag();
Expand Down Expand Up @@ -42,10 +42,10 @@ function tagPackage(name: string, version: string, npmToken: string): boolean {
}

function getPackagesToTag() {
const packageInfos: AllPackageInfo = getAllPackageInfo();
const packageInfos: AllPackageInfo = getAllPackageInfo(isConvergedPackage);
return Object.values(packageInfos)
.map(packageInfo => {
if (!packageInfo.packageJson.private && isConvergedPackage({ packagePathOrJson: packageInfo.packageJson })) {
if (!packageInfo.packageJson.private) {
return {
name: packageInfo.packageJson.name,
version: packageInfo.packageJson.version,
Expand All @@ -67,4 +67,6 @@ function main(argv: yargs.Arguments) {

if (require.main === module && process.env.RELEASE_VNEXT) {
main(yargs.argv);
} else {
console.log('"RELEASE_VNEXT" not set - skipping');
}
10 changes: 7 additions & 3 deletions scripts/generators/create-package/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { spawnSync } from 'child_process';
import * as path from 'path';

import { PackageJson, findGitRoot, flushTreeChanges, getProjectMetadata, tree } from '@fluentui/scripts-monorepo';
import { addProjectConfiguration } from '@nx/devkit';
import { PackageJson, findGitRoot, flushTreeChanges, tree } from '@fluentui/scripts-monorepo';
import { addProjectConfiguration, getProjects } from '@nx/devkit';
import chalk from 'chalk';
import * as fs from 'fs-extra';
import _ from 'lodash';
Expand Down Expand Up @@ -188,10 +188,14 @@ function replaceVersionsFromReference(

const depTypes = ['dependencies', 'devDependencies', 'peerDependencies'] as const;

const projects = getProjects(tree);
// Read the package.json files of the given reference packages and combine into one object.
// This way if a dep is defined in any of them, it can easily be copied to newPackageJson.
const packageJsons = referencePackages.map(pkgName => {
const metadata = getProjectMetadata(pkgName);
const metadata = projects.get(pkgName);
if (!metadata) {
throw new Error(`Couldn't find metadata for package ${pkgName}`);
}

return fs.readJSONSync(path.join(metadata.root, 'package.json'));
});
Expand Down
16 changes: 13 additions & 3 deletions scripts/monorepo/src/getAllPackageInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ let cwdForPackageInfo;

/**
* @returns {typeof packageInfo}
* @param {(metadata:{project:import('@nx/devkit').ProjectConfiguration;packageJson:import('nx/src/utils/package-json').PackageJson})=>boolean} [predicate]
*/
function getAllPackageInfo() {
if (packageInfo && cwdForPackageInfo === process.cwd()) {
function getAllPackageInfo(predicate) {
if (!predicate && packageInfo && cwdForPackageInfo === process.cwd()) {
return packageInfo;
}

Expand All @@ -28,9 +29,18 @@ function getAllPackageInfo() {
cwdForPackageInfo = process.cwd();

for (const [projectName, projectConfig] of projects) {
const packageJson = JSON.parse(
fs.readFileSync(path.join(workspaceRoot, projectConfig.root, 'package.json'), 'utf-8'),
);

if (predicate && !predicate({ project: projectConfig, packageJson })) {
continue;
}

packageInfo[projectName] = {
packagePath: projectConfig.root,
packageJson: JSON.parse(fs.readFileSync(path.join(workspaceRoot, projectConfig.root, 'package.json'), 'utf-8')),
packageJson,
projectConfig,
};
}

Expand Down
17 changes: 17 additions & 0 deletions scripts/monorepo/src/getAllPackageInfo.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as path from 'path';

import getAllPackageInfo from './getAllPackageInfo';

describe(`#getAllPackageinfo`, () => {
Expand All @@ -9,13 +10,29 @@ describe(`#getAllPackageinfo`, () => {

expect(allPackages['@fluentui/noop']).toBe(undefined);
expect(packageName).toEqual(expect.stringMatching(/^@fluentui\/[a-z-]+/));

expect(packageMetadata).toEqual({
packagePath: expect.any(String),
packageJson: expect.objectContaining({
name: expect.any(String),
version: expect.any(String),
}),
projectConfig: expect.objectContaining({
$schema: expect.any(String),
implicitDependencies: expect.any(Array),
name: expect.any(String),
projectType: expect.any(String),
root: expect.any(String),
}),
});
expect(path.isAbsolute(packageMetadata.packagePath)).toBe(false);
});

it(`should accept predicate filter function`, () => {
const allPackages = getAllPackageInfo(metadata => {
return metadata.packageJson.name === '@fluentui/non-existent-package';
});

expect(allPackages).toEqual({});
});
});
4 changes: 2 additions & 2 deletions scripts/monorepo/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ export { getDependencies } from './getDependencies';
export { default as findGitRoot } from './findGitRoot';
export { default as findRepoDeps } from './findRepoDeps';
export { default as getAllPackageInfo } from './getAllPackageInfo';
export { isConvergedPackage, shipsAMD } from './isConvergedPackage';
export { isConvergedPackage } from './isConvergedPackage';
export { getAffectedPackages } from './getAffectedPackages';
export { getDefaultEnvironmentVars } from './getDefaultEnvironmentVars';
export { getProjectMetadata, workspaceRoot, getUncommittedFiles, getUntrackedFiles } from './utils';
export { workspaceRoot, getUncommittedFiles, getUntrackedFiles } from './utils';
export * as eslintConstants from './eslint-constants';
export { getNthCommit } from './getNthCommit';
export { tree, flushTreeChanges } from './tree';
Expand Down
78 changes: 4 additions & 74 deletions scripts/monorepo/src/isConvergedPackage.js
Original file line number Diff line number Diff line change
@@ -1,85 +1,16 @@
const { readConfig } = require('@fluentui/scripts-utils');
const semver = require('semver');

const { getProjectMetadata } = require('./utils');

/**
* @typedef {string | import('./index').PackageJson} PathOrPackageJson
*/

/**
* Determines whether a package is converged, based on its version.
*
* @param {Object} [options]
* @param {PathOrPackageJson} [options.packagePathOrJson] - optional different package path to run in OR previously-read package.json
* (defaults to reading package.json from `process.cwd()`)
* @param {'library' | 'application' | 'all'} [options.projectType] - filter for what project types you wanna apply the condition
*
* @returns {boolean} true if it's a converged package (version >= 9)
* @param {{project:import('@nx/devkit').ProjectConfiguration;packageJson:import('nx/src/utils/package-json').PackageJson}} metadata
*/
function isConvergedPackage(options = {}) {
const { packagePathOrJson, projectType = 'all' } = options;
const packageJson =
!packagePathOrJson || typeof packagePathOrJson === 'string'
? readConfig('package.json', /** @type {string|undefined} */ (packagePathOrJson))
: packagePathOrJson;

if (!packageJson) {
throw new Error(`package.json doesn't exist`);
}

const metadata = getProjectMetadata(packageJson.name);

if (projectType !== 'all' && metadata.projectType !== projectType) {
return false;
}

const hasVNextTag = !!metadata.tags?.includes('vNext');
function isConvergedPackage(metadata) {
const { packageJson, project } = metadata;
Hotell marked this conversation as resolved.
Show resolved Hide resolved
const hasVNextTag = Boolean(project.tags?.includes('vNext'));

return semver.major(packageJson.version) >= 9 || isNightlyVersion(packageJson.version) || hasVNextTag;
}

/**
* @param {Object} [options]
* @param {PathOrPackageJson} [options.packagePathOrJson] - optional different package path to run in OR previously-read package.json
* (defaults to reading package.json from `process.cwd()`)
* @returns
*/
function shipsAMD(options = {}) {
const { packagePathOrJson } = options;
const packageJson =
!packagePathOrJson || typeof packagePathOrJson === 'string'
? readConfig('package.json', /** @type {string|undefined} */ (packagePathOrJson))
: packagePathOrJson;

if (!packageJson) {
throw new Error(`package.json doesn't exist`);
}

const metadata = getProjectMetadata(packageJson.name);

if (metadata.projectType !== 'library') {
return false;
}

const tags = new Set(metadata.tags ?? []);
const isV8 = tags.has('v8');
const isV9 = tags.has('vNext');
const needsAMD = tags.has('ships-amd');
const isMixedProject = isV9 && isV8;

if (needsAMD) {
return true;
}
if (isMixedProject) {
return true;
}
if (isV9) {
return false;
}
return true;
}

/**
* Determines if a version is the 0.0.0 nightly version used by converged packages
* @param {string} version
Expand All @@ -89,4 +20,3 @@ function isNightlyVersion(version) {
}

exports.isConvergedPackage = isConvergedPackage;
exports.shipsAMD = shipsAMD;
39 changes: 39 additions & 0 deletions scripts/monorepo/src/isConvergedPackage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { isConvergedPackage } from './isConvergedPackage';

describe(`#isConverged`, () => {
it(`should return true for vNext package`, () => {
const actual = isConvergedPackage({
packageJson: {
name: '@proj/one',
version: '9.0.0',
},
project: { root: 'packages/foo/bar/one', projectType: 'library', tags: ['vNext'] },
});

expect(actual).toBe(true);
});

it(`should return true for vNext nightly package`, () => {
const actual = isConvergedPackage({
packageJson: {
name: '@proj/one',
version: '0.0.0-nightly.20210101',
},
project: { root: 'packages/foo/bar/one', projectType: 'library', tags: ['vNext'] },
});

expect(actual).toBe(true);
});

it(`should return false for non vNext Package`, () => {
const actual = isConvergedPackage({
packageJson: {
name: '@proj/one',
version: '8.1.2',
},
project: { root: 'packages/foo/bar/one', projectType: 'library', tags: ['v8'] },
});

expect(actual).toBe(false);
});
});
1 change: 1 addition & 0 deletions scripts/monorepo/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface PackageInfo {
packagePath: string;
/** package.json contents */
packageJson: PackageJson;
projectConfig: import('@nx/devkit').ProjectConfiguration;
}

/**
Expand Down
14 changes: 1 addition & 13 deletions scripts/monorepo/src/utils.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
const { execSync } = require('child_process');

const { workspaceRoot, readProjectConfiguration } = require('@nx/devkit');

const { tree } = require('./tree');
const { workspaceRoot } = require('@nx/devkit');

const TEN_MEGABYTES = 1024 * 10000;

/**
* Gets nx project metadata
* @param {string} projectName - package name
* @returns {import('@nx/devkit').ProjectConfiguration}
*/
function getProjectMetadata(projectName) {
return readProjectConfiguration(tree, projectName);
}

/**
*
* @param {string} command
Expand Down Expand Up @@ -46,5 +35,4 @@ function getUntrackedFiles() {

exports.getUncommittedFiles = getUncommittedFiles;
exports.getUntrackedFiles = getUntrackedFiles;
exports.getProjectMetadata = getProjectMetadata;
exports.workspaceRoot = workspaceRoot;