Skip to content

build(common-utils): modernize build, lint, and test setup#27319

Open
ChumpChief wants to merge 32 commits into
microsoft:mainfrom
ChumpChief:modernize-common-utils-tsconfig
Open

build(common-utils): modernize build, lint, and test setup#27319
ChumpChief wants to merge 32 commits into
microsoft:mainfrom
ChumpChief:modernize-common-utils-tsconfig

Conversation

@ChumpChief
Copy link
Copy Markdown
Contributor

@ChumpChief ChumpChief commented May 15, 2026

Description

Tracking: AB#73445.

@fluidframework/common-utils is deprecated but lives in this repo as its own pnpm workspace. Its bespoke build/lint/test setup makes routine maintenance (CG alerts, pipeline rollouts) expensive — each change has to be done twice. This PR brings it in line with client-utils and protocol-definitions.

Updates

  • Modern exports map. Replaced the legacy main/module/browser field setup with a conditional exports map providing per-environment entrypoints. indexBrowser.ts and indexNode.ts now carry the full public API surface (was: thin env-specific files used only by the bundler browser-field remap).
  • Dropped the ESM dual-emit. Removed tsconfig.esnext.json and the lib/ output. Modern resolution already routed to CJS via main since there was no exports map; the ESM build was orphaned. attw 🟢 across all four modes. client-utils is the dual-emit replacement for downstream.
  • Node16 tsconfig layout. Single root tsconfig extending tsconfig.node16.json, .js-extension relative imports, tsconfig.test.node16.json-based test configs (mirrors client-utils).
  • API extraction. Replaced the single api-extractor.json + api-extractor-lint.json with a per-entrypoint api-extractor/ config dir (browser/node lint + model-browser docs). Added @arethetypeswrong/cli, chained into lint.
  • Mocha setup. Adopted @fluid-internal/mocha-test-setup via .mocharc.cjs, replacing the bespoke test:report / test:mocha:multireport chain.
  • Eslint config. Replaced the hand-rolled flat config with @fluidframework/eslint-config-fluid@^10 recommended (resolves AB#59243). Violations silenced package-wide — deprecation mode, no source cleanups.
  • Tooling refresh. Bumped @fluid-tools/build-cli + @fluidframework/build-tools to ^0.65.0; refreshed mocha/rewire/cross-env/jest-junit/ts-node.
  • Misc cleanup. lodashlodash.clonedeep (named import broken under ESM); dropped @types/base64-js and the bump-version script; renamed jest.config.js / jest-puppeteer.config.js.cjs.
  • The Buffer re-export from indexNode is now type-only. import { Buffer } from "@fluidframework/common-utils"; new Buffer(...) will fail at runtime. Package is @internal/@deprecated with no in-repo consumers; Node global Buffer and IsoBuffer remain available.

Reviewer Guidance

The review process is outlined on this wiki page.

Commits are scoped to one logical change for bisecting. Verified locally: build, test:mocha (53), test:jest (19), check:exports (3/3), attw (🟢 all four modes), eslint.

ChumpChief and others added 15 commits May 14, 2026 20:52
Prep for switching the package to ESM (Node16) emit. Adding the
explicit ".js" suffixes is a no-op under the current TypeScript
compiler settings, so it can land safely ahead of the tsconfig swap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adopt the same tsconfig topology used by client-utils and
protocol-definitions:

- tsconfig.json now extends build-common's tsconfig.node16.json and
  emits ESM into ./lib (was the role of tsconfig.esnext.json).
- New tsconfig.cjs.json extends tsconfig.json and emits CJS into
  ./dist via fluid-tsc commonjs.
- tsconfig.esnext.json removed.
- Test tsconfigs migrated to extend tsconfig.test.node16.json. The
  mocha config now has both an ESM (tsconfig.json) and a CJS
  (tsconfig.cjs.json) variant; the jest config is renamed
  tsconfig.cjs.json since it must remain CJS (rewire).

package.json:
- "type": "module" with an exports map that resolves node vs.
  browser entry points for both import and require, replacing the
  older main/module/browser fields.
- tsc script now runs fluid-tsc commonjs and copies the cjs
  package.json shim to ./dist.
- build:esnext targets the new tsconfig.json.
- build:test:mocha now drives both the ESM and CJS variants.
- fluidBuild task graph updated for the split mocha builds and the
  separate build:esnext step.

Source updates required by Node16 module resolution:
- Drop internal-module imports (sha.js/sha1, sha.js/sha256,
  lodash/cloneDeep) in favor of top-level imports.
- Add the missing .js suffix on the dynamic ./hashFileNode import.

Strictness:
- exactOptionalPropertyTypes and noUncheckedIndexedAccess are
  disabled in tsconfig.json. The package is deprecated, so we opt
  out of the stricter base-config defaults rather than churn the
  source.

api-extractor:
- api-extractor.json now extends api-extractor-base.esm.no-legacy
  (instead of cjs).
- api-extractor-lint.json points at lib/index.d.ts.

eslint.config.mts switches from projectService to an explicit project
list so the renamed jest tsconfig is picked up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cosmetic only \u2014 reorder the mocha tsconfig types array to
["mocha", "node"] to match client-utils.

attw was attempted as a follow-up but reverted: adding the
@arethetypeswrong/cli devDependency forces a lockfile refresh, which
trips the workspace's no-downgrade trust policy on a pre-existing
rewire@5.0.0 \u2192 eslint@6.8.0 \u2192 cross-spawn@6.0.6 \u2192 semver@5.7.2
chain. Bumping rewire is out of scope for this change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The package now declares "type": "module", which makes Node treat
.js files as ESM. Both jest config files use CommonJS module.exports
syntax, so they fail to load with:

  ReferenceError: module is not defined in ES module scope

Rename them to .cjs so Node treats them as CommonJS, matching the
client-utils convention. No content changes \u2014 jest auto-discovers
both .cjs files by name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the legacy npm-script orchestration of build, build:compile,
build:commonjs, lint, and lint:fix with the fluid-build task graph
form used by client-utils and protocol-definitions:

  build         -> fluid-build . --task build
  build:commonjs-> fluid-build . --task commonjs
  build:compile -> fluid-build . --task compile
  lint          -> fluid-build . --task lint
  lint:fix      -> fluid-build . --task lint:fix

The existing fluidBuild.tasks block in package.json already declares
the dependency graph, so fluid-build picks it up unchanged. Verified
`npm run build` runs all 12 tasks, plus mocha (53), jest (19), and
lint stay green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Bump @fluid-tools/build-cli and @fluidframework/build-tools from
  ^0.63.0 to ^0.65.0 (matches the workspace-wide bump in
  7f856ce557cef04023a6dcfbb7eebbeedd30b95d).
- Add the trust-policy exclusions that bump pulls in transitively
  (@octokit/endpoint@9.0.6, semver@5.7.2 || 6.3.1).
- Regenerate src/test/types/validateCommonUtilsPrevious.generated.ts
  with the new build-cli; the only changes are a header tweak and a
  switch from a relative import to a package self-import:
    -import type * as current from "../../index.js";
    +import type * as current from "@fluidframework/common-utils";

That self-import surfaced a real bug in our modernized exports map.
The map points each platform entry directly at the platform file:
    node import    -> lib/indexNode.js
    default import -> lib/indexBrowser.js
but those two files only carried the platform-specific Buffer/hash/
performance bindings. The bulk of the API (assert, Heap,
NumberComparer, toUtf8, ...) lived in index.ts and was unreachable
through the package self-import.

Fix by adopting the client-utils convention:
- index.ts is now a stub that just re-exports indexNode (kept only
  so the type-test generator can resolve a canonical entry).
- indexNode.ts and indexBrowser.ts each list the full public API
  for their platform, with bufferNode/hashFileNode swapped for
  bufferBrowser/hashFileBrowser.

Side effect: api-report now records "(No @packageDocumentation
comment for this package)" because the comment moved off the
canonical entry; api-extractor only inspects that one. This matches
the existing client-utils api-report layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bump six devDependencies to the versions client-utils ships with:

  @types/base64-js  ^1.3.0  -> ^1.3.2
  cross-env         ^7.0.3  -> ^10.1.0
  jest-junit       ^10.0.0  -> ^16.0.0
  mocha             ^10.8.2 -> ^11.7.5
  rewire            ^5.0.0  -> ^9.0.1
  ts-node          ^10.9.1  -> ^10.9.2

