Skip to content

Commit

Permalink
feat(build-infrastructure): Add default implementations for core inte…
Browse files Browse the repository at this point in the history
…rfaces (#22865)

This change adds default implementations of the `IFluidRepo`,
`IWorkspace`, `IReleaseGroup`, and `IPackage` interfaces. These
implementations are sufficient for most uses in build-tools and
build-cli. They may be further extended for specific scenarios in future
changes.

I disabled the TypeDoc generation in this PR to make the code changes
easier to review. Once this series of PRs lands, we can enable TypeDoc
or api-markdown-documenter and regen the docs.

---------

Co-authored-by: Alex Villarreal <[email protected]>
  • Loading branch information
tylerbutler and alexvy86 authored Oct 29, 2024
1 parent 59441f3 commit 199b9d0
Show file tree
Hide file tree
Showing 24 changed files with 1,891 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { SimpleGit } from 'simple-git';
// @public
export type AdditionalPackageProps = Record<string, string> | undefined;

// @public
export function createPackageManager(name: PackageManagerName): IPackageManager;

// @public
export interface FluidPackageJsonFields {
pnpm?: {
Expand All @@ -22,6 +25,30 @@ export interface FluidPackageJsonFields {
// @public
export const FLUIDREPO_CONFIG_VERSION = 1;

// @public
export class FluidRepoBase<P extends IPackage> implements IFluidRepo<P> {
constructor(searchPath: string,
upstreamRemotePartialUrl?: string | undefined);
protected readonly configFilePath: string;
readonly configuration: IFluidRepoLayout;
getGitRepository(): Promise<Readonly<SimpleGit>>;
getPackageReleaseGroup(pkg: Readonly<P>): Readonly<IReleaseGroup>;
get packages(): Map<PackageName, P>;
relativeToRepo(p: string): string;
get releaseGroups(): Map<ReleaseGroupName, IReleaseGroup>;
reload(): void;
readonly root: string;
readonly upstreamRemotePartialUrl?: string | undefined;
get workspaces(): Map<WorkspaceName, IWorkspace>;
}

// @public
export function getAllDependenciesInRepo(repo: IFluidRepo, packages: IPackage[]): {
packages: IPackage[];
releaseGroups: IReleaseGroup[];
workspaces: IWorkspace[];
};

// @public
export function getFluidRepoLayout(searchPath: string, noCache?: boolean): {
config: IFluidRepoLayout;
Expand Down Expand Up @@ -49,7 +76,6 @@ export interface IFluidRepo<P extends IPackage = IPackage> extends Reloadable {
configuration: IFluidRepoLayout;
getGitRepository(): Promise<Readonly<SimpleGit>>;
getPackageReleaseGroup(pkg: Readonly<P>): Readonly<IReleaseGroup>;
getPackageWorkspace(pkg: Readonly<P>): Readonly<IWorkspace>;
packages: Map<PackageName, P>;
relativeToRepo(p: string): string;
releaseGroups: Map<ReleaseGroupName, IReleaseGroup>;
Expand All @@ -72,7 +98,7 @@ export interface IFluidRepoLayout {

// @public
export interface Installable {
checkInstall(): Promise<boolean>;
checkInstall(): Promise<true | string[]>;
install(updateLockfile: boolean): Promise<boolean>;
}

Expand Down Expand Up @@ -126,6 +152,7 @@ export function isIReleaseGroup(toCheck: Exclude<any, string | number | ReleaseG
// @public
export interface IWorkspace extends Installable, Reloadable {
directory: string;
fluidRepo: IFluidRepo;
name: WorkspaceName;
packages: IPackage[];
releaseGroups: Map<ReleaseGroupName, IReleaseGroup>;
Expand All @@ -134,13 +161,47 @@ export interface IWorkspace extends Installable, Reloadable {
toString(): string;
}

// @public
export function loadFluidRepo<P extends IPackage>(searchPath: string, upstreamRemotePartialUrl?: string): IFluidRepo<P>;

// @public
export class NotInGitRepository extends Error {
constructor(path: string);
// (undocumented)
constructor(
path: string);
readonly path: string;
}

// @public
export abstract class PackageBase<J extends PackageJson = PackageJson, TAddProps extends AdditionalPackageProps = undefined> implements IPackage<J> {
constructor(
packageJsonFilePath: string,
packageManager: IPackageManager,
workspace: IWorkspace,
isWorkspaceRoot: boolean,
releaseGroup: ReleaseGroupName,
isReleaseGroupRoot: boolean, additionalProperties?: TAddProps);
checkInstall(): Promise<true | string[]>;
get combinedDependencies(): Generator<PackageDependency, void>;
get directory(): string;
getScript(name: string): string | undefined;
install(updateLockfile: boolean): Promise<boolean>;
isReleaseGroupRoot: boolean;
readonly isWorkspaceRoot: boolean;
get name(): PackageName;
get nameColored(): string;
get packageJson(): J;
readonly packageJsonFilePath: string;
readonly packageManager: IPackageManager;
get private(): boolean;
readonly releaseGroup: ReleaseGroupName;
reload(): void;
savePackageJson(): Promise<void>;
// (undocumented)
toString(): string;
get version(): string;
readonly workspace: IWorkspace;
}

// @public
export interface PackageDependency {
depKind: "prod" | "dev" | "peer";
Expand Down Expand Up @@ -170,7 +231,6 @@ export type ReleaseGroupName = Opaque<string, IReleaseGroup>;

// @public
export interface Reloadable {
// (undocumented)
reload(): void;
}

Expand Down
3 changes: 2 additions & 1 deletion build-tools/packages/build-infrastructure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@
"build": "fluid-build . --task build",
"build:commonjs": "npm run tsc && npm run build:test",
"build:compile": "npm run build:commonjs",
"build:docs": "api-extractor run --local && typedoc",
"build:docs": "api-extractor run --local",
"build:esnext": "tsc --project ./tsconfig.json",
"build:manifest": "oclif manifest",
"build:readme": "oclif readme --version 0.0.0 --no-aliases --readme-path src/docs/cli.md",
"build:test": "npm run build:test:esm && npm run build:test:cjs",
"build:test:cjs": "fluid-tsc commonjs --project ./src/test/tsconfig.cjs.json",
"build:test:esm": "tsc --project ./src/test/tsconfig.json",
Expand Down
91 changes: 91 additions & 0 deletions build-tools/packages/build-infrastructure/src/commands/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

import { Command, Flags } from "@oclif/core";
import colors from "picocolors";

import { getAllDependenciesInRepo, loadFluidRepo } from "../fluidRepo.js";
import type { IFluidRepo } from "../types.js";

/**
* This command is intended for testing and debugging use only.
*/
export class ListCommand extends Command {
static override description =
"List objects in the Fluid repo, like release groups, workspaces, and packages. USED FOR TESTING ONLY.";

static override flags = {
path: Flags.directory({
description: "Path to start searching for the Fluid repo.",
default: ".",
}),
full: Flags.boolean({
description: "Output the full report.",
}),
} as const;

async run(): Promise<void> {
const { flags } = await this.parse(ListCommand);
const { path: searchPath, full } = flags;

// load the Fluid repo
const repo = loadFluidRepo(searchPath);
const _ = full ? await this.logFullReport(repo) : await this.logCompactReport(repo);
}

private async logFullReport(repo: IFluidRepo): Promise<void> {
this.logIndent(colors.underline("Repository layout"));
for (const workspace of repo.workspaces.values()) {
this.log();
this.logIndent(colors.blue(workspace.toString()), 1);
for (const releaseGroup of workspace.releaseGroups.values()) {
this.log();
this.logIndent(colors.green(releaseGroup.toString()), 2);
this.logIndent(colors.bold("Packages"), 3);
for (const pkg of releaseGroup.packages) {
const pkgMessage = colors.white(
`${pkg.name}${pkg.isReleaseGroupRoot ? colors.bold(" (root)") : ""}`,
);
this.logIndent(pkgMessage, 4);
}

const { releaseGroups, workspaces } = getAllDependenciesInRepo(
repo,
releaseGroup.packages,
);
if (releaseGroups.length > 0 || workspaces.length > 0) {
this.log();
this.logIndent(colors.bold("Depends on:"), 3);
for (const depReleaseGroup of releaseGroups) {
this.logIndent(depReleaseGroup.toString(), 4);
}
for (const depWorkspace of workspaces) {
this.logIndent(depWorkspace.toString(), 4);
}
}
}
}
}

private async logCompactReport(repo: IFluidRepo): Promise<void> {
this.logIndent(colors.underline("Repository layout"));
for (const workspace of repo.workspaces.values()) {
this.log();
this.logIndent(colors.blue(workspace.toString()), 1);
this.logIndent(colors.bold("Packages"), 2);
for (const pkg of workspace.packages) {
const pkgMessage = colors.white(
`${pkg.isReleaseGroupRoot ? colors.bold("(root) ") : ""}${pkg.name} ${colors.black(colors.bgGreen(pkg.releaseGroup))}`,
);
this.logIndent(pkgMessage, 3);
}
}
}

private logIndent(message: string, indent: number = 0): void {
const spaces = " ".repeat(2 * indent);
this.log(`${spaces}${message}`);
}
}
7 changes: 4 additions & 3 deletions build-tools/packages/build-infrastructure/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,11 @@ export function matchesReleaseGroupDefinition(
}

/**
* Finds the name of the release group that a package belongs to.
* Finds the name of the release group that a package belongs to based on the release group configuration within a
* workspace.
*
* @param pkg - The package for which to fina a release group.
* @param definition - The "releaseGroups" config from the RepoLayout config/
* @param pkg - The package for which to find a release group.
* @param definition - The "releaseGroups" config from the RepoLayout configuration.
* @returns The name of the package's release group.
*/
export function findReleaseGroupForPackage(
Expand Down
37 changes: 37 additions & 0 deletions build-tools/packages/build-infrastructure/src/docs/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: repo-layout -- the build-infrastructure CLI
---

# repo-layout -- the build-infrastructure CLI

# Table of contents

<!-- toc -->
* [repo-layout -- the build-infrastructure CLI](#repo-layout----the-build-infrastructure-cli)
* [Table of contents](#table-of-contents)
* [Commands](#commands)
<!-- tocstop -->

# Commands

<!-- commands -->
* [`repo-layout list`](#repo-layout-list)

## `repo-layout list`

List objects in the Fluid repo, like release groups, workspaces, and packages. USED FOR TESTING ONLY.

```
USAGE
$ repo-layout list [--path <value>] [--full]
FLAGS
--full Output the full report.
--path=<value> [default: .] Path to start searching for the Fluid repo.
DESCRIPTION
List objects in the Fluid repo, like release groups, workspaces, and packages. USED FOR TESTING ONLY.
```

_See code: [src/commands/list.ts](https://github.com/microsoft/FluidFramework/blob/main/build-tools/packages/build-infrastructure/src/commands/list.ts)_
<!-- commandsstop -->
7 changes: 6 additions & 1 deletion build-tools/packages/build-infrastructure/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
* An error thrown when a path is not within a Git repository.
*/
export class NotInGitRepository extends Error {
constructor(public readonly path: string) {
constructor(
/**
* The path that was checked and found to be outside a Git repository.
*/
public readonly path: string,
) {
super(`Path is not in a Git repository: ${path}`);
}
}
Loading

0 comments on commit 199b9d0

Please sign in to comment.