diff --git a/build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildTasks.ts b/build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildTasks.ts index 8a103a3bc59f..53f308fd5275 100644 --- a/build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildTasks.ts +++ b/build-tools/packages/build-cli/src/library/repoPolicyCheck/fluidBuildTasks.ts @@ -4,31 +4,23 @@ */ import fs from "node:fs"; -import { createRequire } from "node:module"; import path from "node:path"; -import { - updatePackageJsonFile, - updatePackageJsonFileAsync, -} from "@fluid-tools/build-infrastructure"; +import { updatePackageJsonFile } from "@fluid-tools/build-infrastructure"; import { FluidRepo, type Package, type PackageJson, TscUtils, - getEsLintConfigFilePath, getFluidBuildConfig, getTaskDefinitions, normalizeGlobalTaskDefinitions, } from "@fluidframework/build-tools"; -import JSON5 from "json5"; import * as semver from "semver"; import type { TsConfigJson } from "type-fest"; import { getFlubConfig } from "../../config.js"; import { type Handler, readFile } from "./common.js"; import { FluidBuildDatabase } from "./fluidBuildDatabase.js"; -const require = createRequire(import.meta.url); - /** * Get and cache the tsc check ignore setting */ @@ -164,129 +156,6 @@ function findTscScript(json: Readonly, project: string): string | u throw new Error(`'${project}' used in scripts '${tscScripts.join("', '")}'`); } -// This should be TSESLint.Linter.Config or .ConfigType from @typescript-eslint/utils -// but that can only be used once this project is using Node16 resolution. PR #20972 -// We could derive type from @typescript-eslint/eslint-plugin, but that it will add -// peer dependency requirements. -interface EslintConfig { - parserOptions?: { - // https://typescript-eslint.io/packages/parser/#project - // eslint-disable-next-line @rushstack/no-new-null - project?: string | string[] | boolean | null; - }; -} -/** - * Get a list of build script names that the eslint depends on, based on .eslintrc file. - * @remarks eslint does not depend on build tasks for the projects it references. (The - * projects' configurations guide eslint typescript parser to use original typescript - * source.) The packages that those projects depend on must be built. So effectively - * eslint has the same prerequisites as the build tasks for the projects referenced. - * @param packageDir - directory of the package - * @param root - directory of the Fluid repo root - * @param json - content of the package.json - * @returns list of build script names that the eslint depends on - */ -async function eslintGetScriptDependencies( - packageDir: string, - root: string, - json: Readonly, -): Promise<(string | string[])[]> { - if (json.scripts?.eslint === undefined) { - return []; - } - - const eslintConfig = getEsLintConfigFilePath(packageDir); - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (!eslintConfig) { - throw new Error(`Unable to find eslint config file for package in ${packageDir}`); - } - - let config: EslintConfig; - try { - const { ext } = path.parse(eslintConfig); - if (ext === ".mjs") { - throw new Error(`Eslint config '${eslintConfig}' is ESM; only CommonJS is supported.`); - } - - if (ext !== ".js" && ext !== ".cjs") { - // TODO: optimize double read for TscDependentTask.getDoneFileContent and there. - const configFile = fs.readFileSync(eslintConfig, "utf8"); - config = JSON5.parse(configFile); - } else { - // This code assumes that the eslint config will be in CommonJS, because if it's ESM the require call will fail. - config = require(path.resolve(eslintConfig)) as EslintConfig; - if (config === undefined) { - throw new Error(`Exports not found in ${eslintConfig}`); - } - } - } catch (error) { - throw new Error(`Unable to load eslint config file ${eslintConfig}. ${error}`); - } - - let projects = config.parserOptions?.project; - if (!Array.isArray(projects) && typeof projects !== "string") { - // "config" is normally the raw configuration as file is on disk and has not - // resolved and merged any extends specifications. So, "project" is what is - // set in top file. - if (projects === false || projects === null) { - // type based linting is disabled - assume no task prerequisites - return []; - } - // @typescript-eslint/parser allows true to mean use closest tsconfig.json, but we want - // explicit listings for dependency clarity. - if (projects === true) { - throw new Error( - `${json.name} eslint config's 'parserOptions' setting has 'project' set to 'true', which is unsupported by fluid-build. Please specify one or more tsconfig files instead.`, - ); - } - // projects === undefined, which @typescript-eslint/eslint-plugin handles by using - // project path: ./tsconfig.json. - projects = ["./tsconfig.json"]; - } - const projectsArray = Array.isArray(projects) ? projects : [projects]; - - // Get the build scripts for the projects - const siblingTscScripts = projectsArray - // Projects with ".lint." in the name are not required to have other associated tasks. - .filter((project) => !project.includes(".lint.")) - .map((project) => { - const found = findTscScript(json, project); - - if (found === undefined) { - throw new Error( - `Unable to find tsc script using project '${project}' specified in '${eslintConfig}' within package '${json.name}'`, - ); - } - - return found; - }); - if (siblingTscScripts.length === 0) { - return []; - } - - // Get the dependencies for the sibling tsc scripts that are the dependencies for eslint - const packageMap = getFluidPackageMap(root); - const emptyIgnoreSet = new Set(); - const collectiveDependencies: (string | string[])[] = []; - for (const script of siblingTscScripts) { - const scriptCommands = json.scripts[script]; - if (scriptCommands === undefined) { - throw new Error( - `internal inconsistency - expected '${script}' not found in package '${json.name}'`, - ); - } - for (const commandUntrimmed of scriptCommands.split("&&")) { - const command = commandUntrimmed.trim(); - if (shouldProcessScriptForTsc(script, command, emptyIgnoreSet)) { - collectiveDependencies.push( - ...getTscCommandDependencies(packageDir, json, script, command, packageMap), - ); - } - } - } - return collectiveDependencies; -} - /** * Check if package has Fluid build enabled. * These are packages that are described in 'repoPackages' property in Fluid build config @@ -723,46 +592,6 @@ function checkTscDependencies({ const match = /(^|\/)package\.json/i; export const handlers: Handler[] = [ - { - name: "fluid-build-tasks-eslint", - match, - handler: async (file: string, root: string): Promise => { - let json: PackageJson; - try { - json = JSON.parse(readFile(file)) as PackageJson; - } catch { - return `Error parsing JSON file: ${file}`; - } - - if (!isFluidBuildEnabled(root, json)) { - return; - } - try { - const scriptDeps = await eslintGetScriptDependencies(path.dirname(file), root, json); - return checkTaskDeps(root, json, "eslint", scriptDeps); - } catch (error: unknown) { - return (error as Error).message; - } - }, - resolver: async ( - file: string, - root: string, - ): Promise<{ resolved: boolean; message?: string }> => { - let result: { resolved: boolean; message?: string } = { resolved: true }; - await updatePackageJsonFileAsync(path.dirname(file), async (json) => { - if (!isFluidBuildEnabled(root, json)) { - return; - } - try { - const scriptDeps = await eslintGetScriptDependencies(path.dirname(file), root, json); - patchTaskDeps(root, json, "eslint", scriptDeps); - } catch (error: unknown) { - result = { resolved: false, message: (error as Error).message }; - } - }); - return result; - }, - }, { /** * Checks that all tsc project files (tsconfig.json), are only used once as the main diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts index 72e8d3bddbad..6420dd24edc0 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts @@ -587,12 +587,12 @@ export class UnknownLeafTask extends LeafTask { */ export abstract class LeafWithFileStatDoneFileTask extends LeafWithDoneFileTask { /** - * @returns the list of absolute paths to files that this task depends on. + * @returns the list of package relative or absolute paths to files that this task depends on. */ protected abstract getInputFiles(): Promise; /** - * @returns the list of absolute paths to files that this task generates. + * @returns the list of package relative or absolute paths to files that this task generates. */ protected abstract getOutputFiles(): Promise; @@ -608,69 +608,69 @@ export abstract class LeafWithFileStatDoneFileTask extends LeafWithDoneFileTask } protected async getDoneFileContent(): Promise { - if (this.useHashes) { - return this.getHashDoneFileContent(); - } - + let srcFiles: string[]; + let dstFiles: string[]; // Gather the file information try { - const srcFiles = await this.getInputFiles(); - const dstFiles = await this.getOutputFiles(); - const srcTimesP = Promise.all( - srcFiles - .map((match) => this.getPackageFileFullPath(match)) - .map((match) => stat(match)), + srcFiles = (await this.getInputFiles()).map((match) => + this.getPackageFileFullPath(match), ); - const dstTimesP = Promise.all( - dstFiles - .map((match) => this.getPackageFileFullPath(match)) - .map((match) => stat(match)), + dstFiles = (await this.getOutputFiles()).map((match) => + this.getPackageFileFullPath(match), ); - const [srcTimes, dstTimes] = await Promise.all([srcTimesP, dstTimesP]); - - const srcInfo = srcTimes.map((srcTime) => { - return { mtimeMs: srcTime.mtimeMs, size: srcTime.size }; - }); - const dstInfo = dstTimes.map((dstTime) => { - return { mtimeMs: dstTime.mtimeMs, size: dstTime.size }; - }); - return JSON.stringify({ srcFiles, dstFiles, srcInfo, dstInfo }); } catch (e: any) { - this.traceError(`error comparing file times: ${e.message}`); + this.traceError(`error collecting files: ${e.message}`); this.traceTrigger("failed to get file stats"); return undefined; } - } - private async getHashDoneFileContent(): Promise { - const mapHash = async (name: string) => { - const hash = await this.node.context.fileHashCache.getFileHash( - this.getPackageFileFullPath(name), - ); - return { name, hash }; - }; + // sort by path for determinism + srcFiles.sort(); + dstFiles.sort(); - try { - const srcFiles = await this.getInputFiles(); - const dstFiles = await this.getOutputFiles(); - const srcHashesP = Promise.all(srcFiles.map(mapHash)); - const dstHashesP = Promise.all(dstFiles.map(mapHash)); - - const [srcHashes, dstHashes] = await Promise.all([srcHashesP, dstHashesP]); - - // sort by name for determinism - srcHashes.sort(sortByName); - dstHashes.sort(sortByName); - - const output = JSON.stringify({ - srcHashes, - dstHashes, - }); - return output; - } catch (e: any) { - this.traceError(`error calculating file hashes: ${e.message}`); - this.traceTrigger("failed to get file hash"); - return undefined; + if (!this.useHashes) { + try { + const srcTimesP = Promise.all(srcFiles.map((match) => stat(match))); + const dstTimesP = Promise.all(dstFiles.map((match) => stat(match))); + const [srcTimes, dstTimes] = await Promise.all([srcTimesP, dstTimesP]); + + const srcInfo = srcTimes.map((srcTime) => ({ + mtimeMs: srcTime.mtimeMs, + size: srcTime.size, + })); + const dstInfo = dstTimes.map((dstTime) => ({ + mtimeMs: dstTime.mtimeMs, + size: dstTime.size, + })); + return JSON.stringify({ srcFiles, dstFiles, srcInfo, dstInfo }); + } catch (e: any) { + this.traceError(`error comparing file times: ${e.message}`); + this.traceTrigger("failed to get file stats"); + return undefined; + } + } else { + const mapHash = async (name: string) => { + const hash = await this.node.context.fileHashCache.getFileHash(name); + return { name, hash }; + }; + + try { + const srcFiles = await this.getInputFiles(); + const dstFiles = await this.getOutputFiles(); + const srcHashesP = Promise.all(srcFiles.map(mapHash)); + const dstHashesP = Promise.all(dstFiles.map(mapHash)); + + const [srcHashes, dstHashes] = await Promise.all([srcHashesP, dstHashesP]); + const output = JSON.stringify({ + srcHashes, + dstHashes, + }); + return output; + } catch (e: any) { + this.traceError(`error calculating file hashes: ${e.message}`); + this.traceTrigger("failed to get file hash"); + return undefined; + } } } } @@ -747,13 +747,3 @@ export abstract class LeafWithGlobInputOutputDoneFileTask extends LeafWithFileSt return files; } } - -function sortByName(a: { name: string }, b: { name: string }): number { - if (a.name < b.name) { - return -1; - } - if (a.name > b.name) { - return 1; - } - return 0; -} diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/lintTasks.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/lintTasks.ts index bc2e36a9e9e5..4ef6df4e58cd 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/lintTasks.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/lintTasks.ts @@ -3,29 +3,39 @@ * Licensed under the MIT License. */ -import { getEsLintConfigFilePath, getInstalledPackageVersion } from "../taskUtils"; -import { TscDependentTask } from "./tscTask"; +import path from "node:path"; +import { existsSync } from "node:fs"; -export class TsLintTask extends TscDependentTask { - protected get configFileFullPaths() { - return [this.getPackageFileFullPath("tslint.json")]; - } +import { + getEsLintConfigFilePath, + getInstalledPackageVersion, + getRecursiveFiles, +} from "../taskUtils"; +import { LeafWithFileStatDoneFileTask } from "./leafTask"; - protected async getToolVersion() { - return getInstalledPackageVersion("tslint", this.node.pkg.directory); - } -} +export class EsLintTask extends LeafWithFileStatDoneFileTask { + protected async getInputFiles(): Promise { + // Files which might be linted + // To be truly correct, this would read the config file, interpret the config, find the projects files, and include those, then include the files from their blobs. + // This would be difficult with the current config file format, which will also be changing soon, so not worth doing. + // Assuming all packages have a similar structure, and just lint these files is close enough. + const lintDirectories = ["src", "tests", "test"]; -export class EsLintTask extends TscDependentTask { - private _configFileFullPath: string | undefined; - protected get configFileFullPaths() { - if (!this._configFileFullPath) { - this._configFileFullPath = getEsLintConfigFilePath(this.package.directory); - if (!this._configFileFullPath) { - throw new Error(`Unable to find config file for eslint ${this.command}`); - } + const files: string[] = await getRecursiveFiles( + ...lintDirectories + .map((dir) => path.join(this.node.pkg.directory, dir)) + .filter((dir) => existsSync(dir)), + ); + // Include config file if present + const config = getEsLintConfigFilePath(this.node.pkg.directory); + if (config) { + files.push(config); } - return [this._configFileFullPath]; + + return files; + } + protected async getOutputFiles(): Promise { + return []; } protected get useWorker() { diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/prettierTask.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/prettierTask.ts deleted file mode 100644 index 9953c2b3fe03..000000000000 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/prettierTask.ts +++ /dev/null @@ -1,122 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { existsSync } from "node:fs"; -import { readFile, stat } from "node:fs/promises"; -import * as path from "node:path"; -import ignore from "ignore"; - -import type { BuildContext } from "../../buildContext"; -import type { BuildPackage } from "../../buildGraph"; -import { getInstalledPackageVersion, getRecursiveFiles, globFn } from "../taskUtils"; -import { LeafWithDoneFileTask } from "./leafTask"; - -export class PrettierTask extends LeafWithDoneFileTask { - private parsed: boolean = false; - private entries: string[] = []; - private ignorePath: string | undefined; - constructor( - node: BuildPackage, - command: string, - context: BuildContext, - taskName: string | undefined, - ) { - super(node, command, context, taskName); - - // TODO: something better - const args = this.command.split(" "); - if (args[0] !== "prettier") { - return; - } - for (let i = 1; i < args.length; i++) { - if (args[i].startsWith("--")) { - if (args[i] === "--check" || args[i] === "--cache") { - continue; - } - if (args[i] === "--ignore-path" && i + 1 < args.length) { - this.ignorePath = args[i + 1]; - i++; - continue; - } - return; - } - let entry = args[i]; - if (entry.startsWith('"') && entry.endsWith('"')) { - entry = entry.substring(1, entry.length - 1); - } - this.entries.push(entry); - } - this.parsed = this.entries.length !== 0; - } - protected get configFileFullPath() { - // Currently there's no package-level config file, so just use tsconfig.json - return this.getPackageFileFullPath(".prettierrc.json"); - } - - protected async getDoneFileContent() { - if (!this.parsed) { - this.traceError(`error generating done file content, unable to understand command line`); - return undefined; - } - - let ignoreEntries: string[] = []; - const ignorePath = this.ignorePath ?? ".prettierignore"; - const ignoreFile = this.getPackageFileFullPath(ignorePath); - try { - if (existsSync(ignoreFile)) { - const ignoreFileContent = await readFile(ignoreFile, "utf8"); - ignoreEntries = ignoreFileContent.split(/\r?\n/); - ignoreEntries = ignoreEntries.filter((value) => value && !value.startsWith("#")); - } else if (this.ignorePath) { - this.traceError(`error generating done file content, unable to find ${ignoreFile}`); - return undefined; - } - } catch (e) { - this.traceError(`error generating done file content, unable to read ${ignoreFile} file`); - return undefined; - } - - // filter some of the extension the prettier doesn't care about as well - ignoreEntries.push("**/*.log", "**/*.tsbuildinfo"); - - const ignoreObject = ignore().add(ignoreEntries); - let files: string[] = []; - try { - for (let i = 0; i < this.entries.length; i++) { - const entry = this.entries[i]; - const fullPath = this.getPackageFileFullPath(entry); - if (existsSync(fullPath)) { - if ((await stat(fullPath)).isDirectory()) { - // TODO: This includes files that prettier might not check - const recursiveFiles = await getRecursiveFiles(fullPath); - files.push( - ...recursiveFiles.map((file) => path.relative(this.node.pkg.directory, file)), - ); - } else { - files.push(entry); - } - } else { - const globFiles = await globFn(entry, { cwd: this.node.pkg.directory }); - files.push(...globFiles); - } - } - files = ignoreObject.filter(files); - const hashesP = files.map(async (name) => { - const hash = await this.node.context.fileHashCache.getFileHash( - this.getPackageFileFullPath(name), - ); - return { name, hash }; - }); - const hashes = await Promise.all(hashesP); - return JSON.stringify({ - version: await getInstalledPackageVersion("prettier", this.node.pkg.directory), - hashes, - }); - } catch (e) { - this.traceError(`error generating done file content. ${e}`); - return undefined; - } - } -} diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts index 595b83bd34f8..bd43fad8d0de 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts @@ -15,7 +15,7 @@ import { DeclarativeLeafTask } from "./leaf/declarativeTask"; import { FlubCheckLayerTask, FlubCheckPolicyTask, FlubListTask } from "./leaf/flubTasks"; import { GenerateEntrypointsTask } from "./leaf/generateEntrypointsTask.js"; import { type LeafTask, UnknownLeafTask } from "./leaf/leafTask"; -import { EsLintTask, TsLintTask } from "./leaf/lintTasks"; +import { EsLintTask } from "./leaf/lintTasks"; import { CopyfilesTask, DepCruiseTask, @@ -25,7 +25,6 @@ import { LesscTask, TypeValidationTask, } from "./leaf/miscTasks"; -import { PrettierTask } from "./leaf/prettierTask"; import { Ts2EsmTask } from "./leaf/ts2EsmTask"; import { TscTask } from "./leaf/tscTask"; import { WebpackTask } from "./leaf/webpackTask"; @@ -39,14 +38,12 @@ const executableToLeafTask: { "ts2esm": Ts2EsmTask, "tsc": TscTask, "fluid-tsc": TscTask, - "tslint": TsLintTask, "eslint": EsLintTask, "webpack": WebpackTask, "parallel-webpack": WebpackTask, "lessc": LesscTask, "copyfiles": CopyfilesTask, "echo": EchoTask, - "prettier": PrettierTask, "gen-version": GenVerTask, "gf": GoodFence, "api-extractor": ApiExtractorTask, diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/taskUtils.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/taskUtils.ts index bb12876ed8e0..86d5bc12ba40 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/taskUtils.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/taskUtils.ts @@ -40,22 +40,22 @@ export async function getInstalledPackageVersion(packageName: string, cwd: strin /** * Given a directory path, returns an array of all files within the path, rooted in the provided path. + * @remarks + * Order might not be stable. */ -export async function getRecursiveFiles(pathName: string) { - const files = await readdir(pathName, { withFileTypes: true }); - const result: string[] = []; - for (let i = 0; i < files.length; i++) { - const dirent = files[i]; - const subPathName = path.join(pathName, dirent.name); - if (dirent.name !== "node_modules" && !dirent.name.startsWith(".")) { - if (dirent.isDirectory()) { - result.push(...(await getRecursiveFiles(subPathName))); - } else { - result.push(subPathName); +export async function getRecursiveFiles(...pathName: string[]): Promise { + const files: string[] = []; + await Promise.all( + pathName.map(async (dir) => { + const contents = await readdir(dir, { withFileTypes: true, recursive: true }); + for (const item of contents) { + if (item.isFile()) { + files.push(path.join(item.parentPath, item.name)); + } } - } - } - return result; + }), + ); + return files; } /** diff --git a/fluidBuild.config.cjs b/fluidBuild.config.cjs index 3acdd5d769fb..241684b0efcc 100644 --- a/fluidBuild.config.cjs +++ b/fluidBuild.config.cjs @@ -81,6 +81,7 @@ module.exports = { "build:test": ["typetests:gen", "tsc"], "build:test:cjs": ["typetests:gen", "tsc"], "build:test:esm": ["typetests:gen", "build:esnext"], + // Everything needed for the API of the package to be consumable by its dependencies. "api": { dependsOn: ["api-extractor:commonjs", "api-extractor:esnext"], script: false, @@ -129,9 +130,14 @@ module.exports = { }, "check:biome": [], "check:prettier": [], - // ADO #7297: Review why the direct dependency on 'build:esm:test' is necessary. - // Should 'compile' be enough? compile -> build:test -> build:test:esm - "eslint": ["compile", "build:test:esm"], + // Linting can require resolving imports into dependencies, and thus "^api". + // If all packages only linted esm build this could be "^api-extractor:esnext:, but some, like + // experimental/PropertyDDS/packages/property-properties lint commonjs builds. + // Linting also requires all the code to lint to exist, so we depend on "typetests:gen": + // That could be omitted if we ensure that generated code is not linted. + // The lint task does not seem to invalidate properly, so we take a dependency on "build:test:esm" as well so lint will rerun when the code changes. + // This may be related to AB#7297. + "eslint": ["^api", "typetests:gen", "build:test:esm"], "good-fences": [], "format:biome": [], "format:prettier": [], @@ -336,17 +342,10 @@ module.exports = { // Exclusion per handler handlerExclusions: { "fluid-build-tasks-eslint": [ - // There are no built files, but a tsconfig.json is present to simplify the - // eslint config. - "azure/packages/azure-local-service/package.json", - // eslint doesn't really depend on build. Doing so just slows down a package build. - "^packages/test/snapshots/package.json", - "^packages/test/test-utils/package.json", - // TODO: AB#7630 uses lint only ts projects for coverage which don't have representative tsc scripts - "^packages/tools/fluid-runner/package.json", - - // Server packages need to be cleaned up; excluding as a workaround - "^server/routerlicious/packages/.*/package.json", + // This rule forces dependencies on "Tsc" for the package being linted, which is undesired. + // Additionally this rule's validation was very complex compared to expressing the requirements here and already needed many exceptions, and thus is not offering much value. + // Future versions of build tools may remove this rule entirely. + ".*", ], "fluid-build-tasks-tsc": [ // Server packages need to be cleaned up; excluding as a workaround diff --git a/packages/dds/tree/package.json b/packages/dds/tree/package.json index cc0c52ad8e40..98ed598c50c5 100644 --- a/packages/dds/tree/package.json +++ b/packages/dds/tree/package.json @@ -231,6 +231,10 @@ "...", "@fluidframework/id-compressor#build:test:esm" ], + "eslint": [ + "...", + "@fluidframework/id-compressor#build:test:esm" + ], "ci:build:docs": [ "build:esnext" ]