Side effect: the rewire 5 -> 9 bump drops the legacy
eslint@6 -> cross-spawn@6 -> semver@5.7.2 chain that previously
forced a trust-policy exclusion on semver@5.7.2 just to install.
The semver exclusion is retained because semver@6.3.1 is still
pulled in elsewhere.

Add chokidar@4.0.3 to the workspace trust-policy exclusions; mocha@11
pulls it in transitively (precedent already in the root workspace).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
base64-js@1.5.x ships its own .d.ts files, so the @types/base64-js
package is now a no-op stub. pnpm flagged it as deprecated:

  WARN deprecated @types/base64-js@1.5.0: This is a stub types
  definition. base64-js provides its own type definitions, so you
  do not need this installed.

Build, mocha (53), and jest (19) stay green without it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The earlier attempt to add @arethetypeswrong/cli was blocked by a
trust-downgrade error on semver@5.7.2, pulled in transitively
through rewire@5 -> eslint@6 -> cross-spawn@6. The subsequent
rewire@5 -> ^9 bump removed that chain, so the install now goes
through cleanly.

Adds the script and the devDependency at ^0.15.2 (matches
protocol-definitions; client-utils uses ^0.18.2 but staying on
avoids pulling in a newer toolchain).

attw passes all four resolution modes (node10, node16 CJS/ESM,
bundler) — confirms the modernized exports map is correct.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match the protocol-definitions pattern of running attw as part of
lint:

  lint: fluid-build . --task lint --task check:are-the-types-wrong

