diff --git a/README.md b/README.md index c75a4436ce1..5fbf77f7665 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/heft-plugins/heft-webpack4-plugin](./heft-plugins/heft-webpack4-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fheft-webpack4-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Fheft-webpack4-plugin) | [changelog](./heft-plugins/heft-webpack4-plugin/CHANGELOG.md) | [@rushstack/heft-webpack4-plugin](https://www.npmjs.com/package/@rushstack/heft-webpack4-plugin) | | [/heft-plugins/heft-webpack5-plugin](./heft-plugins/heft-webpack5-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fheft-webpack5-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Fheft-webpack5-plugin) | [changelog](./heft-plugins/heft-webpack5-plugin/CHANGELOG.md) | [@rushstack/heft-webpack5-plugin](https://www.npmjs.com/package/@rushstack/heft-webpack5-plugin) | | [/libraries/api-extractor-model](./libraries/api-extractor-model/) | [![npm version](https://badge.fury.io/js/%40microsoft%2Fapi-extractor-model.svg)](https://badge.fury.io/js/%40microsoft%2Fapi-extractor-model) | [changelog](./libraries/api-extractor-model/CHANGELOG.md) | [@microsoft/api-extractor-model](https://www.npmjs.com/package/@microsoft/api-extractor-model) | +| [/libraries/credential-cache](./libraries/credential-cache/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fcredential-cache.svg)](https://badge.fury.io/js/%40rushstack%2Fcredential-cache) | [changelog](./libraries/credential-cache/CHANGELOG.md) | [@rushstack/credential-cache](https://www.npmjs.com/package/@rushstack/credential-cache) | | [/libraries/debug-certificate-manager](./libraries/debug-certificate-manager/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fdebug-certificate-manager.svg)](https://badge.fury.io/js/%40rushstack%2Fdebug-certificate-manager) | [changelog](./libraries/debug-certificate-manager/CHANGELOG.md) | [@rushstack/debug-certificate-manager](https://www.npmjs.com/package/@rushstack/debug-certificate-manager) | | [/libraries/heft-config-file](./libraries/heft-config-file/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fheft-config-file.svg)](https://badge.fury.io/js/%40rushstack%2Fheft-config-file) | [changelog](./libraries/heft-config-file/CHANGELOG.md) | [@rushstack/heft-config-file](https://www.npmjs.com/package/@rushstack/heft-config-file) | | [/libraries/load-themed-styles](./libraries/load-themed-styles/) | [![npm version](https://badge.fury.io/js/%40microsoft%2Fload-themed-styles.svg)](https://badge.fury.io/js/%40microsoft%2Fload-themed-styles) | [changelog](./libraries/load-themed-styles/CHANGELOG.md) | [@microsoft/load-themed-styles](https://www.npmjs.com/package/@microsoft/load-themed-styles) | diff --git a/common/changes/@microsoft/rush/extract-credential-cache_2025-10-22-01-44.json b/common/changes/@microsoft/rush/extract-credential-cache_2025-10-22-01-44.json new file mode 100644 index 00000000000..dc9077e574f --- /dev/null +++ b/common/changes/@microsoft/rush/extract-credential-cache_2025-10-22-01-44.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Extract CredentialCache API out into \"@rushstack/credential-cache\". Reference directly in plugins to avoid pulling in all of \"@rushstack/rush-sdk\" unless necessary.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@rushstack/credential-cache/extract-credential-cache_2025-10-22-01-44.json b/common/changes/@rushstack/credential-cache/extract-credential-cache_2025-10-22-01-44.json new file mode 100644 index 00000000000..2a2ea4de55e --- /dev/null +++ b/common/changes/@rushstack/credential-cache/extract-credential-cache_2025-10-22-01-44.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/credential-cache", + "comment": "Create dedicated package for the Rush \"CredentialCache\" API. This API manages credential persistence on the local machine.", + "type": "minor" + } + ], + "packageName": "@rushstack/credential-cache" +} \ No newline at end of file diff --git a/common/changes/@rushstack/node-core-library/extract-credential-cache_2025-10-22-01-44.json b/common/changes/@rushstack/node-core-library/extract-credential-cache_2025-10-22-01-44.json new file mode 100644 index 00000000000..4e750208dbe --- /dev/null +++ b/common/changes/@rushstack/node-core-library/extract-credential-cache_2025-10-22-01-44.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/node-core-library", + "comment": "Add \"Objects.areDeepEqual\" and \"User.getHomeFolder\" APIs.", + "type": "minor" + } + ], + "packageName": "@rushstack/node-core-library" +} \ No newline at end of file diff --git a/common/config/rush/nonbrowser-approved-packages.json b/common/config/rush/nonbrowser-approved-packages.json index 90bf63302d7..161327d854d 100644 --- a/common/config/rush/nonbrowser-approved-packages.json +++ b/common/config/rush/nonbrowser-approved-packages.json @@ -130,6 +130,10 @@ "name": "@rspack/dev-server", "allowedCategories": [ "libraries" ] }, + { + "name": "@rushstack/credential-cache", + "allowedCategories": [ "libraries" ] + }, { "name": "@rushstack/debug-certificate-manager", "allowedCategories": [ "libraries", "vscode-extensions" ] diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index 0092b867cf9..87c86fc3784 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -111,10 +111,10 @@ importers: version: file:../../../apps/heft(@types/node@20.17.19) '@rushstack/heft-lint-plugin': specifier: file:../../heft-plugins/heft-lint-plugin - version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19) + version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19) '@rushstack/heft-typescript-plugin': specifier: file:../../heft-plugins/heft-typescript-plugin - version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19) + version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19) eslint: specifier: ~9.25.1 version: 9.25.1 @@ -7842,7 +7842,7 @@ packages: - supports-color dev: true - file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19): + file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19): resolution: {directory: ../../../heft-plugins/heft-api-extractor-plugin, type: directory} id: file:../../../heft-plugins/heft-api-extractor-plugin name: '@rushstack/heft-api-extractor-plugin' @@ -7856,7 +7856,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19)(jest-environment-node@29.5.0): + file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19)(jest-environment-node@29.5.0): resolution: {directory: ../../../heft-plugins/heft-jest-plugin, type: directory} id: file:../../../heft-plugins/heft-jest-plugin name: '@rushstack/heft-jest-plugin' @@ -7891,7 +7891,7 @@ packages: - ts-node dev: true - file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19): + file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19): resolution: {directory: ../../../heft-plugins/heft-lint-plugin, type: directory} id: file:../../../heft-plugins/heft-lint-plugin name: '@rushstack/heft-lint-plugin' @@ -7906,7 +7906,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19): + file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19): resolution: {directory: ../../../heft-plugins/heft-typescript-plugin, type: directory} id: file:../../../heft-plugins/heft-typescript-plugin name: '@rushstack/heft-typescript-plugin' @@ -7935,6 +7935,15 @@ packages: - '@types/node' dev: true + file:../../../libraries/credential-cache(@types/node@20.17.19): + resolution: {directory: ../../../libraries/credential-cache, type: directory} + id: file:../../../libraries/credential-cache + name: '@rushstack/credential-cache' + dependencies: + '@rushstack/node-core-library': file:../../../libraries/node-core-library(@types/node@20.17.19) + transitivePeerDependencies: + - '@types/node' + file:../../../libraries/heft-config-file(@types/node@20.17.19): resolution: {directory: ../../../libraries/heft-config-file, type: directory} id: file:../../../libraries/heft-config-file @@ -8062,6 +8071,7 @@ packages: '@pnpm/dependency-path-lockfile-pre-v10': /@pnpm/dependency-path@5.1.7 '@pnpm/dependency-path-lockfile-pre-v9': /@pnpm/dependency-path@2.1.8 '@pnpm/link-bins': 5.3.25 + '@rushstack/credential-cache': file:../../../libraries/credential-cache(@types/node@20.17.19) '@rushstack/heft-config-file': file:../../../libraries/heft-config-file(@types/node@20.17.19) '@rushstack/lookup-by-path': file:../../../libraries/lookup-by-path(@types/node@20.17.19) '@rushstack/node-core-library': file:../../../libraries/node-core-library(@types/node@20.17.19) @@ -8109,6 +8119,7 @@ packages: name: '@rushstack/rush-sdk' dependencies: '@pnpm/lockfile.types': 1.0.3 + '@rushstack/credential-cache': file:../../../libraries/credential-cache(@types/node@20.17.19) '@rushstack/lookup-by-path': file:../../../libraries/lookup-by-path(@types/node@20.17.19) '@rushstack/node-core-library': file:../../../libraries/node-core-library(@types/node@20.17.19) '@rushstack/package-deps-hash': file:../../../libraries/package-deps-hash(@types/node@20.17.19) @@ -8160,7 +8171,7 @@ packages: transitivePeerDependencies: - '@types/node' - file:../../../rigs/heft-node-rig(@rushstack/heft@1.1.1)(@types/node@20.17.19): + file:../../../rigs/heft-node-rig(@rushstack/heft@1.1.2)(@types/node@20.17.19): resolution: {directory: ../../../rigs/heft-node-rig, type: directory} id: file:../../../rigs/heft-node-rig name: '@rushstack/heft-node-rig' @@ -8170,10 +8181,10 @@ packages: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@20.17.19) '@rushstack/eslint-config': file:../../../eslint/eslint-config(eslint@9.37.0)(typescript@5.8.2) '@rushstack/heft': file:../../../apps/heft(@types/node@20.17.19) - '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19) - '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19)(jest-environment-node@29.5.0) - '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19) - '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@1.1.1)(@types/node@20.17.19) + '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19) + '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19)(jest-environment-node@29.5.0) + '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19) + '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@1.1.2)(@types/node@20.17.19) '@types/heft-jest': 1.0.1 eslint: 9.37.0 jest-environment-node: 29.5.0 @@ -8195,7 +8206,7 @@ packages: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@20.17.19) '@rushstack/eslint-patch': file:../../../eslint/eslint-patch '@rushstack/heft': file:../../../apps/heft(@types/node@20.17.19) - '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@1.1.1)(@types/node@20.17.19) + '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@1.1.2)(@types/node@20.17.19) '@types/heft-jest': 1.0.1 '@types/node': 20.17.19 eslint: 9.37.0 diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index 1e915afede2..20cd48a9b6b 100644 --- a/common/config/subspaces/build-tests-subspace/repo-state.json +++ b/common/config/subspaces/build-tests-subspace/repo-state.json @@ -1,6 +1,6 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "1dc2b69423ca353582cec8716410a0b8a8210aff", + "pnpmShrinkwrapHash": "c9e998d8d98c587075f2380a11aa0816aecfd2fc", "preferredVersionsHash": "550b4cee0bef4e97db6c6aad726df5149d20e7d9", - "packageJsonInjectedDependenciesHash": "7bf222162008849ca931709cbaa90632d56766da" + "packageJsonInjectedDependenciesHash": "9c50330f156d5d74c5dd1fb2e3aa6b9c2a2403c7" } diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 9465e8c01d2..1c54bb7c298 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -3365,6 +3365,22 @@ importers: specifier: ~9.37.0 version: 9.37.0(supports-color@8.1.1) + ../../../libraries/credential-cache: + dependencies: + '@rushstack/node-core-library': + specifier: workspace:* + version: link:../node-core-library + devDependencies: + '@rushstack/heft': + specifier: workspace:* + version: link:../../apps/heft + eslint: + specifier: ~9.37.0 + version: 9.37.0(supports-color@8.1.1) + local-node-rig: + specifier: workspace:* + version: link:../../rigs/local-node-rig + ../../../libraries/debug-certificate-manager: dependencies: '@rushstack/node-core-library': @@ -3737,6 +3753,9 @@ importers: '@pnpm/link-bins': specifier: ~5.3.7 version: 5.3.25 + '@rushstack/credential-cache': + specifier: workspace:* + version: link:../credential-cache '@rushstack/heft-config-file': specifier: workspace:* version: link:../heft-config-file @@ -3909,6 +3928,9 @@ importers: '@pnpm/lockfile.types': specifier: ~1.0.3 version: 1.0.3 + '@rushstack/credential-cache': + specifier: workspace:* + version: link:../credential-cache '@rushstack/lookup-by-path': specifier: workspace:* version: link:../lookup-by-path @@ -4497,6 +4519,9 @@ importers: ../../../rush-plugins/rush-amazon-s3-build-cache-plugin: dependencies: + '@rushstack/credential-cache': + specifier: workspace:* + version: link:../../libraries/credential-cache '@rushstack/node-core-library': specifier: workspace:* version: link:../../libraries/node-core-library @@ -4531,6 +4556,9 @@ importers: '@azure/storage-blob': specifier: ~12.26.0 version: 12.26.0 + '@rushstack/credential-cache': + specifier: workspace:* + version: link:../../libraries/credential-cache '@rushstack/node-core-library': specifier: workspace:* version: link:../../libraries/node-core-library @@ -4609,6 +4637,9 @@ importers: ../../../rush-plugins/rush-http-build-cache-plugin: dependencies: + '@rushstack/credential-cache': + specifier: workspace:* + version: link:../../libraries/credential-cache '@rushstack/node-core-library': specifier: workspace:* version: link:../../libraries/node-core-library diff --git a/common/reviews/api/credential-cache.api.md b/common/reviews/api/credential-cache.api.md new file mode 100644 index 00000000000..f6563cc246a --- /dev/null +++ b/common/reviews/api/credential-cache.api.md @@ -0,0 +1,50 @@ +## API Report File for "@rushstack/credential-cache" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +// @public (undocumented) +export class CredentialCache implements Disposable { + // (undocumented) + [Symbol.dispose](): void; + // (undocumented) + deleteCacheEntry(cacheId: string): void; + // (undocumented) + dispose(): void; + // (undocumented) + static initializeAsync(options: ICredentialCacheOptions): Promise; + // (undocumented) + saveIfModifiedAsync(): Promise; + // (undocumented) + setCacheEntry(cacheId: string, entry: ICredentialCacheEntry): void; + // (undocumented) + trimExpiredEntries(): void; + // (undocumented) + tryGetCacheEntry(cacheId: string): ICredentialCacheEntry | undefined; + // (undocumented) + static usingAsync(options: ICredentialCacheOptions, doActionAsync: (credentialCache: CredentialCache) => Promise | void): Promise; +} + +// @public (undocumented) +export interface ICredentialCacheEntry { + // (undocumented) + credential: string; + // (undocumented) + credentialMetadata?: object; + // (undocumented) + expires?: Date; +} + +// @public (undocumented) +export interface ICredentialCacheOptions { + // (undocumented) + cacheFilePath?: string; + // (undocumented) + supportEditing: boolean; +} + +// @public +export const RUSH_USER_FOLDER_NAME: '.rush-user'; + +``` diff --git a/common/reviews/api/node-core-library.api.md b/common/reviews/api/node-core-library.api.md index 6133072d1c5..cc01c92c873 100644 --- a/common/reviews/api/node-core-library.api.md +++ b/common/reviews/api/node-core-library.api.md @@ -7,7 +7,7 @@ /// import * as child_process from 'node:child_process'; -import * as nodeFs from 'node:fs'; +import * as fs from 'node:fs'; import * as nodePath from 'node:path'; // @public @@ -24,6 +24,9 @@ export class AlreadyReportedError extends Error { constructor(); } +// @public +function areDeepEqual(a: TObject, b: TObject): boolean; + // @public export class Async { static forEachAsync(iterable: Iterable | AsyncIterable, callback: (entry: TEntry, arrayIndex: number) => Promise, options?: (IAsyncParallelismOptions & { @@ -58,6 +61,13 @@ export type Brand = T & { __brand: BrandTag; }; +declare namespace Disposables { + export { + polyfillDisposeSymbols + } +} +export { Disposables } + // @public export enum Encoding { // (undocumented) @@ -216,7 +226,7 @@ export type FileSystemCopyFilesAsyncFilter = (sourcePath: string, destinationPat export type FileSystemCopyFilesFilter = (sourcePath: string, destinationPath: string) => boolean; // @public -export type FileSystemStats = nodeFs.Stats; +export type FileSystemStats = fs.Stats; // @public export class FileWriter { @@ -234,7 +244,10 @@ export const FolderConstants: { }; // @public -export type FolderItem = nodeFs.Dirent; +export type FolderItem = fs.Dirent; + +// @public +function getHomeFolder(): string; // @public export interface IAsyncParallelismOptions { @@ -628,7 +641,7 @@ export interface IReadLinesFromIterableOptions { // @public export interface IRealNodeModulePathResolverOptions { // (undocumented) - fs?: Partial>; + fs?: Partial>; ignoreMissingPaths?: boolean; // (undocumented) path?: Partial>; @@ -791,6 +804,13 @@ export enum NewlineKind { OsDefault = "os" } +declare namespace Objects { + export { + areDeepEqual + } +} +export { Objects } + // @public export class PackageJsonLookup { constructor(parameters?: IPackageJsonLookupParameters); @@ -841,6 +861,9 @@ export class Path { static isUnderOrEqual(childPath: string, parentFolderPath: string): boolean; } +// @public +function polyfillDisposeSymbols(): void; + // @public export enum PosixModeBits { AllExecute = 73, @@ -932,4 +955,11 @@ export class TypeUuid { static registerClass(targetClass: any, typeUuid: string): void; } +declare namespace User { + export { + getHomeFolder + } +} +export { User } + ``` diff --git a/common/reviews/api/rush-azure-storage-build-cache-plugin.api.md b/common/reviews/api/rush-azure-storage-build-cache-plugin.api.md index 4bd893d8be2..26f39c4993a 100644 --- a/common/reviews/api/rush-azure-storage-build-cache-plugin.api.md +++ b/common/reviews/api/rush-azure-storage-build-cache-plugin.api.md @@ -5,9 +5,9 @@ ```ts import { AzureAuthorityHosts } from '@azure/identity'; -import { CredentialCache } from '@rushstack/rush-sdk'; +import { CredentialCache } from '@rushstack/credential-cache'; import { DeviceCodeCredentialOptions } from '@azure/identity'; -import type { ICredentialCacheEntry } from '@rushstack/rush-sdk'; +import type { ICredentialCacheEntry } from '@rushstack/credential-cache'; import { InteractiveBrowserCredentialNodeOptions } from '@azure/identity'; import type { IRushPlugin } from '@rushstack/rush-sdk'; import type { ITerminal } from '@rushstack/terminal'; diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 3fa9d1b178b..d6804daab7e 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -13,7 +13,10 @@ import { AsyncSeriesWaterfallHook } from 'tapable'; import type { CollatedWriter } from '@rushstack/stream-collator'; import type { CommandLineParameter } from '@rushstack/ts-command-line'; import { CommandLineParameterKind } from '@rushstack/ts-command-line'; +import { CredentialCache } from '@rushstack/credential-cache'; import { HookMap } from 'tapable'; +import { ICredentialCacheEntry } from '@rushstack/credential-cache'; +import { ICredentialCacheOptions } from '@rushstack/credential-cache'; import { IFileDiffStatus } from '@rushstack/package-deps-hash'; import { IPackageJson } from '@rushstack/node-core-library'; import { IPrefixMatch } from '@rushstack/lookup-by-path'; @@ -139,25 +142,7 @@ export class CommonVersionsConfiguration { save(): boolean; } -// @beta (undocumented) -export class CredentialCache { - // (undocumented) - deleteCacheEntry(cacheId: string): void; - // (undocumented) - dispose(): void; - // (undocumented) - static initializeAsync(options: ICredentialCacheOptions): Promise; - // (undocumented) - saveIfModifiedAsync(): Promise; - // (undocumented) - setCacheEntry(cacheId: string, entry: ICredentialCacheEntry): void; - // (undocumented) - trimExpiredEntries(): void; - // (undocumented) - tryGetCacheEntry(cacheId: string): ICredentialCacheEntry | undefined; - // (undocumented) - static usingAsync(options: ICredentialCacheOptions, doActionAsync: (credentialCache: CredentialCache) => Promise | void): Promise; -} +export { CredentialCache } // @beta export enum CustomTipId { @@ -433,22 +418,9 @@ export interface ICreateOperationsContext { readonly rushConfiguration: RushConfiguration; } -// @beta (undocumented) -export interface ICredentialCacheEntry { - // (undocumented) - credential: string; - // (undocumented) - credentialMetadata?: object; - // (undocumented) - expires?: Date; -} +export { ICredentialCacheEntry } -// @beta (undocumented) -export interface ICredentialCacheOptions { - cacheFilePath?: string; - // (undocumented) - supportEditing: boolean; -} +export { ICredentialCacheOptions } // @beta export interface ICustomTipInfo { diff --git a/libraries/credential-cache/.npmignore b/libraries/credential-cache/.npmignore new file mode 100644 index 00000000000..bc349f9a4be --- /dev/null +++ b/libraries/credential-cache/.npmignore @@ -0,0 +1,32 @@ +# THIS IS A STANDARD TEMPLATE FOR .npmignore FILES IN THIS REPO. + +# Ignore all files by default, to avoid accidentally publishing unintended files. +* + +# Use negative patterns to bring back the specific things we want to publish. +!/bin/** +!/lib/** +!/lib-*/** +!/dist/** + +!CHANGELOG.md +!CHANGELOG.json +!heft-plugin.json +!rush-plugin-manifest.json +!ThirdPartyNotice.txt + +# Ignore certain patterns that should not get published. +/dist/*.stats.* +/lib/**/test/ +/lib-*/**/test/ +*.test.js + +# NOTE: These don't need to be specified, because NPM includes them automatically. +# +# package.json +# README.md +# LICENSE + +# --------------------------------------------------------------------------- +# DO NOT MODIFY ABOVE THIS LINE! Add any project-specific overrides below. +# --------------------------------------------------------------------------- diff --git a/libraries/credential-cache/LICENSE b/libraries/credential-cache/LICENSE new file mode 100644 index 00000000000..a8687a8508f --- /dev/null +++ b/libraries/credential-cache/LICENSE @@ -0,0 +1,24 @@ +@rushstack/credential-cache + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libraries/credential-cache/README.md b/libraries/credential-cache/README.md new file mode 100644 index 00000000000..72a9d9393cb --- /dev/null +++ b/libraries/credential-cache/README.md @@ -0,0 +1,18 @@ +# @rushstack/credential-cache + +## Installation + +`npm install @rushstack/credential-cache --save-dev` + +## Overview + +This package manages persistent credentials on the local machine. Since these credentials are stored unencrypted, do not use for secure credentials. + +## Links + +- [CHANGELOG.md]( + https://github.com/microsoft/rushstack/blob/main/libraries/credential-cache/CHANGELOG.md) - Find + out what's new in the latest version +- [API Reference](https://api.rushstack.io/pages/credential-cache/) + +**@rushstack/credential-cache** is part of the [Rush Stack](https://rushstack.io/) family of projects. diff --git a/libraries/credential-cache/config/api-extractor.json b/libraries/credential-cache/config/api-extractor.json new file mode 100644 index 00000000000..996e271d3dd --- /dev/null +++ b/libraries/credential-cache/config/api-extractor.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + "mainEntryPointFilePath": "/lib/index.d.ts", + + "apiReport": { + "enabled": true, + "reportFolder": "../../../common/reviews/api" + }, + + "docModel": { + "enabled": true, + "apiJsonFilePath": "../../../common/temp/api/.api.json" + }, + + "dtsRollup": { + "enabled": true + } +} diff --git a/libraries/credential-cache/config/rig.json b/libraries/credential-cache/config/rig.json new file mode 100644 index 00000000000..165ffb001f5 --- /dev/null +++ b/libraries/credential-cache/config/rig.json @@ -0,0 +1,7 @@ +{ + // The "rig.json" file directs tools to look for their config files in an external package. + // Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + "rigPackageName": "local-node-rig" +} diff --git a/libraries/credential-cache/eslint.config.js b/libraries/credential-cache/eslint.config.js new file mode 100644 index 00000000000..c15e6077310 --- /dev/null +++ b/libraries/credential-cache/eslint.config.js @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +const nodeTrustedToolProfile = require('local-node-rig/profiles/default/includes/eslint/flat/profile/node-trusted-tool'); +const friendlyLocalsMixin = require('local-node-rig/profiles/default/includes/eslint/flat/mixins/friendly-locals'); + +module.exports = [ + ...nodeTrustedToolProfile, + ...friendlyLocalsMixin, + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parserOptions: { + tsconfigRootDir: __dirname + } + } + } +]; diff --git a/libraries/credential-cache/package.json b/libraries/credential-cache/package.json new file mode 100644 index 00000000000..b8465bba555 --- /dev/null +++ b/libraries/credential-cache/package.json @@ -0,0 +1,26 @@ +{ + "name": "@rushstack/credential-cache", + "version": "0.0.0", + "description": "Cross-platform functionality to manage cached credentials.", + "main": "lib/index.js", + "typings": "dist/credential-cache.d.ts", + "license": "MIT", + "repository": { + "url": "https://github.com/microsoft/rushstack.git", + "type": "git", + "directory": "libraries/credential-cache" + }, + "scripts": { + "build": "heft build --clean", + "_phase:build": "heft run --only build -- --clean", + "_phase:test": "heft run --only test -- --clean" + }, + "dependencies": { + "@rushstack/node-core-library": "workspace:*" + }, + "devDependencies": { + "@rushstack/heft": "workspace:*", + "eslint": "~9.37.0", + "local-node-rig": "workspace:*" + } +} diff --git a/libraries/rush-lib/src/logic/CredentialCache.ts b/libraries/credential-cache/src/CredentialCache.ts similarity index 85% rename from libraries/rush-lib/src/logic/CredentialCache.ts rename to libraries/credential-cache/src/CredentialCache.ts index 0e52c9ad52c..bac9378e7a9 100644 --- a/libraries/rush-lib/src/logic/CredentialCache.ts +++ b/libraries/credential-cache/src/CredentialCache.ts @@ -3,12 +3,26 @@ import * as path from 'node:path'; -import { FileSystem, JsonFile, JsonSchema, LockFile } from '@rushstack/node-core-library'; +import { + Disposables, + FileSystem, + JsonFile, + JsonSchema, + LockFile, + User, + Objects +} from '@rushstack/node-core-library'; -import { Utilities } from '../utilities/Utilities'; -import { RushUserConfiguration } from '../api/RushUserConfiguration'; -import schemaJson from '../schemas/credentials.schema.json'; -import { objectsAreDeepEqual } from '../utilities/objectUtilities'; +import schemaJson from './schemas/credentials.schema.json'; + +// Polyfill for node 18 +Disposables.polyfillDisposeSymbols(); + +/** + * The name of the default folder in the user's home directory where Rush stores user-specific data. + * @public + */ +export const RUSH_USER_FOLDER_NAME: '.rush-user' = '.rush-user'; const DEFAULT_CACHE_FILENAME: 'credentials.json' = 'credentials.json'; const LATEST_CREDENTIALS_JSON_VERSION: string = '0.1.0'; @@ -27,7 +41,7 @@ interface ICacheEntryJson { } /** - * @beta + * @public */ export interface ICredentialCacheEntry { expires?: Date; @@ -36,25 +50,22 @@ export interface ICredentialCacheEntry { } /** - * @beta + * @public */ export interface ICredentialCacheOptions { supportEditing: boolean; - /** - * If specified, use the specified path instead of the default path of `~/.rush-user/credentials.json` - */ cacheFilePath?: string; } /** - * @beta + * @public */ -export class CredentialCache /* implements IDisposable */ { +export class CredentialCache implements Disposable { private readonly _cacheFilePath: string; private readonly _cacheEntries: Map; private _modified: boolean = false; private _disposed: boolean = false; - private _supportsEditing: boolean; + private readonly _supportsEditing: boolean; private readonly _lockfile: LockFile | undefined; private constructor( @@ -79,7 +90,7 @@ export class CredentialCache /* implements IDisposable */ { cacheDirectory = path.dirname(options.cacheFilePath); cacheFileName = options.cacheFilePath.slice(cacheDirectory.length + 1); } else { - cacheDirectory = RushUserConfiguration.getRushUserFolderPath(); + cacheDirectory = `${User.getHomeFolder()}/${RUSH_USER_FOLDER_NAME}`; cacheFileName = DEFAULT_CACHE_FILENAME; } const cacheFilePath: string = `${cacheDirectory}/${cacheFileName}`; @@ -108,7 +119,12 @@ export class CredentialCache /* implements IDisposable */ { options: ICredentialCacheOptions, doActionAsync: (credentialCache: CredentialCache) => Promise | void ): Promise { - await Utilities.usingAsync(async () => await CredentialCache.initializeAsync(options), doActionAsync); + const cache: CredentialCache = await CredentialCache.initializeAsync(options); + try { + await doActionAsync(cache); + } finally { + cache.dispose(); + } } public setCacheEntry(cacheId: string, entry: ICredentialCacheEntry): void { @@ -120,7 +136,7 @@ export class CredentialCache /* implements IDisposable */ { if ( existingCacheEntry?.credential !== credential || existingCacheEntry?.expires !== expiresMilliseconds || - !objectsAreDeepEqual(existingCacheEntry?.credentialMetadata, credentialMetadata) + !Objects.areDeepEqual(existingCacheEntry?.credentialMetadata, credentialMetadata) ) { this._modified = true; this._cacheEntries.set(cacheId, { @@ -192,6 +208,10 @@ export class CredentialCache /* implements IDisposable */ { } } + public [Symbol.dispose](): void { + this.dispose(); + } + public dispose(): void { this._lockfile?.release(); this._disposed = true; diff --git a/libraries/credential-cache/src/index.ts b/libraries/credential-cache/src/index.ts new file mode 100644 index 00000000000..00ce0172b53 --- /dev/null +++ b/libraries/credential-cache/src/index.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * This package is used to manage persistent, per-user cached credentials. + * + * @packageDocumentation + */ + +export { + CredentialCache, + type ICredentialCacheEntry, + type ICredentialCacheOptions, + RUSH_USER_FOLDER_NAME +} from './CredentialCache'; diff --git a/libraries/rush-lib/src/schemas/credentials.schema.json b/libraries/credential-cache/src/schemas/credentials.schema.json similarity index 100% rename from libraries/rush-lib/src/schemas/credentials.schema.json rename to libraries/credential-cache/src/schemas/credentials.schema.json diff --git a/libraries/credential-cache/src/test/CredentialCache.mock.ts b/libraries/credential-cache/src/test/CredentialCache.mock.ts new file mode 100644 index 00000000000..5a4bcc29188 --- /dev/null +++ b/libraries/credential-cache/src/test/CredentialCache.mock.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type * as GetHomeFolderModule from '@rushstack/node-core-library/lib/user/getHomeFolder'; + +export const mockGetHomeFolder: jest.MockedFunction = jest.fn(); +jest.mock('@rushstack/node-core-library/lib/user/getHomeFolder', (): typeof GetHomeFolderModule => ({ + getHomeFolder: mockGetHomeFolder +})); diff --git a/libraries/rush-lib/src/logic/test/CredentialCache.test.ts b/libraries/credential-cache/src/test/CredentialCache.test.ts similarity index 97% rename from libraries/rush-lib/src/logic/test/CredentialCache.test.ts rename to libraries/credential-cache/src/test/CredentialCache.test.ts index 46ce1bf6ccd..5bdb13c49e6 100644 --- a/libraries/rush-lib/src/logic/test/CredentialCache.test.ts +++ b/libraries/credential-cache/src/test/CredentialCache.test.ts @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import { mockGetHomeFolder } from './CredentialCache.mock'; import { LockFile, Async, FileSystem } from '@rushstack/node-core-library'; -import { RushUserConfiguration } from '../../api/RushUserConfiguration'; -import { CredentialCache, type ICredentialCacheOptions } from '../CredentialCache'; +import { CredentialCache, type ICredentialCacheOptions, RUSH_USER_FOLDER_NAME } from '../CredentialCache'; -const FAKE_RUSH_USER_FOLDER: string = 'temp/.rush-user'; +const FAKE_HOME_FOLDER: string = 'temp'; +const FAKE_RUSH_USER_FOLDER: string = `${FAKE_HOME_FOLDER}/${RUSH_USER_FOLDER_NAME}`; interface IPathsTestCase extends Required> { testCaseName: string; @@ -23,7 +24,7 @@ describe(CredentialCache.name, () => { }); beforeEach(() => { - jest.spyOn(RushUserConfiguration, 'getRushUserFolderPath').mockReturnValue(FAKE_RUSH_USER_FOLDER); + mockGetHomeFolder.mockReturnValue(FAKE_HOME_FOLDER); // TODO: Consider expanding these mocks and moving them to node-core-library jest diff --git a/libraries/rush-lib/src/logic/test/__snapshots__/CredentialCache.test.ts.snap b/libraries/credential-cache/src/test/__snapshots__/CredentialCache.test.ts.snap similarity index 100% rename from libraries/rush-lib/src/logic/test/__snapshots__/CredentialCache.test.ts.snap rename to libraries/credential-cache/src/test/__snapshots__/CredentialCache.test.ts.snap diff --git a/libraries/credential-cache/tsconfig.json b/libraries/credential-cache/tsconfig.json new file mode 100644 index 00000000000..dac21d04081 --- /dev/null +++ b/libraries/credential-cache/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/local-node-rig/profiles/default/tsconfig-base.json" +} diff --git a/libraries/node-core-library/src/Disposables.ts b/libraries/node-core-library/src/Disposables.ts new file mode 100644 index 00000000000..8edc3e0e794 --- /dev/null +++ b/libraries/node-core-library/src/Disposables.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as Disposables from './disposables/index'; + +export { Disposables }; diff --git a/libraries/node-core-library/src/Objects.ts b/libraries/node-core-library/src/Objects.ts new file mode 100644 index 00000000000..ac7923db5f7 --- /dev/null +++ b/libraries/node-core-library/src/Objects.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as Objects from './objects/index'; + +export { Objects }; diff --git a/libraries/node-core-library/src/User.ts b/libraries/node-core-library/src/User.ts new file mode 100644 index 00000000000..3107da72bf6 --- /dev/null +++ b/libraries/node-core-library/src/User.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as User from './user/index'; + +export { User }; diff --git a/libraries/node-core-library/src/disposables/index.ts b/libraries/node-core-library/src/disposables/index.ts new file mode 100644 index 00000000000..f3aa7768ad1 --- /dev/null +++ b/libraries/node-core-library/src/disposables/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +export { polyfillDisposeSymbols } from './polyfillDisposeSymbols'; diff --git a/libraries/node-core-library/src/disposables/polyfillDisposeSymbols.ts b/libraries/node-core-library/src/disposables/polyfillDisposeSymbols.ts new file mode 100644 index 00000000000..621807aa12f --- /dev/null +++ b/libraries/node-core-library/src/disposables/polyfillDisposeSymbols.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * @public + * Polyfill for `Symbol.dispose` and `Symbol.asyncDispose` for Node.js versions prior to 20 + */ +export function polyfillDisposeSymbols(): void { + (Symbol as { dispose?: typeof Symbol.dispose }).dispose ??= Symbol.for( + 'Symbol.dispose' + ) as typeof Symbol.dispose; + (Symbol as { asyncDispose?: typeof Symbol.asyncDispose }).asyncDispose ??= Symbol.for( + 'Symbol.asyncDispose' + ) as typeof Symbol.asyncDispose; +} diff --git a/libraries/node-core-library/src/index.ts b/libraries/node-core-library/src/index.ts index 648b559353c..c48a9c950a3 100644 --- a/libraries/node-core-library/src/index.ts +++ b/libraries/node-core-library/src/index.ts @@ -9,7 +9,10 @@ * @packageDocumentation */ +export type { IProblemPattern } from '@rushstack/problem-matcher'; + export { AlreadyReportedError } from './AlreadyReportedError'; + export { Async, AsyncQueue, @@ -18,10 +21,15 @@ export { type IRunWithTimeoutOptions, type IWeighted } from './Async'; -export type { Brand } from './PrimitiveTypes'; + export { FileConstants, FolderConstants } from './Constants'; + +export { Disposables } from './Disposables'; + export { Enum } from './Enum'; + export { EnvironmentMap, type IEnvironmentEntry } from './EnvironmentMap'; + export { type ExecutableStdioStreamMapping, type ExecutableStdioMapping, @@ -36,18 +44,32 @@ export { type IProcessInfo, Executable } from './Executable'; + export { type IFileErrorOptions, type IFileErrorFormattingOptions, FileError } from './FileError'; -export type { IProblemPattern } from '@rushstack/problem-matcher'; -export type { - INodePackageJson, - IPackageJson, - IPackageJsonDependencyTable, - IPackageJsonScriptTable, - IPackageJsonRepository, - IPeerDependenciesMetaTable, - IDependenciesMetaTable, - IPackageJsonExports -} from './IPackageJson'; + +export { + AlreadyExistsBehavior, + FileSystem, + type FileSystemCopyFilesAsyncFilter, + type FileSystemCopyFilesFilter, + type FolderItem, + type FileSystemStats, + type IFileSystemCopyFileBaseOptions, + type IFileSystemCopyFileOptions, + type IFileSystemCopyFilesAsyncOptions, + type IFileSystemCopyFilesOptions, + type IFileSystemCreateLinkOptions, + type IFileSystemDeleteFileOptions, + type IFileSystemMoveOptions, + type IFileSystemReadFileOptions, + type IFileSystemReadFolderOptions, + type IFileSystemUpdateTimeParameters, + type IFileSystemWriteBinaryFileOptions, + type IFileSystemWriteFileOptions +} from './FileSystem'; + +export { FileWriter, type IFileWriterFlags } from './FileWriter'; + export { Import, type IImportResolveOptions, @@ -57,7 +79,20 @@ export { type IImportResolvePackageOptions, type IImportResolvePackageAsyncOptions } from './Import'; + export { InternalError } from './InternalError'; + +export type { + INodePackageJson, + IPackageJson, + IPackageJsonDependencyTable, + IPackageJsonScriptTable, + IPackageJsonRepository, + IPeerDependenciesMetaTable, + IDependenciesMetaTable, + IPackageJsonExports +} from './IPackageJson'; + export { type JsonObject, type JsonNull, @@ -68,6 +103,7 @@ export { type IJsonFileSaveOptions, JsonFile } from './JsonFile'; + export { type IJsonSchemaErrorInfo, type IJsonSchemaCustomFormat, @@ -79,12 +115,19 @@ export { JsonSchema, type JsonSchemaVersion } from './JsonSchema'; + +export { LegacyAdapters, type LegacyCallback } from './LegacyAdapters'; + export { LockFile } from './LockFile'; + export { MapExtensions } from './MapExtensions'; + export { MinimumHeap } from './MinimumHeap'; -export { PosixModeBits } from './PosixModeBits'; -export { ProtectableMap, type IProtectableMapParameters } from './ProtectableMap'; + +export { Objects } from './Objects'; + export { type IPackageJsonLookupParameters, PackageJsonLookup } from './PackageJsonLookup'; + export { PackageName, PackageNameParser, @@ -92,37 +135,30 @@ export { type IParsedPackageName, type IParsedPackageNameOrError } from './PackageName'; + export { Path, type FileLocationStyle, type IPathFormatFileLocationOptions, type IPathFormatConciselyOptions } from './Path'; + +export { PosixModeBits } from './PosixModeBits'; + +export type { Brand } from './PrimitiveTypes'; + +export { ProtectableMap, type IProtectableMapParameters } from './ProtectableMap'; + export { RealNodeModulePathResolver, type IRealNodeModulePathResolverOptions } from './RealNodeModulePath'; -export { Encoding, Text, NewlineKind, type IReadLinesFromIterableOptions } from './Text'; + export { Sort } from './Sort'; -export { - AlreadyExistsBehavior, - FileSystem, - type FileSystemCopyFilesAsyncFilter, - type FileSystemCopyFilesFilter, - type FolderItem, - type FileSystemStats, - type IFileSystemCopyFileBaseOptions, - type IFileSystemCopyFileOptions, - type IFileSystemCopyFilesAsyncOptions, - type IFileSystemCopyFilesOptions, - type IFileSystemCreateLinkOptions, - type IFileSystemDeleteFileOptions, - type IFileSystemMoveOptions, - type IFileSystemReadFileOptions, - type IFileSystemReadFolderOptions, - type IFileSystemUpdateTimeParameters, - type IFileSystemWriteBinaryFileOptions, - type IFileSystemWriteFileOptions -} from './FileSystem'; -export { FileWriter, type IFileWriterFlags } from './FileWriter'; -export { LegacyAdapters, type LegacyCallback } from './LegacyAdapters'; + export { StringBuilder, type IStringBuilder } from './StringBuilder'; + export { type ISubprocessOptions, SubprocessTerminator } from './SubprocessTerminator'; + +export { Encoding, Text, NewlineKind, type IReadLinesFromIterableOptions } from './Text'; + export { TypeUuid } from './TypeUuid'; + +export { User } from './User'; diff --git a/libraries/node-core-library/src/objects/areDeepEqual.ts b/libraries/node-core-library/src/objects/areDeepEqual.ts new file mode 100644 index 00000000000..09d618e588a --- /dev/null +++ b/libraries/node-core-library/src/objects/areDeepEqual.ts @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * Determines if two objects are deeply equal. + * @public + */ +export function areDeepEqual(a: TObject, b: TObject): boolean { + if (a === b) { + return true; + } else { + const aType: string = typeof a; + const bType: string = typeof b; + + if (aType !== bType) { + return false; + } else { + if (aType === 'object') { + if (a === null || b === null) { + // We already handled the case where a === b, so if either is null, they are not equal + return false; + } else if (Array.isArray(a)) { + if (!Array.isArray(b) || a.length !== b.length) { + return false; + } else { + for (let i: number = 0; i < a.length; ++i) { + if (!areDeepEqual(a[i], b[i])) { + return false; + } + } + + return true; + } + } else { + const aObjectProperties: Set = new Set(Object.getOwnPropertyNames(a)); + const bObjectProperties: Set = new Set(Object.getOwnPropertyNames(b)); + if (aObjectProperties.size !== bObjectProperties.size) { + return false; + } else { + for (const property of aObjectProperties) { + if (bObjectProperties.delete(property)) { + if ( + !areDeepEqual( + (a as Record)[property], + (b as Record)[property] + ) + ) { + return false; + } + } else { + return false; + } + } + + return bObjectProperties.size === 0; + } + } + } else { + return false; + } + } + } +} diff --git a/libraries/node-core-library/src/objects/index.ts b/libraries/node-core-library/src/objects/index.ts new file mode 100644 index 00000000000..77fdef00371 --- /dev/null +++ b/libraries/node-core-library/src/objects/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +export { areDeepEqual } from './areDeepEqual'; diff --git a/libraries/node-core-library/src/objects/test/areDeepEqual.test.ts b/libraries/node-core-library/src/objects/test/areDeepEqual.test.ts new file mode 100644 index 00000000000..28f29790685 --- /dev/null +++ b/libraries/node-core-library/src/objects/test/areDeepEqual.test.ts @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { areDeepEqual } from '../areDeepEqual'; + +describe(areDeepEqual.name, () => { + it('can compare primitives', () => { + expect(areDeepEqual(1, 1)).toEqual(true); + expect(areDeepEqual(1, undefined)).toEqual(false); + expect(areDeepEqual(1, null)).toEqual(false); + expect(areDeepEqual(undefined, 1)).toEqual(false); + expect(areDeepEqual(null, 1)).toEqual(false); + expect(areDeepEqual(1, 2)).toEqual(false); + + expect(areDeepEqual('a', 'a')).toEqual(true); + expect(areDeepEqual('a', undefined)).toEqual(false); + expect(areDeepEqual('a', null)).toEqual(false); + expect(areDeepEqual(undefined, 'a')).toEqual(false); + expect(areDeepEqual(null, 'a')).toEqual(false); + expect(areDeepEqual('a', 'b')).toEqual(false); + + expect(areDeepEqual(true, true)).toEqual(true); + expect(areDeepEqual(true, undefined)).toEqual(false); + expect(areDeepEqual(true, null)).toEqual(false); + expect(areDeepEqual(undefined, true)).toEqual(false); + expect(areDeepEqual(null, true)).toEqual(false); + expect(areDeepEqual(true, false)).toEqual(false); + + expect(areDeepEqual(undefined, undefined)).toEqual(true); + expect(areDeepEqual(undefined, null)).toEqual(false); + expect(areDeepEqual(null, null)).toEqual(true); + }); + + it('can compare arrays', () => { + expect(areDeepEqual([], [])).toEqual(true); + expect(areDeepEqual([], undefined)).toEqual(false); + expect(areDeepEqual([], null)).toEqual(false); + expect(areDeepEqual(undefined, [])).toEqual(false); + expect(areDeepEqual(null, [])).toEqual(false); + + expect(areDeepEqual([1], [1])).toEqual(true); + expect(areDeepEqual([1], [2])).toEqual(false); + + expect(areDeepEqual([1, 2], [1, 2])).toEqual(true); + expect(areDeepEqual([1, 2], [2, 1])).toEqual(false); + + expect(areDeepEqual([1, 2, 3], [1, 2, 3])).toEqual(true); + expect(areDeepEqual([1, 2, 3], [1, 2, 4])).toEqual(false); + }); + + it('can compare objects', () => { + expect(areDeepEqual({}, {})).toEqual(true); + expect(areDeepEqual({}, undefined)).toEqual(false); + expect(areDeepEqual({}, null)).toEqual(false); + expect(areDeepEqual(undefined, {})).toEqual(false); + expect(areDeepEqual(null, {})).toEqual(false); + + expect(areDeepEqual({ a: 1 }, { a: 1 })).toEqual(true); + expect(areDeepEqual({ a: 1 }, { a: 2 })).toEqual(false); + expect(areDeepEqual({ a: 1 }, {})).toEqual(false); + expect(areDeepEqual({}, { a: 1 })).toEqual(false); + expect(areDeepEqual({ a: 1 }, { b: 1 })).toEqual(false); + + expect(areDeepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toEqual(true); + expect(areDeepEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toEqual(false); + expect(areDeepEqual({ a: 1, b: 2 }, { a: 1, c: 2 })).toEqual(false); + expect(areDeepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toEqual(true); + }); + + it('can compare nested objects', () => { + expect(areDeepEqual({ a: { b: 1 } }, { a: { b: 1 } })).toEqual(true); + expect(areDeepEqual({ a: { b: 1 } }, { a: { b: 2 } })).toEqual(false); + expect(areDeepEqual({ a: { b: 1 } }, { a: { c: 1 } })).toEqual(false); + expect(areDeepEqual({ a: { b: 1 } }, { a: { b: 1, c: 2 } })).toEqual(false); + expect(areDeepEqual({ a: { b: 1 } }, { a: { b: 1 }, c: 2 })).toEqual(false); + }); +}); diff --git a/libraries/node-core-library/src/user/getHomeFolder.ts b/libraries/node-core-library/src/user/getHomeFolder.ts new file mode 100644 index 00000000000..61db1b38714 --- /dev/null +++ b/libraries/node-core-library/src/user/getHomeFolder.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'node:path'; + +import { FileSystem } from '../FileSystem'; + +let _cachedHomeFolder: string | undefined; + +/** + * Returns the current user's home folder path. + * Throws if it cannot be determined. Successful results are cached. + * @public + */ +export function getHomeFolder(): string { + if (_cachedHomeFolder !== undefined) { + return _cachedHomeFolder; + } + + const unresolvedUserFolder: string | undefined = + process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']; + const dirError: string = "Unable to determine the current user's home directory"; + if (unresolvedUserFolder === undefined) { + throw new Error(dirError); + } + + const homeFolder: string = path.resolve(unresolvedUserFolder); + if (!FileSystem.exists(homeFolder)) { + throw new Error(dirError); + } + + _cachedHomeFolder = homeFolder; + + return homeFolder; +} diff --git a/libraries/node-core-library/src/user/index.ts b/libraries/node-core-library/src/user/index.ts new file mode 100644 index 00000000000..9e4ef36360e --- /dev/null +++ b/libraries/node-core-library/src/user/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +export { getHomeFolder } from './getHomeFolder'; diff --git a/libraries/rush-lib/package.json b/libraries/rush-lib/package.json index 27769a34588..99563d59046 100644 --- a/libraries/rush-lib/package.json +++ b/libraries/rush-lib/package.json @@ -33,6 +33,7 @@ "@pnpm/dependency-path-lockfile-pre-v10": "npm:@pnpm/dependency-path@~5.1.7", "@pnpm/dependency-path": "~1000.0.9", "@pnpm/link-bins": "~5.3.7", + "@rushstack/credential-cache": "workspace:*", "@rushstack/heft-config-file": "workspace:*", "@rushstack/lookup-by-path": "workspace:*", "@rushstack/node-core-library": "workspace:*", diff --git a/libraries/rush-lib/src/api/FlagFile.ts b/libraries/rush-lib/src/api/FlagFile.ts index 13466552817..535029943c9 100644 --- a/libraries/rush-lib/src/api/FlagFile.ts +++ b/libraries/rush-lib/src/api/FlagFile.ts @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { FileSystem, JsonFile, type JsonObject } from '@rushstack/node-core-library'; - -import { objectsAreDeepEqual } from '../utilities/objectUtilities'; +import { FileSystem, JsonFile, type JsonObject, Objects } from '@rushstack/node-core-library'; /** * A base class for flag file. @@ -38,7 +36,7 @@ export class FlagFile { try { oldState = await JsonFile.loadAsync(this.path); const newState: JsonObject = this._state; - return objectsAreDeepEqual(oldState, newState); + return Objects.areDeepEqual(oldState, newState); } catch (err) { return false; } diff --git a/libraries/rush-lib/src/api/LastInstallFlag.ts b/libraries/rush-lib/src/api/LastInstallFlag.ts index a20688cca32..ab854ac6ef7 100644 --- a/libraries/rush-lib/src/api/LastInstallFlag.ts +++ b/libraries/rush-lib/src/api/LastInstallFlag.ts @@ -3,7 +3,7 @@ import { pnpmSyncGetJsonVersion } from 'pnpm-sync-lib'; -import { JsonFile, type JsonObject, Path, type IPackageJson } from '@rushstack/node-core-library'; +import { JsonFile, type JsonObject, Path, type IPackageJson, Objects } from '@rushstack/node-core-library'; import type { PackageManagerName } from './packageManager/PackageManager'; import type { RushConfiguration } from './RushConfiguration'; @@ -124,7 +124,7 @@ export class LastInstallFlag extends FlagFile> { } } - if (!objectUtilities.objectsAreDeepEqual(oldState, newState)) { + if (!Objects.areDeepEqual(oldState, newState)) { if (checkValidAndReportStoreIssues) { const pkgManager: PackageManagerName = newState.packageManager as PackageManagerName; if (pkgManager === 'pnpm') { diff --git a/libraries/rush-lib/src/api/RushGlobalFolder.ts b/libraries/rush-lib/src/api/RushGlobalFolder.ts index e1081f2d4f7..18f77f7f938 100644 --- a/libraries/rush-lib/src/api/RushGlobalFolder.ts +++ b/libraries/rush-lib/src/api/RushGlobalFolder.ts @@ -3,7 +3,8 @@ import * as path from 'node:path'; -import { Utilities } from '../utilities/Utilities'; +import { User } from '@rushstack/node-core-library'; + import { EnvironmentConfiguration } from './EnvironmentConfiguration'; /** @@ -45,7 +46,7 @@ export class RushGlobalFolder { if (rushGlobalFolderOverride !== undefined) { this.path = rushGlobalFolderOverride; } else { - this.path = path.join(Utilities.getHomeFolder(), '.rush'); + this.path = path.join(User.getHomeFolder(), '.rush'); } const normalizedNodeVersion: string = process.version.match(/^[a-z0-9\-\.]+$/i) diff --git a/libraries/rush-lib/src/api/RushUserConfiguration.ts b/libraries/rush-lib/src/api/RushUserConfiguration.ts index 6b20ace19a3..cdf767363bb 100644 --- a/libraries/rush-lib/src/api/RushUserConfiguration.ts +++ b/libraries/rush-lib/src/api/RushUserConfiguration.ts @@ -3,9 +3,8 @@ import * as path from 'node:path'; -import { FileSystem, JsonFile, JsonSchema } from '@rushstack/node-core-library'; +import { FileSystem, JsonFile, JsonSchema, User } from '@rushstack/node-core-library'; -import { Utilities } from '../utilities/Utilities'; import { RushConstants } from '../logic/RushConstants'; import schemaJson from '../schemas/rush-user-settings.schema.json'; @@ -52,7 +51,7 @@ export class RushUserConfiguration { } public static getRushUserFolderPath(): string { - const homeFolderPath: string = Utilities.getHomeFolder(); + const homeFolderPath: string = User.getHomeFolder(); return `${homeFolderPath}/${RushConstants.rushUserConfigurationFolderName}`; } } diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts index 1b5a36318fe..7c7787a4362 100644 --- a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts @@ -9,7 +9,8 @@ import { FileConstants, FileSystem, JsonFile, - type JsonObject + type JsonObject, + Objects } from '@rushstack/node-core-library'; import { Colorize, @@ -28,7 +29,6 @@ import { PurgeManager } from '../logic/PurgeManager'; import type { IBuiltInPluginConfiguration } from '../pluginFramework/PluginLoader/BuiltInPluginLoader'; import type { BaseInstallManager } from '../logic/base/BaseInstallManager'; import type { IInstallManagerOptions } from '../logic/base/BaseInstallManagerTypes'; -import { objectsAreDeepEqual } from '../utilities/objectUtilities'; import { Utilities } from '../utilities/Utilities'; import type { Subspace } from '../api/Subspace'; import type { PnpmOptionsConfiguration } from '../logic/pnpm/PnpmOptionsConfiguration'; @@ -495,7 +495,7 @@ export class RushPnpmCommandLineParser { const currentGlobalPatchedDependencies: Record | undefined = pnpmOptions?.globalPatchedDependencies; - if (!objectsAreDeepEqual(currentGlobalPatchedDependencies, newGlobalPatchedDependencies)) { + if (!Objects.areDeepEqual(currentGlobalPatchedDependencies, newGlobalPatchedDependencies)) { const commonTempPnpmPatchesFolder: string = `${subspaceTempFolder}/${RushConstants.pnpmPatchesFolderName}`; const rushPnpmPatchesFolder: string = this._subspace.getSubspacePnpmPatchesFolderPath(); diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index 85a59f9ad77..88dfb89789e 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -8,9 +8,16 @@ * @packageDocumentation */ -// For backwards compatibility +// #region Backwards compatibility export { LookupByPath as LookupByPath, type IPrefixMatch } from '@rushstack/lookup-by-path'; +export { + type ICredentialCacheOptions, + type ICredentialCacheEntry, + CredentialCache +} from '@rushstack/credential-cache'; +// #endregion + export { ApprovedPackagesPolicy } from './api/ApprovedPackagesPolicy'; export { RushConfiguration, type ITryFindRushJsonLocationOptions } from './api/RushConfiguration'; @@ -176,12 +183,6 @@ export type { ICobuildCompletedState } from './logic/cobuild/ICobuildLockProvider'; -export { - type ICredentialCacheOptions, - type ICredentialCacheEntry, - CredentialCache -} from './logic/CredentialCache'; - export type { ITelemetryData, ITelemetryMachineInfo, ITelemetryOperationResult } from './logic/Telemetry'; export type { IStopwatchResult } from './utilities/Stopwatch'; diff --git a/libraries/rush-lib/src/logic/RushConstants.ts b/libraries/rush-lib/src/logic/RushConstants.ts index e04c03d2f2e..0f5ed03c8e8 100644 --- a/libraries/rush-lib/src/logic/RushConstants.ts +++ b/libraries/rush-lib/src/logic/RushConstants.ts @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { RUSH_USER_FOLDER_NAME } from '@rushstack/credential-cache'; + +// Use the typing here to enforce consistency between the two libraries +const rushUserConfigurationFolderName: typeof RUSH_USER_FOLDER_NAME = '.rush-user'; + /** * Constants used by the Rush tool. * @beta @@ -298,7 +303,7 @@ export class RushConstants { /** * The name of the per-user Rush configuration data folder. */ - public static readonly rushUserConfigurationFolderName: '.rush-user' = '.rush-user'; + public static readonly rushUserConfigurationFolderName: '.rush-user' = rushUserConfigurationFolderName; /** * The name of the project `rush-logs` folder. diff --git a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts index 8343b6c8fc9..9411c549e43 100644 --- a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts +++ b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts @@ -13,6 +13,7 @@ import { AlreadyReportedError, Async, type IDependenciesMetaTable, + Objects, Path, Sort } from '@rushstack/node-core-library'; @@ -39,7 +40,6 @@ import { ShrinkwrapFileFactory } from '../ShrinkwrapFileFactory'; import { BaseProjectShrinkwrapFile } from '../base/BaseProjectShrinkwrapFile'; import { type CustomTipId, type ICustomTipInfo, PNPM_CUSTOM_TIPS } from '../../api/CustomTipsConfiguration'; import type { PnpmShrinkwrapFile } from '../pnpm/PnpmShrinkwrapFile'; -import { objectsAreDeepEqual } from '../../utilities/objectUtilities'; import type { Subspace } from '../../api/Subspace'; import { BaseLinkManager, SymlinkKind } from '../base/BaseLinkManager'; import { FlagFile } from '../../api/FlagFile'; @@ -372,7 +372,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { } // Now, we compare these two objects to see if they are equal or not - const dependenciesMetaAreEqual: boolean = objectsAreDeepEqual( + const dependenciesMetaAreEqual: boolean = Objects.areDeepEqual( expectedDependenciesMetaByProjectRelativePath, lockfileDependenciesMetaByProjectRelativePath ); @@ -388,7 +388,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { const pnpmOptions: PnpmOptionsConfiguration = subspace.getPnpmOptions() || this.rushConfiguration.pnpmOptions; - const overridesAreEqual: boolean = objectsAreDeepEqual>( + const overridesAreEqual: boolean = Objects.areDeepEqual>( pnpmOptions.globalOverrides ?? {}, shrinkwrapFile?.overrides ? Object.fromEntries(shrinkwrapFile?.overrides) : {} ); diff --git a/libraries/rush-lib/src/logic/setup/SetupPackageRegistry.ts b/libraries/rush-lib/src/logic/setup/SetupPackageRegistry.ts index 3a681a5b75e..ffbabe9e18b 100644 --- a/libraries/rush-lib/src/logic/setup/SetupPackageRegistry.ts +++ b/libraries/rush-lib/src/logic/setup/SetupPackageRegistry.ts @@ -11,7 +11,8 @@ import { InternalError, type JsonObject, NewlineKind, - Text + Text, + User } from '@rushstack/node-core-library'; import { PrintUtilities, Colorize, ConsoleTerminalProvider, Terminal } from '@rushstack/terminal'; @@ -353,7 +354,7 @@ export class SetupPackageRegistry { // ...then append the stuff we got from the REST API, but discard any junk that isn't a proper key/value linesToAdd.push(...responseLines.filter((x) => SetupPackageRegistry._getNpmrcKey(x) !== undefined)); - const npmrcPath: string = path.join(Utilities.getHomeFolder(), '.npmrc'); + const npmrcPath: string = path.join(User.getHomeFolder(), '.npmrc'); this._mergeLinesIntoNpmrc(npmrcPath, linesToAdd); } diff --git a/libraries/rush-lib/src/utilities/objectUtilities.ts b/libraries/rush-lib/src/utilities/objectUtilities.ts index 6224b94dc30..7261c88ef4d 100644 --- a/libraries/rush-lib/src/utilities/objectUtilities.ts +++ b/libraries/rush-lib/src/utilities/objectUtilities.ts @@ -1,65 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -/** - * Determines if two objects are deeply equal. - */ -export function objectsAreDeepEqual(a: TObject, b: TObject): boolean { - if (a === b) { - return true; - } else { - const aType: string = typeof a; - const bType: string = typeof b; - if (aType !== bType) { - return false; - } else { - if (aType === 'object') { - if (a === null || b === null) { - // We already handled the case where a === b, so if either is null, they are not equal - return false; - } else if (Array.isArray(a)) { - if (!Array.isArray(b) || a.length !== b.length) { - return false; - } else { - for (let i: number = 0; i < a.length; ++i) { - if (!objectsAreDeepEqual(a[i], b[i])) { - return false; - } - } - - return true; - } - } else { - const aObjectProperties: Set = new Set(Object.getOwnPropertyNames(a)); - const bObjectProperties: Set = new Set(Object.getOwnPropertyNames(b)); - if (aObjectProperties.size !== bObjectProperties.size) { - return false; - } else { - for (const property of aObjectProperties) { - if (bObjectProperties.delete(property)) { - if ( - !objectsAreDeepEqual( - (a as Record)[property], - (b as Record)[property] - ) - ) { - return false; - } - } else { - return false; - } - } - - return bObjectProperties.size === 0; - } - } - } else { - return false; - } - } - } -} - export function cloneDeep(obj: TObject): TObject { return cloneDeepInner(obj, new Set()); } diff --git a/libraries/rush-lib/src/utilities/test/objectUtilities.test.ts b/libraries/rush-lib/src/utilities/test/objectUtilities.test.ts index 9235fd63ab8..ce023119369 100644 --- a/libraries/rush-lib/src/utilities/test/objectUtilities.test.ts +++ b/libraries/rush-lib/src/utilities/test/objectUtilities.test.ts @@ -1,82 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { objectsAreDeepEqual, cloneDeep, merge, removeNullishProps } from '../objectUtilities'; +import { cloneDeep, merge, removeNullishProps } from '../objectUtilities'; describe('objectUtilities', () => { - describe(objectsAreDeepEqual.name, () => { - it('can compare primitives', () => { - expect(objectsAreDeepEqual(1, 1)).toEqual(true); - expect(objectsAreDeepEqual(1, undefined)).toEqual(false); - expect(objectsAreDeepEqual(1, null)).toEqual(false); - expect(objectsAreDeepEqual(undefined, 1)).toEqual(false); - expect(objectsAreDeepEqual(null, 1)).toEqual(false); - expect(objectsAreDeepEqual(1, 2)).toEqual(false); - - expect(objectsAreDeepEqual('a', 'a')).toEqual(true); - expect(objectsAreDeepEqual('a', undefined)).toEqual(false); - expect(objectsAreDeepEqual('a', null)).toEqual(false); - expect(objectsAreDeepEqual(undefined, 'a')).toEqual(false); - expect(objectsAreDeepEqual(null, 'a')).toEqual(false); - expect(objectsAreDeepEqual('a', 'b')).toEqual(false); - - expect(objectsAreDeepEqual(true, true)).toEqual(true); - expect(objectsAreDeepEqual(true, undefined)).toEqual(false); - expect(objectsAreDeepEqual(true, null)).toEqual(false); - expect(objectsAreDeepEqual(undefined, true)).toEqual(false); - expect(objectsAreDeepEqual(null, true)).toEqual(false); - expect(objectsAreDeepEqual(true, false)).toEqual(false); - - expect(objectsAreDeepEqual(undefined, undefined)).toEqual(true); - expect(objectsAreDeepEqual(undefined, null)).toEqual(false); - expect(objectsAreDeepEqual(null, null)).toEqual(true); - }); - - it('can compare arrays', () => { - expect(objectsAreDeepEqual([], [])).toEqual(true); - expect(objectsAreDeepEqual([], undefined)).toEqual(false); - expect(objectsAreDeepEqual([], null)).toEqual(false); - expect(objectsAreDeepEqual(undefined, [])).toEqual(false); - expect(objectsAreDeepEqual(null, [])).toEqual(false); - - expect(objectsAreDeepEqual([1], [1])).toEqual(true); - expect(objectsAreDeepEqual([1], [2])).toEqual(false); - - expect(objectsAreDeepEqual([1, 2], [1, 2])).toEqual(true); - expect(objectsAreDeepEqual([1, 2], [2, 1])).toEqual(false); - - expect(objectsAreDeepEqual([1, 2, 3], [1, 2, 3])).toEqual(true); - expect(objectsAreDeepEqual([1, 2, 3], [1, 2, 4])).toEqual(false); - }); - - it('can compare objects', () => { - expect(objectsAreDeepEqual({}, {})).toEqual(true); - expect(objectsAreDeepEqual({}, undefined)).toEqual(false); - expect(objectsAreDeepEqual({}, null)).toEqual(false); - expect(objectsAreDeepEqual(undefined, {})).toEqual(false); - expect(objectsAreDeepEqual(null, {})).toEqual(false); - - expect(objectsAreDeepEqual({ a: 1 }, { a: 1 })).toEqual(true); - expect(objectsAreDeepEqual({ a: 1 }, { a: 2 })).toEqual(false); - expect(objectsAreDeepEqual({ a: 1 }, {})).toEqual(false); - expect(objectsAreDeepEqual({}, { a: 1 })).toEqual(false); - expect(objectsAreDeepEqual({ a: 1 }, { b: 1 })).toEqual(false); - - expect(objectsAreDeepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toEqual(true); - expect(objectsAreDeepEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toEqual(false); - expect(objectsAreDeepEqual({ a: 1, b: 2 }, { a: 1, c: 2 })).toEqual(false); - expect(objectsAreDeepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toEqual(true); - }); - - it('can compare nested objects', () => { - expect(objectsAreDeepEqual({ a: { b: 1 } }, { a: { b: 1 } })).toEqual(true); - expect(objectsAreDeepEqual({ a: { b: 1 } }, { a: { b: 2 } })).toEqual(false); - expect(objectsAreDeepEqual({ a: { b: 1 } }, { a: { c: 1 } })).toEqual(false); - expect(objectsAreDeepEqual({ a: { b: 1 } }, { a: { b: 1, c: 2 } })).toEqual(false); - expect(objectsAreDeepEqual({ a: { b: 1 } }, { a: { b: 1 }, c: 2 })).toEqual(false); - }); - }); - describe(cloneDeep.name, () => { function testClone(source: unknown): void { const clone: unknown = cloneDeep(source); diff --git a/libraries/rush-sdk/package.json b/libraries/rush-sdk/package.json index a47186ad257..5ee83a57f4f 100644 --- a/libraries/rush-sdk/package.json +++ b/libraries/rush-sdk/package.json @@ -40,6 +40,7 @@ "license": "MIT", "dependencies": { "@pnpm/lockfile.types": "~1.0.3", + "@rushstack/credential-cache": "workspace:*", "@rushstack/lookup-by-path": "workspace:*", "@rushstack/node-core-library": "workspace:*", "@rushstack/package-deps-hash": "workspace:*", diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json b/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json index 970c2756992..8ae7cfb51ce 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json @@ -19,6 +19,7 @@ "_phase:test": "heft run --only test -- --clean" }, "dependencies": { + "@rushstack/credential-cache": "workspace:*", "@rushstack/node-core-library": "workspace:*", "@rushstack/rush-sdk": "workspace:*", "@rushstack/terminal": "workspace:*", diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3BuildCacheProvider.ts b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3BuildCacheProvider.ts index 2d4292bd8af..ea8d7b903c0 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3BuildCacheProvider.ts +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/AmazonS3BuildCacheProvider.ts @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import { type ICredentialCacheEntry, CredentialCache } from '@rushstack/credential-cache'; import type { ITerminal } from '@rushstack/terminal'; import { type ICloudBuildCacheProvider, - type ICredentialCacheEntry, - CredentialCache, type RushSession, RushConstants, EnvironmentVariableNames, diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3BuildCacheProvider.test.ts b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3BuildCacheProvider.test.ts index f66b89415ed..f846371ff0b 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3BuildCacheProvider.test.ts +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/src/test/AmazonS3BuildCacheProvider.test.ts @@ -5,13 +5,9 @@ jest.mock('@rushstack/rush-sdk/lib/utilities/WebClient', () => { return jest.requireActual('@microsoft/rush-lib/lib/utilities/WebClient'); }); +import { CredentialCache } from '@rushstack/credential-cache'; import { ConsoleTerminalProvider, StringBufferTerminalProvider, Terminal } from '@rushstack/terminal'; -import { - RushSession, - CredentialCache, - EnvironmentConfiguration, - RushUserConfiguration -} from '@rushstack/rush-sdk'; +import { RushSession, EnvironmentConfiguration, RushUserConfiguration } from '@rushstack/rush-sdk'; import { AmazonS3BuildCacheProvider } from '../AmazonS3BuildCacheProvider'; diff --git a/rush-plugins/rush-azure-storage-build-cache-plugin/package.json b/rush-plugins/rush-azure-storage-build-cache-plugin/package.json index 99d2feffe64..c326b6f4467 100644 --- a/rush-plugins/rush-azure-storage-build-cache-plugin/package.json +++ b/rush-plugins/rush-azure-storage-build-cache-plugin/package.json @@ -20,6 +20,7 @@ "dependencies": { "@azure/identity": "~4.5.0", "@azure/storage-blob": "~12.26.0", + "@rushstack/credential-cache": "workspace:*", "@rushstack/node-core-library": "workspace:*", "@rushstack/rush-sdk": "workspace:*", "@rushstack/terminal": "workspace:*" diff --git a/rush-plugins/rush-azure-storage-build-cache-plugin/src/AzureAuthenticationBase.ts b/rush-plugins/rush-azure-storage-build-cache-plugin/src/AzureAuthenticationBase.ts index f5c968e1328..2be93baa713 100644 --- a/rush-plugins/rush-azure-storage-build-cache-plugin/src/AzureAuthenticationBase.ts +++ b/rush-plugins/rush-azure-storage-build-cache-plugin/src/AzureAuthenticationBase.ts @@ -19,10 +19,10 @@ import { } from '@azure/identity'; import type { ITerminal } from '@rushstack/terminal'; -import { CredentialCache } from '@rushstack/rush-sdk'; +import { CredentialCache } from '@rushstack/credential-cache'; // Use a separate import line so the .d.ts file ends up with an `import type { ... }` // See https://github.com/microsoft/rushstack/issues/3432 -import type { ICredentialCacheEntry } from '@rushstack/rush-sdk'; +import type { ICredentialCacheEntry } from '@rushstack/credential-cache'; import { PrintUtilities } from '@rushstack/terminal'; import { AdoCodespacesAuthCredential } from './AdoCodespacesAuthCredential'; diff --git a/rush-plugins/rush-azure-storage-build-cache-plugin/src/test/AzureStorageBuildCacheProvider.test.ts b/rush-plugins/rush-azure-storage-build-cache-plugin/src/test/AzureStorageBuildCacheProvider.test.ts index 4bde79dd705..23bb0ead55b 100644 --- a/rush-plugins/rush-azure-storage-build-cache-plugin/src/test/AzureStorageBuildCacheProvider.test.ts +++ b/rush-plugins/rush-azure-storage-build-cache-plugin/src/test/AzureStorageBuildCacheProvider.test.ts @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import { CredentialCache } from '@rushstack/credential-cache'; import { StringBufferTerminalProvider, Terminal } from '@rushstack/terminal'; -import { CredentialCache, EnvironmentConfiguration, RushUserConfiguration } from '@rushstack/rush-sdk'; +import { EnvironmentConfiguration, RushUserConfiguration } from '@rushstack/rush-sdk'; import { AzureStorageBuildCacheProvider } from '../AzureStorageBuildCacheProvider'; import type { AzureEnvironmentName } from '../AzureAuthenticationBase'; diff --git a/rush-plugins/rush-http-build-cache-plugin/package.json b/rush-plugins/rush-http-build-cache-plugin/package.json index 369e18c3ffd..0e85ee46bb1 100644 --- a/rush-plugins/rush-http-build-cache-plugin/package.json +++ b/rush-plugins/rush-http-build-cache-plugin/package.json @@ -19,6 +19,7 @@ "_phase:test": "heft run --only test -- --clean" }, "dependencies": { + "@rushstack/credential-cache": "workspace:*", "@rushstack/node-core-library": "workspace:*", "@rushstack/rush-sdk": "workspace:*", "https-proxy-agent": "~5.0.0" diff --git a/rush-plugins/rush-http-build-cache-plugin/src/HttpBuildCacheProvider.ts b/rush-plugins/rush-http-build-cache-plugin/src/HttpBuildCacheProvider.ts index 8868fdbd09f..5acdd8a9457 100644 --- a/rush-plugins/rush-http-build-cache-plugin/src/HttpBuildCacheProvider.ts +++ b/rush-plugins/rush-http-build-cache-plugin/src/HttpBuildCacheProvider.ts @@ -3,12 +3,11 @@ import type { SpawnSyncReturns } from 'node:child_process'; +import { type ICredentialCacheEntry, CredentialCache } from '@rushstack/credential-cache'; import { Executable, Async } from '@rushstack/node-core-library'; import type { ITerminal } from '@rushstack/terminal'; import { type ICloudBuildCacheProvider, - type ICredentialCacheEntry, - CredentialCache, type RushSession, EnvironmentConfiguration } from '@rushstack/rush-sdk'; diff --git a/rush.json b/rush.json index 0cb4ae72f2e..a3ff7deea8e 100644 --- a/rush.json +++ b/rush.json @@ -1154,6 +1154,12 @@ "shouldPublish": true, "decoupledLocalDependencies": ["@rushstack/heft"] }, + { + "packageName": "@rushstack/credential-cache", + "projectFolder": "libraries/credential-cache", + "reviewCategory": "libraries", + "shouldPublish": true + }, { "packageName": "@rushstack/debug-certificate-manager", "projectFolder": "libraries/debug-certificate-manager",