Verified `npm run lint` runs the new attw task alongside the
existing 9 lint tasks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop two pieces of legacy plumbing that no other modernized package
in the repo carries:

  - --typescript-compiler-folder ./node_modules/typescript:
    forces api-extractor to use the package's own typescript install
    instead of its bundled one. api-extractor 7.58.1 ships with
    typescript 5.9.3 already, and the analyzer doesn't care which
    compiler emitted the .d.ts files it inspects.

  - copyfiles -u 1 "./_api-extractor-temp/doc-models/*" ../../../_api-extractor-temp/:
    mirrors the generated api.json into a repo-root temp directory.
    No tooling reads from there anymore; downstream docs aggregation
    walks each package's own ./_api-extractor-temp/doc-models/.

After this change build:docs / ci:build:docs match the
protocol-definitions form:

  build:docs    -> api-extractor run --local
  ci:build:docs -> api-extractor run

Verified `npm run build`, `npm run ci:build`, `npm run test:mocha`,
`npm run test:jest`, `npm run lint` (with attw chain) all pass.
The copyfiles devDependency is retained — the `tsc` script still
uses it to drop the cjs package.json shim into ./dist.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wire mocha through @fluid-internal/mocha-test-setup so the modernized
ESM test build under lib/test/mocha is actually executed (it was being
compiled but never run). Add a minimal .mocharc.cjs that delegates to
getFluidTestMochaConfig, mirroring the standalone-workspace pattern
used by tools/api-markdown-documenter and tools/benchmark, and replace
the bespoke :report script chain with the client-utils-style
test:mocha (esm + cjs), test:coverage, and ci:test scripts.

ESM execution exposed two latent bugs that worked under the old CJS-only
mocha runner:

* rangeTracker.ts pulled cloneDeep via a named import from lodash@4,
  which has no named ESM exports. Switch to the single-function
  lodash.clonedeep package (with @types/lodash.clonedeep) so the
  default import resolves cleanly under Node ESM.
* indexNode.ts re-exported Buffer as a value, but Buffer is declared
  with 'declare class' in bufferNode.ts and has no runtime presence.
  Re-export it as 'type Buffer' to match the client-utils convention
  and stop Node's ESM loader from rejecting the missing named binding.

While here, restore prettier formatting on the indexNode/indexBrowser
re-export lists.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the standalone hand-rolled flat config with the published
@fluidframework/eslint-config-fluid v10 'recommended' config, mirroring
the pattern client-utils uses internally (which consumes the same
package via the workspace catalog). This resolves the AB#59243 TODO
that motivated the standalone config in the first place.

Side benefits:
* drops five direct devDeps that the shared config now provides
  transitively: @eslint/js, @rushstack/eslint-plugin,
  eslint-plugin-import, eslint-plugin-unicorn, typescript-eslint
* picks up the import-x / depend / eslint-comments / promise / jsdoc
  rule sets that the shared config layers on

Common-utils is in maintenance for deprecation, so rather than fixing
the new violations the shared config surfaces, disable the rules
package-wide. The disabled set covers existing source patterns
(deprecated re-exports, node 'events' import, raw any, lodash.clonedeep
ban, charCodeAt vs codePointAt, 'utf-8' string, etc.) plus a couple of
warnings tied to legacy inline disable comments
(reportUnusedDisableDirectives, eslint-comments/require-description).

The non-standard test directory layout still requires overriding
projectService and providing an explicit project list, matching what
client-utils does.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Aligns with the version client-utils pins. attw still reports a clean
result across all four resolution modes (node10, node16 CJS, node16
ESM, bundler).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The bump-version script (npm version minor with no-push/no-git-tag-version)
predates the modern release-group machinery and has no remaining callers
in the repo (no pipeline yaml, no build-tools script, no other package
script). Reference packages such as client-utils and protocol-definitions
do not carry it. Versioning for this package now flows through the same
flub bump / flub release tooling as the rest of the client release group.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

Hi! Thank you for opening this PR. Want me to review it?

Based on the diff (4722 lines, 47 files), I've queued these reviewers:

  • Correctness — logic errors, race conditions, lifecycle issues
  • Security — vulnerabilities, secret exposure, injection
  • API Compatibility — breaking changes, release tags, type design
  • Performance — algorithmic regressions, memory leaks
  • Testing — coverage gaps, hollow tests

How this works

  • Adjust the reviewer set by ticking/unticking boxes above. Reviewer toggles alone don't trigger anything.

  • Tick Start review below to dispatch the review fleet.

  • After review finishes, tick Start review again to request another run — it auto-resets after each dispatch.

  • This comment updates as new commits land; your reviewer selections are preserved.

  • Start review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

🔭 PR Review Fleet Report

Note

This report is generated by an experimental AI review fleet and is provided as a beta feature. Findings are a starting point for discussion, not a gate. Use your own judgement.

Verdict: ❌ Request Changes

0 Spicy, 1 Pungent, 1 Smelly

Findings

Sev # Area File What Fix
🧄 Pungent H1 Correctness common/lib/common-utils/src/indexNode.ts:11 Buffer is re-exported as type-only (export { type Buffer }) from indexNode.ts, but the type-compatibility test in validateCommonUtilsPrevious.generated.ts:313 uses typeof current.Buffer in a type position — which requires Buffer to be a value export. Before this PR, index.ts did export * from './indexNode' which exported Buffer as a value (via export declare class Buffer in bufferNode.ts). Changing to export { type Buffer } removes the value declaration from the public API, making typeof current.Buffer a TypeScript error ('Buffer' only refers to a type, but is being used as a value here). Since ClassStatics_Buffer is not listed in typeValidation.broken in package.json, the test at line 313 is expected to pass — it will instead fail to compile, breaking the build:test:types CI step. Either change export { type Buffer } to export { Buffer } in indexNode.ts to preserve the value export (matching the old export * from './indexNode' behaviour), or add "ClassStatics_Buffer": {"backCompat": false} to the typeValidation.broken section in package.json and add a corresponding // @ts-expect-error compatibility expected to be broken comment above line 313 in validateCommonUtilsPrevious.generated.ts.
🧅 Smelly M1 Testing common/lib/common-utils/src/rangeTracker.ts:77 cloneDeep (lodash) was replaced with structuredClone in both the snapshot constructor (line 77) and serialize() (line 90). The existing tests verify that snapshot values are copied correctly but never verify that the copy is independent from the source — no test mutates the deserialized RangeTracker (via .add() or .updateBase()) and then checks that the original snapshot object's ranges array is unchanged. If structuredClone were ever replaced with a shallow copy (e.g., [...primary.ranges]), mutations to the cloned tracker's IRange items would corrupt the original snapshot object, and the current tests would not detect it. Add a test: (1) create a RangeTracker, add several ranges, call serialize() to get snapshot; (2) construct a new RangeTracker(snapshot) copy; (3) call copy.add(...) and copy.updateBase(...) on the copy; (4) assert snapshot.ranges is unchanged and the original tracker's values are unchanged. This verifies the deep-copy contract introduced by structuredClone.

View workflow run

ChumpChief and others added 2 commits May 15, 2026 09:33
PR feedback flagged that lodash.clonedeep@4.5.0 is unmaintained (last
published in 2017) and never received the security patches that lodash
proper got. RangeTracker.ranges is loaded from snapshot data, so a
crafted document could plausibly carry a __proto__ payload through
cloneDeep into Object.prototype.

structuredClone (Node >=17, modern browsers) is built in, dependency-
free, and safe for the plain {primary, secondary, length} objects that
IRange[] contains. Removing lodash.clonedeep also gets rid of one more
unmaintained third-party dep that would otherwise turn into a
recurring CG alert magnet on this deprecated package -- which is
exactly the maintenance cost this PR is trying to drive down.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Satisfies the npm-package-exports-apis-linted policy by adding per-entrypoint
api-extractor lint configs and check:exports scripts that match client-utils'
pattern. The previous check:release-tags script pointed at ./lib/index.d.ts,
which isn't in the exports map, so it provided no real coverage; replaced by
the new per-entrypoint flow.

Tagged the seven Browser-side exports (Uint8ArrayToString, stringToBuffer,
bufferToString, isArrayBuffer, IsoBuffer, hashFile, gitHashFile) as @internal
to mirror their Node counterparts and clear ae-missing-release-tag. No
api-report diff: the package's api-extractor.json extends no-legacy, so
@internal symbols are already excluded from the generated reports.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ChumpChief ChumpChief marked this pull request as ready for review May 15, 2026 17:01
@ChumpChief ChumpChief requested a review from a team as a code owner May 15, 2026 17:01
@ChumpChief ChumpChief requested review from Copilot and jason-ha May 15, 2026 17:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes the deprecated @fluidframework/common-utils workspace to align with the repo’s current build/lint/test and ESM/exports patterns (similar to client-utils), so future maintenance (tooling updates, CG responses, pipeline alignment) is cheaper and more uniform.

Changes:

  • Migrates to Node16 ESM-first TypeScript config and updates source/test imports to explicit .js extensions.
  • Reworks package entrypoints/exports to support proper self-import usage across Node/browser and ESM/CJS, plus adds export-validation and arethetypeswrong checks.
  • Updates build, lint, and test wiring (fluid-build tasks, eslint-config-fluid flat config, mocha/jest configs, refreshed devDependencies).

Reviewed changes

Copilot reviewed 40 out of 43 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
common/lib/common-utils/tsconfig.json Switches to Node16 ESM tsconfig base and adjusts output to lib/, relaxing a couple of strictness flags for deprecated code.
common/lib/common-utils/tsconfig.esnext.json Removes the old ESNext-specific tsconfig.
common/lib/common-utils/tsconfig.cjs.json Adds a dedicated CJS build tsconfig (for fluid-tsc commonjs) outputting to dist/.
common/lib/common-utils/src/trace.ts Updates import to .js extension (now ESM-style).
common/lib/common-utils/src/timer.ts Updates internal imports to .js extensions.
common/lib/common-utils/src/test/types/validateCommonUtilsPrevious.generated.ts Updates generated type-test import to package self-import and refreshes generator comment.
common/lib/common-utils/src/test/types/tsconfig.json Moves type-test build output to lib/ and aligns test tsconfig with Node16 test base.
common/lib/common-utils/src/test/mocha/typedEventEmitter.spec.ts Switches test imports to explicit entrypoint/module paths with .js.
common/lib/common-utils/src/test/mocha/tsconfig.json Aligns mocha test TS config with Node16 test base and outputs to lib/.
common/lib/common-utils/src/test/mocha/tsconfig.cjs.json Adds a CJS-specific mocha test build config emitting into dist/.
common/lib/common-utils/src/test/mocha/timer.spec.ts Switches test import to explicit index.js entrypoint with .js.
common/lib/common-utils/src/test/mocha/rangeTracker.spec.ts Switches test import to explicit index.js entrypoint with .js.
common/lib/common-utils/src/test/mocha/promiseCache.spec.ts Switches test import to explicit index.js entrypoint with .js.
common/lib/common-utils/src/test/mocha/idleTaskScheduler.spec.ts Switches test import to explicit module path with .js.
common/lib/common-utils/src/test/mocha/assert.spec.ts Switches test import to explicit module path with .js.
common/lib/common-utils/src/test/jest/tsconfig.cjs.json Aligns jest test compilation config with Node16 test base and references the package CJS build config.
common/lib/common-utils/src/test/jest/gitHash.spec.ts Switches test import to explicit module path with .js.
common/lib/common-utils/src/test/jest/buffer.spec.ts Switches test imports to explicit module paths with .js.
common/lib/common-utils/src/rangeTracker.ts Replaces lodash cloneDeep usage with structuredClone.
common/lib/common-utils/src/indexNode.ts Builds a complete Node entrypoint surface via explicit re-exports and adds @packageDocumentation.
common/lib/common-utils/src/indexBrowser.ts Builds a complete browser entrypoint surface via explicit re-exports and adds @packageDocumentation.
common/lib/common-utils/src/index.ts Converts index.ts into a stub re-export for tooling compatibility.
common/lib/common-utils/src/hashFileNode.ts Updates sha.js import style and .js extension on internal type import.
common/lib/common-utils/src/hashFileBrowser.ts Updates imports/paths for ESM .js extensions; adds @internal tags to deprecated APIs.
common/lib/common-utils/src/bufferBrowser.ts Adds @internal tags to deprecated browser buffer utilities.
common/lib/common-utils/src/base64Encoding.ts Updates import path to ESM .js extension.
common/lib/common-utils/pnpm-workspace.yaml Adds trust-policy exclusions with rationale for specific transitive versions.
common/lib/common-utils/package.json Converts package to ESM (type: module), adds conditional exports (node/default + import/require + types), updates scripts, and refreshes deps/devDeps.
common/lib/common-utils/jest.config.cjs Adds a CJS jest config (puppeteer preset + junit reporter).
common/lib/common-utils/jest-puppeteer.config.cjs Adds a CJS jest-puppeteer launch config for CI environments.
common/lib/common-utils/eslint.config.mts Replaces bespoke flat config with @fluidframework/eslint-config-fluid and customizes TS parser project list + disables for existing deprecated violations.
common/lib/common-utils/api-report/common-utils.public.api.md Updates generated API report content (now indicates missing package documentation for the configured entrypoint).
common/lib/common-utils/api-report/common-utils.beta.api.md Updates generated API report content (now indicates missing package documentation for the configured entrypoint).
common/lib/common-utils/api-report/common-utils.alpha.api.md Updates generated API report content (now indicates missing package documentation for the configured entrypoint).
common/lib/common-utils/api-extractor/api-extractor-lint-indexNode.esm.json Adds API-extractor lint config for the Node ESM entrypoint.
common/lib/common-utils/api-extractor/api-extractor-lint-indexNode.cjs.json Adds API-extractor lint config for the Node CJS entrypoint.
common/lib/common-utils/api-extractor/api-extractor-lint-indexBrowser.esm.json Adds API-extractor lint config for the browser ESM entrypoint.
common/lib/common-utils/api-extractor/api-extractor-lint-indexBrowser.cjs.json Adds API-extractor lint config for the browser CJS entrypoint.
common/lib/common-utils/api-extractor/api-extractor-lint-bundle.json Adds API-extractor lint config for the “bundle” entrypoint.
common/lib/common-utils/api-extractor.json Switches API Extractor base config to the ESM (no-legacy) base.
common/lib/common-utils/api-extractor-lint.json Removes the old single-entrypoint API-extractor lint config.
common/lib/common-utils/.mocharc.cjs Adds mocha config via @fluid-internal/mocha-test-setup.

Comment thread common/lib/common-utils/src/base64Encoding.ts Outdated
Comment thread common/lib/common-utils/src/trace.ts Outdated
Comment thread common/lib/common-utils/src/index.ts Outdated
@ChumpChief ChumpChief requested a review from MarioJGMsoft May 15, 2026 17:18
ChumpChief and others added 2 commits May 15, 2026 10:23
base64Encoding.ts and trace.ts both imported through ./indexNode.js, which
worked under the legacy 'browser' field that remapped indexNode to
indexBrowser at bundle time. With this PR's switch to conditional exports,
that browser field is gone and those relative imports always resolve to the
Node entrypoint at runtime, dragging Node-only modules (sha.js, the Node
Buffer surface, etc.) into browser bundles.

* Split base64Encoding.ts into base64EncodingBrowser.ts and
  base64EncodingNode.ts, each importing IsoBuffer from the platform-correct
  buffer module. Mirrors the client-utils pattern. indexBrowser/indexNode
  re-export from the matching half.
* trace.ts now imports performance directly from ./performanceIsomorphic.js,
  which is already platform-isomorphic and avoids the entrypoint round-trip
  entirely.

No public API change (no api-report diff). All tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a @packageDocumentation comment to the three index files
(index.ts, indexBrowser.ts, indexNode.ts) and notes that the package
is deprecated in favor of @fluid-internal/client-utils. Clears the
'(No @packageDocumentation comment for this package)' line from the
generated api-reports.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@kian-thompson kian-thompson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed via Copilot CLI (deep mode). Verdict: Approve with suggestions — 0 CRITICAL, 0 HIGH, 4 MEDIUM.

Findings

Sev Area File What Fix
🟡 API Quality / Correctness src/indexNode.ts:17 Buffer re-export changed from value+type to export { type Buffer }. Any import { Buffer } from "@fluidframework/common-utils"; new Buffer(...) will fail at runtime. No in-repo consumers and the package is @internal+@deprecated, so likely acceptable, but it isn't called out in the PR description as a behavior change. Re-export Buffer as a value from ./bufferNode.js, or explicitly note the symbol removal in the commit (common-utils: stop pulling Node graph into browser builds) and PR description.
🟡 API Quality src/base64EncodingBrowser.ts:15,25,37 (also pre-existing in bufferBrowser.ts, hashFileBrowser.ts, bufferNode.ts, etc.) New file's @deprecated tags point at @fluidframework-internal/client-utils, which is not a real package. The real one (correctly used in the new @packageDocumentation block in index*.ts) is @fluid-internal/client-utils. src/timer.ts:246,263 uses yet a third name (@fluid-private/client-utils). IntelliSense steers users to nonexistent packages. At minimum fix the three new occurrences in base64EncodingBrowser.ts. Ideally do a sweep across the package: @fluidframework-internal/client-utils and @fluid-private/client-utils@fluid-internal/client-utils.
🟡 Architecture src/index.ts:15 Comment says "THIS FILE IS NOT ACTUALLY USED", but it remains the api-extractor mainEntryPointFilePath (inherited as <projectFolder>/lib/index.d.ts from api-extractor-base.esm.json, no override in api-extractor.json) and the type-test path validateCommonUtilsPrevious.generated.ts resolves @fluidframework/common-utils through the exports map. The comment will mislead future maintainers. Update the comment to acknowledge the api-extractor + type-test role, or override mainEntryPointFilePath in api-extractor.json to point at a real shipped entrypoint (e.g. lib/indexNode.d.ts).
🟡 Correctness package.json:39-40 Legacy fallback main: "lib/indexBrowser.js" / types: "lib/indexBrowser.d.ts" while "type": "module". Resolvers that don't honor exports (older bundlers / legacy tooling) will try to require() ESM → ERR_REQUIRE_ESM. Previously these pointed at the CJS dist/ build. Either drop the legacy fields (exports-only) or point them at the CJS bundle (./dist/indexBrowser.js / ./dist/indexBrowser.d.ts).

Suggestions (non-blocking)

  • The tsc script copies common/build/build-common/src/cjs/package.json into ./dist for CJS subpath resolution. Worth a quick parity check against client-utils to confirm both packages use the same mechanism.
  • c8 config in package.json still only includes dist/**/*.*js / dist/test/**/*.*js. With the dual ESM/CJS layout, coverage now misses lib/**. Consider broadening if coverage parity matters.

Copy link
Copy Markdown
Contributor

@jason-ha jason-ha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main question is switch to ESM and whether instead ESM support should just be removed. More detailed ramblings in sub-issues.

Comment thread common/lib/common-utils/package.json Outdated
Comment thread common/lib/common-utils/package.json Outdated
Comment on lines +39 to +40
"main": "lib/indexBrowser.js",
"types": "lib/indexBrowser.d.ts",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not switch this around now since it is deprecated but keep with the same to reduce any risk.
In fact, if the ESM build was invalid which I have commonly found recently for packages that weren't dealt with in the large client migration, then it feels safe to drop the ESM build.
Node16 cleanup is good but leave out ESM.
"Restore" "type": "commonjs" (that was implicit before).
If there is a need for valid ESM build, then make ESM secondary using fluid-tsc module.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to CJS primary in latest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't actually switch to CJS primary for consumers in case that is what you are hoping for.
Once exports are added, then these properties are ignored by modern things.
So, as it stands ESM consumers will get ESM where they would have gotten CJS before.
I don't think we should add ESM here. Can discuss more next week.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped ESM in latest - my main goal was to align with other packages as best we could (so by default, dual-building like most do) but the ESM build isn't crucial to most of the gains (aligning the dependencies, build tools, etc.).

Comment thread common/lib/common-utils/src/index.ts Outdated
Comment thread common/lib/common-utils/package.json
Comment thread common/lib/common-utils/package.json Outdated
Comment thread common/lib/common-utils/package.json
Comment thread common/lib/common-utils/api-extractor.json Outdated
…ary)

This deprecated package previously had "type": "module" with ESM as
the primary output in lib/ and a copied dist/package.json overriding to
"commonjs" for the CJS build. Per PR feedback, restore the historic
implicit "type": "commonjs" identity while keeping the ESM build as a
secondary output. lib/package.json now carries the "type":"module"
override (instead of dist/package.json carrying "type":"commonjs").

This also resolves the legacy main/types fields pointing at ESM under
"type": "module" — they now match the new package type and point at
the CJS build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also updated client-utils based on feedback that equally applied there.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 57 changed files in this pull request and generated no new comments.

Comment thread common/lib/common-utils/api-report/common-utils.legacy.beta.api.md Outdated
This deprecated package has no @legacy-tagged exports, so the legacy
api-extractor rollups produced empty report files. Remove the dead
machinery:

- Drop --outFileLegacyBeta flag from flub generate entrypoints
- Remove ./internal-api-report/legacy from exports map
- Remove 6 legacy scripts (build:api-reports:*:legacy,
  ci:build:api-reports:*:legacy, check:exports:esm:*:legacy)
- Remove 4 legacy api-extractor configs
- Remove 2 empty legacy api-report files
- Trim attw --exclude-entrypoints

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ChumpChief ChumpChief marked this pull request as draft May 21, 2026 22:02
@ChumpChief ChumpChief marked this pull request as ready for review May 21, 2026 22:02
GitHub PR record was stuck on a stale head_sha; this empty commit nudges
the back-end webhook to re-sync.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 51 changed files in this pull request and generated 2 comments.

Comment thread common/lib/common-utils/package.json Outdated
Comment thread common/lib/common-utils/src/test/types/tsconfig.json
With the package now "type": "commonjs", the typetests' self-import of
@fluidframework/common-utils resolves via the exports map's "require"
condition to dist/*.d.ts (the CJS build) rather than lib/*.d.ts (the
ESM build). Update the fluidBuild dependency from build:esnext to tsc
so a standalone "npm run build:test:types" has its prerequisites built.

Also update the test tsconfig project reference from the root tsconfig
(ESM) to tsconfig.cjs.json (CJS) to match what build:test:types actually
consumes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 51 changed files in this pull request and generated 1 comment.

Comment thread common/lib/common-utils/pnpm-workspace.yaml
Copy link
Copy Markdown
Contributor

@jason-ha jason-ha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

partial (quick) re-review

Comment thread packages/common/client-utils/package.json Outdated
ChumpChief and others added 2 commits May 21, 2026 18:38
Revert "common-utils, client-utils: drop redundant "types" entries from "." export" (8da24e6). Per jason-ha's PR feedback, the typetest generator's custom resolution path (`getTypesPathFromPackage`) currently has a bug that requires explicit "types" entries; the failure only manifests post-publish when downstream packages consume the prior version. Carry the entries until the build-tools fix lands.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per PR feedback (jason-ha), the dual ESM/CJS emit didn't actually make the
package CJS-primary for consumers — the exports map's `import` condition
still routed modern ESM consumers to ESM. Drop the ESM build entirely:

- Remove `build:esnext`, the `lib/` output, and the `import` conditions in
  the exports map.
- Drop the ESM-only mocha test variant; mocha now only runs against CJS.
- Delete the ESM-only api-extractor lint configs.
- Drop the `./internal-api-report/current` export (no in-repo consumers)
  and the `build:api-reports*` machinery that produced the now-removed
  `api-report/common-utils.*.api.md` files; consistent with the legacy
  report cleanup in 83a6939. `build:docs` is kept and repointed to
  `dist/`.
- Collapse `tsconfig.cjs.json` into `tsconfig.json` (single CJS config) and
  do the same for the test configs. Replace `fluid-tsc commonjs` calls with
  plain `tsc` since `"type": "commonjs"` is now the default.

attw still passes 🟢 across all four resolution modes; the node16-from-ESM
column now reports `(CJS)` instead of `(ESM)`, which is the point of the
change. common-utils is deprecated; client-utils is the modern dual-emit
replacement and is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 47 changed files in this pull request and generated no new comments.

Forgotten in a1835f3. CI rejects --frozen-lockfile installs when the
lockfile doesn't match package.json.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  288859 links
    1925 destination URLs
    2175 URLs ignored
       0 warnings
       0 errors


@ChumpChief ChumpChief requested a review from jason-ha May 22, 2026 03:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants