From 1d1f3c4ec586d4c748867ff6050b4722eda65218 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Fri, 12 Sep 2025 20:04:33 -0700 Subject: [PATCH 01/21] Add launch.json --- apps/lockfile-explorer/.vscode/launch.json | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 apps/lockfile-explorer/.vscode/launch.json diff --git a/apps/lockfile-explorer/.vscode/launch.json b/apps/lockfile-explorer/.vscode/launch.json new file mode 100644 index 00000000000..a0272b7d4ed --- /dev/null +++ b/apps/lockfile-explorer/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug Jest tests", + "program": "${workspaceFolder}/node_modules/@rushstack/heft/lib/start.js", + "cwd": "${workspaceFolder}", + "args": ["--debug", "test", "--clean"], + "console": "integratedTerminal", + "sourceMaps": true + }, + { + "type": "node", + "request": "launch", + "name": "Run in specified folder", + "program": "${workspaceFolder}/lib/start-explorer.js", +// "cwd": "C:\\Git\\lockfile-explorer-demos", + "cwd": "(your project path)", + "args": ["--debug"], + "sourceMaps": true + } + ] +} From 0d26de0512af63034bb408b114d486456faf14ba Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Fri, 12 Sep 2025 20:28:25 -0700 Subject: [PATCH 02/21] Rename getPackageFiles.ts -> lfxApiClient.ts --- .../src/components/ConnectionModal/index.tsx | 2 +- .../src/containers/LockfileEntryDetailsView/index.tsx | 2 +- .../src/containers/PackageJsonViewer/index.tsx | 2 +- .../src/{parsing/getPackageFiles.ts => helpers/lfxApiClient.ts} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename apps/lockfile-explorer-web/src/{parsing/getPackageFiles.ts => helpers/lfxApiClient.ts} (100%) diff --git a/apps/lockfile-explorer-web/src/components/ConnectionModal/index.tsx b/apps/lockfile-explorer-web/src/components/ConnectionModal/index.tsx index d7e1d963bdf..9a29a13d849 100644 --- a/apps/lockfile-explorer-web/src/components/ConnectionModal/index.tsx +++ b/apps/lockfile-explorer-web/src/components/ConnectionModal/index.tsx @@ -5,7 +5,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Button, Text } from '@rushstack/rush-themed-ui'; import styles from './styles.scss'; import appStyles from '../../App.scss'; -import { checkAliveAsync } from '../../parsing/getPackageFiles'; +import { checkAliveAsync } from '../../helpers/lfxApiClient'; import type { ReactNull } from '../../types/ReactNull'; export const ConnectionModal = (): JSX.Element | ReactNull => { diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index 277edde9e61..b7b8315bab1 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -6,7 +6,7 @@ import { ScrollArea, Text } from '@rushstack/rush-themed-ui'; import styles from './styles.scss'; import appStyles from '../../App.scss'; import { IDependencyType, type LockfileDependency } from '../../parsing/LockfileDependency'; -import { readPackageJsonAsync } from '../../parsing/getPackageFiles'; +import { readPackageJsonAsync } from '../../helpers/lfxApiClient'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { pushToStack, selectCurrentEntry } from '../../store/slices/entrySlice'; import { ReactNull } from '../../types/ReactNull'; diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index d7bdd1ad267..de3f7e1c1ed 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import React, { useCallback, useEffect, useState } from 'react'; -import { readPnpmfileAsync, readPackageSpecAsync, readPackageJsonAsync } from '../../parsing/getPackageFiles'; +import { readPnpmfileAsync, readPackageSpecAsync, readPackageJsonAsync } from '../../helpers/lfxApiClient'; import styles from './styles.scss'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { selectCurrentEntry } from '../../store/slices/entrySlice'; diff --git a/apps/lockfile-explorer-web/src/parsing/getPackageFiles.ts b/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts similarity index 100% rename from apps/lockfile-explorer-web/src/parsing/getPackageFiles.ts rename to apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts From 0606b062f1b06bfba672f9c551492df43cc49250 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Fri, 12 Sep 2025 20:29:07 -0700 Subject: [PATCH 03/21] Fix broken devServer configuration --- apps/lockfile-explorer-web/webpack.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/lockfile-explorer-web/webpack.config.js b/apps/lockfile-explorer-web/webpack.config.js index bd625b455a9..10ce2633322 100644 --- a/apps/lockfile-explorer-web/webpack.config.js +++ b/apps/lockfile-explorer-web/webpack.config.js @@ -25,6 +25,12 @@ module.exports = function createConfig(env, argv) { }, devServer: { port: 8096, + + open: '/', + // Disable HTTPS to simplify Fiddler configuration + server: { type: 'http' }, + //hot: false, + static: { directory: path.join(__dirname, 'dist') }, From a8c8139eba2b934318e28b028735ef63e1a1cbad Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Fri, 12 Sep 2025 23:04:00 -0700 Subject: [PATCH 04/21] Fix broken IntelliSense due to missing dev dependency --- apps/lockfile-explorer-web/package.json | 3 ++- common/config/subspaces/default/pnpm-lock.yaml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/lockfile-explorer-web/package.json b/apps/lockfile-explorer-web/package.json index d9c76c4bca2..d8a641df65c 100644 --- a/apps/lockfile-explorer-web/package.json +++ b/apps/lockfile-explorer-web/package.json @@ -25,6 +25,7 @@ "@types/react": "17.0.74", "@types/react-dom": "17.0.25", "eslint": "~9.25.1", - "local-web-rig": "workspace:*" + "local-web-rig": "workspace:*", + "typescript": "5.8.2" } } diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 9f808d8cd08..fc244639a7d 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -300,6 +300,9 @@ importers: local-web-rig: specifier: workspace:* version: link:../../rigs/local-web-rig + typescript: + specifier: 5.8.2 + version: 5.8.2 ../../../apps/rundown: dependencies: From ff30b7e750cd9ee5dc8a2236bbb65cc3e50cbc72 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 00:02:59 -0700 Subject: [PATCH 05/21] Initial work on replacing server-only IAppState with client ILfxWorkspace --- .../src/helpers/lfxApiClient.ts | 39 ++++++++++++-- .../src/types/AppContext.ts | 11 ++++ .../{AppContext.ts => types/IAppContext.ts} | 7 --- .../src/types/lfxProtocol.ts | 34 ++++++++++++ .../cli/explorer/ExplorerCommandLineParser.ts | 6 ++- apps/lockfile-explorer/src/state/index.ts | 30 ++--------- apps/lockfile-explorer/src/utils/init.ts | 54 +++++++++---------- 7 files changed, 115 insertions(+), 66 deletions(-) create mode 100644 apps/lockfile-explorer-web/src/types/AppContext.ts rename apps/lockfile-explorer-web/src/{AppContext.ts => types/IAppContext.ts} (87%) create mode 100644 apps/lockfile-explorer-web/src/types/lfxProtocol.ts diff --git a/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts b/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts index 81283b33548..d48d4e4cd67 100644 --- a/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts +++ b/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts @@ -2,18 +2,47 @@ // See LICENSE in the project root for license information. import type { IPackageJson } from '../types/IPackageJson'; +import type { ILfxWorkspace } from '../types/lfxProtocol'; -const apiPath: string = `${window.appContext.serviceUrl}/api`; +const serviceUrl: string = window.appContext.serviceUrl; export async function checkAliveAsync(): Promise { try { - await fetch(`${apiPath}/health`); + await fetch(`${serviceUrl}/api/health`); return true; } catch (e) { return false; } } +/** + * Read the contents of a text file under the workspace directory. + * @param relativePath - a file path that is relative to the working directory. + */ +export async function readWorkspaceConfigAsync(): Promise { + let response: Response; + + try { + response = await fetch(`${serviceUrl}/api/workspace`); + if (!response.ok) { + const responseText: string = await response.text(); + const error = new Error( + 'The operation failed: ' + (responseText.trim() ?? 'An unknown error occurred') + ); + // eslint-disable-next-line no-console + console.error('readFileAsTextAsync() failed: ', error); + throw error; + } + } catch (e) { + // eslint-disable-next-line no-console + console.error('Network error in readWorkspaceConfigAsync(): ', e); + throw new Error('Network error: ' + (e.message ?? 'An unknown error occurred')); + } + + const responseJson: ILfxWorkspace = await response.json(); + return responseJson; +} + /** * Fetches a projects configuration files from the local file system * @@ -21,7 +50,7 @@ export async function checkAliveAsync(): Promise { */ export async function readPnpmfileAsync(): Promise { try { - const response = await fetch(`${apiPath}/pnpmfile`); + const response = await fetch(`${serviceUrl}/api/pnpmfile`); return await response.text(); } catch (e) { // eslint-disable-next-line no-console @@ -32,7 +61,7 @@ export async function readPnpmfileAsync(): Promise { export async function readPackageJsonAsync(projectPath: string): Promise { try { - const response = await fetch(`${apiPath}/package-json`, { + const response = await fetch(`${serviceUrl}/api/package-json`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -51,7 +80,7 @@ export async function readPackageJsonAsync(projectPath: string): Promise { try { - const response = await fetch(`${apiPath}/package-spec`, { + const response = await fetch(`${serviceUrl}/api/package-spec`, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/apps/lockfile-explorer-web/src/types/AppContext.ts b/apps/lockfile-explorer-web/src/types/AppContext.ts new file mode 100644 index 00000000000..28e788cf31f --- /dev/null +++ b/apps/lockfile-explorer-web/src/types/AppContext.ts @@ -0,0 +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 { IAppContext } from './IAppContext'; + +declare global { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Window { + appContext: IAppContext; + } +} diff --git a/apps/lockfile-explorer-web/src/AppContext.ts b/apps/lockfile-explorer-web/src/types/IAppContext.ts similarity index 87% rename from apps/lockfile-explorer-web/src/AppContext.ts rename to apps/lockfile-explorer-web/src/types/IAppContext.ts index 098a8432e53..93792c57b59 100644 --- a/apps/lockfile-explorer-web/src/AppContext.ts +++ b/apps/lockfile-explorer-web/src/types/IAppContext.ts @@ -34,10 +34,3 @@ export interface IAppContext { */ debugMode: boolean; } - -declare global { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface Window { - appContext: IAppContext; - } -} diff --git a/apps/lockfile-explorer-web/src/types/lfxProtocol.ts b/apps/lockfile-explorer-web/src/types/lfxProtocol.ts new file mode 100644 index 00000000000..04a1d793964 --- /dev/null +++ b/apps/lockfile-explorer-web/src/types/lfxProtocol.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +export interface ILfxWorkspaceRushConfig { + /** + * The rushVersion from rush.json. + */ + rushVersion: string; + + /** + * If the subspaces feature is enabled and a subspace was loaded, the name of the subspace. + * Otherwise this will be an empty string. + */ + subspaceName: string; +} + +export interface ILfxWorkspace { + /** + * Absolute path to the workspace folder that is opened by the app. + * Relative paths are generally relative to this path. + */ + workspaceRootFolder: string; + + /** + * The path to the pnpm-lock.yaml file. + */ + pnpmLockfilePath: string; + + /** + * If this is a Rush workspace (versus a plain PNPM workspace), then + * this section will be defined. + */ + rushConfig: ILfxWorkspaceRushConfig | undefined; +} diff --git a/apps/lockfile-explorer/src/cli/explorer/ExplorerCommandLineParser.ts b/apps/lockfile-explorer/src/cli/explorer/ExplorerCommandLineParser.ts index dc5fc422619..4f9d3efb2f7 100644 --- a/apps/lockfile-explorer/src/cli/explorer/ExplorerCommandLineParser.ts +++ b/apps/lockfile-explorer/src/cli/explorer/ExplorerCommandLineParser.ts @@ -15,7 +15,7 @@ import { CommandLineParser, type IRequiredCommandLineStringParameter } from '@rushstack/ts-command-line'; -import type { IAppContext } from '@rushstack/lockfile-explorer-web/lib/AppContext'; +import type { IAppContext } from '@rushstack/lockfile-explorer-web/lib/types/IAppContext'; import type { Lockfile } from '@pnpm/lockfile-types'; import type { IAppState } from '../../state'; @@ -166,6 +166,10 @@ export class ExplorerCommandLineParser extends CommandLineParser { res.status(200).send(); }); + app.get('/api/workspace', (req: express.Request, res: express.Response) => { + res.type('application/javascript').send(appState.lfxWorkspace); + }); + app.post( '/api/package-json', async (req: express.Request<{}, {}, { projectPath: string }, {}>, res: express.Response) => { diff --git a/apps/lockfile-explorer/src/state/index.ts b/apps/lockfile-explorer/src/state/index.ts index 43b356f6ab7..d718311e17f 100644 --- a/apps/lockfile-explorer/src/state/index.ts +++ b/apps/lockfile-explorer/src/state/index.ts @@ -1,37 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -export enum ProjectType { - RUSH_PROJECT, - PNPM_WORKSPACE -} - -export interface IRushProjectDetails { - projectName: string; - projectFolder: string; -} +import type { ILfxWorkspace } from '@rushstack/lockfile-explorer-web/lib/types/lfxProtocol'; -export interface IAppStateBase { +export interface IAppState { lockfileExplorerProjectRoot: string; - currDir: string; + currentWorkingDirectory: string; projectRoot: string; - projectType: ProjectType; pnpmLockfileLocation: string; pnpmfileLocation: string; appVersion: string; debugMode: boolean; + lfxWorkspace: ILfxWorkspace; } - -export interface IRushAppState extends IAppStateBase { - projectType: ProjectType.RUSH_PROJECT; - rush: { - rushJsonPath: string; - projectsByProjectFolder: Map; - }; -} - -export interface IPnpmWorkspaceAppState extends IAppStateBase { - projectType: ProjectType.PNPM_WORKSPACE; -} - -export type IAppState = IRushAppState | IPnpmWorkspaceAppState; diff --git a/apps/lockfile-explorer/src/utils/init.ts b/apps/lockfile-explorer/src/utils/init.ts index 4a3192db6a8..0f466c20886 100644 --- a/apps/lockfile-explorer/src/utils/init.ts +++ b/apps/lockfile-explorer/src/utils/init.ts @@ -9,7 +9,7 @@ import { RushConfiguration } from '@microsoft/rush-lib/lib/api/RushConfiguration import type { Subspace } from '@microsoft/rush-lib/lib/api/Subspace'; import path from 'path'; -import { type IAppState, type IRushProjectDetails, ProjectType } from '../state'; +import type { IAppState } from '../state'; export const init = (options: { lockfileExplorerProjectRoot: string; @@ -18,14 +18,14 @@ export const init = (options: { subspaceName: string; }): IAppState => { const { lockfileExplorerProjectRoot, appVersion, debugMode, subspaceName } = options; - const currDir = process.cwd(); + const currentWorkingDirectory = process.cwd(); let appState: IAppState | undefined; - let currExploredDir = Path.convertToSlashes(currDir); - while (currExploredDir.includes('/')) { + let currentFolder = Path.convertToSlashes(currentWorkingDirectory); + while (currentFolder.includes('/')) { // Look for a rush.json [rush project] or pnpm-lock.yaml file [regular pnpm workspace] - const rushJsonPath: string = path.resolve(currExploredDir, 'rush.json'); - const pnpmLockPath: string = path.resolve(currExploredDir, 'pnpm-lock.yaml'); + const rushJsonPath: string = path.resolve(currentFolder, 'rush.json'); + const pnpmLockPath: string = path.resolve(currentFolder, 'pnpm-lock.yaml'); if (FileSystem.exists(rushJsonPath)) { console.log('Found a Rush workspace: ', rushJsonPath); @@ -33,45 +33,45 @@ export const init = (options: { const subspace: Subspace = rushConfiguration.getSubspace(subspaceName); const workspaceFolder: string = subspace.getSubspaceTempFolderPath(); - const projectsByProjectFolder: Map = new Map(); - for (const project of rushConfiguration.projects) { - projectsByProjectFolder.set(project.projectFolder, { - projectName: project.packageName, - projectFolder: project.projectFolder - }); - } - + const pnpmLockfileLocation: string = path.resolve(workspaceFolder, 'pnpm-lock.yaml'); appState = { - currDir, + currentWorkingDirectory, appVersion, debugMode, lockfileExplorerProjectRoot, - projectType: ProjectType.RUSH_PROJECT, - pnpmLockfileLocation: path.resolve(workspaceFolder, 'pnpm-lock.yaml'), + pnpmLockfileLocation, pnpmfileLocation: path.resolve(workspaceFolder, '.pnpmfile.cjs'), - projectRoot: currExploredDir, - rush: { - rushJsonPath, - projectsByProjectFolder + projectRoot: currentFolder, + lfxWorkspace: { + workspaceRootFolder: currentFolder, + pnpmLockfilePath: Path.convertToSlashes(path.relative(currentFolder, pnpmLockfileLocation)), + rushConfig: { + rushVersion: rushConfiguration.rushConfigurationJson.rushVersion, + subspaceName: subspaceName ?? '' + } } }; break; } else if (FileSystem.exists(pnpmLockPath)) { appState = { - currDir, + currentWorkingDirectory, appVersion, debugMode, lockfileExplorerProjectRoot, - projectType: ProjectType.PNPM_WORKSPACE, - pnpmLockfileLocation: path.resolve(currExploredDir, 'pnpm-lock.yaml'), - pnpmfileLocation: path.resolve(currExploredDir, '.pnpmfile.cjs'), - projectRoot: currExploredDir + pnpmLockfileLocation: path.resolve(currentFolder, 'pnpm-lock.yaml'), + pnpmfileLocation: path.resolve(currentFolder, '.pnpmfile.cjs'), + projectRoot: currentFolder, + lfxWorkspace: { + workspaceRootFolder: currentFolder, + pnpmLockfilePath: Path.convertToSlashes(path.relative(currentFolder, pnpmLockPath)), + rushConfig: undefined + } }; break; } - currExploredDir = currExploredDir.substring(0, currExploredDir.lastIndexOf('/')); + currentFolder = currentFolder.substring(0, currentFolder.lastIndexOf('/')); } if (!appState) { From 40dea3ae8a6b1a5126f6222a454fe98e47880a6e Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:48:14 -0700 Subject: [PATCH 06/21] Add missing tslib dependency; bump to tslib 2.8.1 --- apps/lockfile-explorer-web/package.json | 7 +- .../package.json | 2 +- .../heft-web-rig-app-tutorial/package.json | 2 +- .../package.json | 2 +- .../heft-webpack-basic-tutorial/package.json | 2 +- .../config/subspaces/default/pnpm-lock.yaml | 292 +++++++++--------- .../config/subspaces/default/repo-state.json | 2 +- .../pnpm/test/repo/apps/foo/package.json | 2 +- .../package.json | 2 +- .../rush-vscode-command-webview/package.json | 2 +- 10 files changed, 158 insertions(+), 157 deletions(-) diff --git a/apps/lockfile-explorer-web/package.json b/apps/lockfile-explorer-web/package.json index d8a641df65c..c6445b31fb0 100644 --- a/apps/lockfile-explorer-web/package.json +++ b/apps/lockfile-explorer-web/package.json @@ -12,13 +12,14 @@ "_phase:test": "heft run --only test -- --clean" }, "dependencies": { - "react": "~17.0.2", - "react-dom": "~17.0.2", "@lifaon/path": "~2.1.0", "@reduxjs/toolkit": "~1.8.6", + "@rushstack/rush-themed-ui": "workspace:*", + "react-dom": "~17.0.2", "react-redux": "~8.0.4", + "react": "~17.0.2", "redux": "~4.2.0", - "@rushstack/rush-themed-ui": "workspace:*" + "tslib": "~2.8.1" }, "devDependencies": { "@rushstack/heft": "workspace:*", diff --git a/build-tests-samples/heft-storybook-react-tutorial/package.json b/build-tests-samples/heft-storybook-react-tutorial/package.json index b6c6f330ae7..b13e1793757 100644 --- a/build-tests-samples/heft-storybook-react-tutorial/package.json +++ b/build-tests-samples/heft-storybook-react-tutorial/package.json @@ -14,7 +14,7 @@ "dependencies": { "react-dom": "~17.0.2", "react": "~17.0.2", - "tslib": "~2.3.1" + "tslib": "~2.8.1" }, "devDependencies": { "@babel/core": "~7.20.0", diff --git a/build-tests-samples/heft-web-rig-app-tutorial/package.json b/build-tests-samples/heft-web-rig-app-tutorial/package.json index 9f89a388039..ebc46f5c98c 100644 --- a/build-tests-samples/heft-web-rig-app-tutorial/package.json +++ b/build-tests-samples/heft-web-rig-app-tutorial/package.json @@ -13,7 +13,7 @@ "heft-web-rig-library-tutorial": "workspace:*", "react": "~17.0.2", "react-dom": "~17.0.2", - "tslib": "~2.3.1" + "tslib": "~2.8.1" }, "devDependencies": { "@rushstack/heft": "workspace:*", diff --git a/build-tests-samples/heft-web-rig-library-tutorial/package.json b/build-tests-samples/heft-web-rig-library-tutorial/package.json index 1dffed1a3a9..9832895cac8 100644 --- a/build-tests-samples/heft-web-rig-library-tutorial/package.json +++ b/build-tests-samples/heft-web-rig-library-tutorial/package.json @@ -15,7 +15,7 @@ "dependencies": { "react": "~17.0.2", "react-dom": "~17.0.2", - "tslib": "~2.3.1" + "tslib": "~2.8.1" }, "devDependencies": { "@rushstack/heft-web-rig": "workspace:*", diff --git a/build-tests-samples/heft-webpack-basic-tutorial/package.json b/build-tests-samples/heft-webpack-basic-tutorial/package.json index 4594196400a..70a7260797c 100644 --- a/build-tests-samples/heft-webpack-basic-tutorial/package.json +++ b/build-tests-samples/heft-webpack-basic-tutorial/package.json @@ -12,7 +12,7 @@ "dependencies": { "react-dom": "~17.0.2", "react": "~17.0.2", - "tslib": "~2.3.1" + "tslib": "~2.8.1" }, "devDependencies": { "@rushstack/heft-jest-plugin": "workspace:*", diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index fc244639a7d..8331a3ef056 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -284,6 +284,9 @@ importers: redux: specifier: ~4.2.0 version: 4.2.1 + tslib: + specifier: ~2.8.1 + version: 2.8.1 devDependencies: '@rushstack/heft': specifier: workspace:* @@ -587,8 +590,8 @@ importers: specifier: ~17.0.2 version: 17.0.2(react@17.0.2) tslib: - specifier: ~2.3.1 - version: 2.3.1 + specifier: ~2.8.1 + version: 2.8.1 devDependencies: '@babel/core': specifier: ~7.20.0 @@ -760,8 +763,8 @@ importers: specifier: ~17.0.2 version: 17.0.2(react@17.0.2) tslib: - specifier: ~2.3.1 - version: 2.3.1 + specifier: ~2.8.1 + version: 2.8.1 devDependencies: '@rushstack/heft': specifier: workspace:* @@ -794,8 +797,8 @@ importers: specifier: ~17.0.2 version: 17.0.2(react@17.0.2) tslib: - specifier: ~2.3.1 - version: 2.3.1 + specifier: ~2.8.1 + version: 2.8.1 devDependencies: '@rushstack/heft': specifier: workspace:* @@ -828,8 +831,8 @@ importers: specifier: ~17.0.2 version: 17.0.2(react@17.0.2) tslib: - specifier: ~2.3.1 - version: 2.3.1 + specifier: ~2.8.1 + version: 2.8.1 devDependencies: '@rushstack/heft': specifier: workspace:* @@ -4637,8 +4640,8 @@ importers: specifier: workspace:* version: link:../vscode-shared tslib: - specifier: ~2.3.1 - version: 2.3.1 + specifier: ~2.8.1 + version: 2.8.1 devDependencies: '@rushstack/heft': specifier: workspace:* @@ -4686,8 +4689,8 @@ importers: specifier: 0.19.0 version: 0.19.0 tslib: - specifier: ~2.3.1 - version: 2.3.1 + specifier: ~2.8.1 + version: 2.8.1 devDependencies: '@rushstack/heft': specifier: workspace:* @@ -5356,7 +5359,7 @@ packages: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -5405,7 +5408,7 @@ packages: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sts' - aws-crt @@ -5452,7 +5455,7 @@ packages: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt dev: true @@ -5499,7 +5502,7 @@ packages: '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -5515,7 +5518,7 @@ packages: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 fast-xml-parser: 4.2.5 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/credential-provider-env@3.567.0: @@ -5525,7 +5528,7 @@ packages: '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/credential-provider-http@3.567.0: @@ -5540,7 +5543,7 @@ packages: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 '@smithy/util-stream': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/credential-provider-ini@3.567.0(@aws-sdk/client-sso-oidc@3.567.0)(@aws-sdk/client-sts@3.567.0): @@ -5559,7 +5562,7 @@ packages: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -5580,7 +5583,7 @@ packages: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -5595,7 +5598,7 @@ packages: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/credential-provider-sso@3.567.0(@aws-sdk/client-sso-oidc@3.567.0): @@ -5608,7 +5611,7 @@ packages: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -5624,7 +5627,7 @@ packages: '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/middleware-host-header@3.567.0: @@ -5634,7 +5637,7 @@ packages: '@aws-sdk/types': 3.567.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/middleware-logger@3.567.0: @@ -5643,7 +5646,7 @@ packages: dependencies: '@aws-sdk/types': 3.567.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/middleware-recursion-detection@3.567.0: @@ -5653,7 +5656,7 @@ packages: '@aws-sdk/types': 3.567.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/middleware-user-agent@3.567.0: @@ -5664,7 +5667,7 @@ packages: '@aws-sdk/util-endpoints': 3.567.0 '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/region-config-resolver@3.567.0: @@ -5676,7 +5679,7 @@ packages: '@smithy/types': 2.12.0 '@smithy/util-config-provider': 2.3.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/token-providers@3.567.0(@aws-sdk/client-sso-oidc@3.567.0): @@ -5690,7 +5693,7 @@ packages: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/types@3.567.0: @@ -5698,7 +5701,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/util-endpoints@3.567.0: @@ -5708,14 +5711,14 @@ packages: '@aws-sdk/types': 3.567.0 '@smithy/types': 2.12.0 '@smithy/util-endpoints': 1.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/util-locate-window@3.567.0: resolution: {integrity: sha512-o05vqq2+IdIHVqu2L28D1aVzZRkjheyQQE0kAIB+aS0fr4hYidsO2XqkXRRnhkaOxW3VN5/K/p2gxCaKt6A1XA==} engines: {node: '>=16.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/util-user-agent-browser@3.567.0: @@ -5724,7 +5727,7 @@ packages: '@aws-sdk/types': 3.567.0 '@smithy/types': 2.12.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/util-user-agent-node@3.567.0: @@ -5739,27 +5742,27 @@ packages: '@aws-sdk/types': 3.567.0 '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@aws-sdk/util-utf8-browser@3.259.0: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} dependencies: - tslib: 2.3.1 + tslib: 2.8.1 dev: true /@azure/abort-controller@2.1.0: resolution: {integrity: sha512-SYtcG13aiV7znycu6plCClWUzD9BBtfnsbIxT89nkkRvQRB4n0kuZyJJvJ7hqdKOn7x7YoGKZ9lVStLJpLnOFw==} engines: {node: '>=18.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/abort-controller@2.1.2: resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} engines: {node: '>=18.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-auth@1.7.0: @@ -5768,7 +5771,7 @@ packages: dependencies: '@azure/abort-controller': 2.1.2 '@azure/core-util': 1.8.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-auth@1.9.0: @@ -5777,7 +5780,7 @@ packages: dependencies: '@azure/abort-controller': 2.1.0 '@azure/core-util': 1.11.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-client@1.9.0: @@ -5790,7 +5793,7 @@ packages: '@azure/core-tracing': 1.2.0 '@azure/core-util': 1.8.0 '@azure/logger': 1.1.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - supports-color dev: false @@ -5805,7 +5808,7 @@ packages: '@azure/core-tracing': 1.1.0 '@azure/core-util': 1.11.0 '@azure/logger': 1.1.0 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - supports-color dev: false @@ -5828,14 +5831,14 @@ packages: '@azure/abort-controller': 2.1.2 '@azure/core-util': 1.8.0 '@azure/logger': 1.1.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-paging@1.6.0: resolution: {integrity: sha512-W8eRv7MVFx/jbbYfcRT5+pGnZ9St/P1UvOi+63vxPwuQ3y+xj+wqWTGxpkXUETv3szsqGu0msdxVtjszCeB4zA==} engines: {node: '>=18.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-rest-pipeline@1.15.0: @@ -5849,7 +5852,7 @@ packages: '@azure/logger': 1.1.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - supports-color dev: false @@ -5865,7 +5868,7 @@ packages: '@azure/logger': 1.1.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 - tslib: 2.6.2 + tslib: 2.8.1 transitivePeerDependencies: - supports-color dev: false @@ -5874,14 +5877,14 @@ packages: resolution: {integrity: sha512-MVeJvGHB4jmF7PeHhyr72vYJsBJ3ff1piHikMgRaabPAC4P3rxhf9fm42I+DixLysBunskJWhsDQD2A+O+plkQ==} engines: {node: '>=18.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-tracing@1.2.0: resolution: {integrity: sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==} engines: {node: '>=18.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-util@1.11.0: @@ -5889,7 +5892,7 @@ packages: engines: {node: '>=18.0.0'} dependencies: '@azure/abort-controller': 2.1.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-util@1.8.0: @@ -5897,7 +5900,7 @@ packages: engines: {node: '>=18.0.0'} dependencies: '@azure/abort-controller': 2.1.2 - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/core-xml@1.4.4: @@ -5905,7 +5908,7 @@ packages: engines: {node: '>=18.0.0'} dependencies: fast-xml-parser: 4.5.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/identity@4.5.0: @@ -5925,7 +5928,7 @@ packages: jws: 4.0.0 open: 8.4.2 stoppable: 1.1.0 - tslib: 2.3.1 + tslib: 2.8.1 transitivePeerDependencies: - supports-color dev: false @@ -5934,7 +5937,7 @@ packages: resolution: {integrity: sha512-BnfkfzVEsrgbVCtqq0RYRMePSH2lL/cgUUR5sYRF4yNN10zJZq/cODz0r89k3ykY83MqeM3twR292a3YBNgC3w==} engines: {node: '>=18.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@azure/msal-browser@3.27.0: @@ -5974,7 +5977,7 @@ packages: '@azure/core-xml': 1.4.4 '@azure/logger': 1.1.0 events: 3.3.0 - tslib: 2.3.1 + tslib: 2.8.1 transitivePeerDependencies: - supports-color dev: false @@ -8162,14 +8165,14 @@ packages: resolution: {integrity: sha512-l+mLfJ2VhdHjBpELLLPDaWgT7GMLynm2aqR7SttbEb6Jh7hc/7ck1MWm93RTb3gYVHYai8SENqimNcvIxHt/zg==} dependencies: '@fluentui/set-version': 8.2.14 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/dom-utilities@2.2.14: resolution: {integrity: sha512-+4DVm5sNfJh+l8fM+7ylpOkGNZkNr4X1z1uKQPzRJ1PRhlnvc6vLpWNNicGwpjTbgufSrVtGKXwP5sf++r81lg==} dependencies: '@fluentui/set-version': 8.2.14 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/font-icons-mdl2@8.5.33(@types/react@17.0.74)(react@17.0.2): @@ -8178,7 +8181,7 @@ packages: '@fluentui/set-version': 8.2.14 '@fluentui/style-utilities': 8.10.4(@types/react@17.0.74)(react@17.0.2) '@fluentui/utilities': 8.14.0(@types/react@17.0.74)(react@17.0.2) - tslib: 2.3.1 + tslib: 2.8.1 transitivePeerDependencies: - '@types/react' - react @@ -8196,13 +8199,13 @@ packages: '@fluentui/utilities': 8.14.0(@types/react@17.0.74)(react@17.0.2) '@types/react': 17.0.74 react: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/keyboard-key@0.4.14: resolution: {integrity: sha512-XzZHcyFEM20H23h3i15UpkHi2AhRBriXPGAHq0Jm98TKFppXehedjjEFuUsh+CyU5JKBhDalWp8TAQ1ArpNzow==} dependencies: - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/keyboard-keys@9.0.7: @@ -8215,7 +8218,7 @@ packages: resolution: {integrity: sha512-Si54VVK/XZQMTPT6aKE/RmqsY7uy9hERreU143Fbqtg9cf+Hr4iJ7FOGC4dXCfrFIXs0KvIHXCh5mtfrEW2aRQ==} dependencies: '@fluentui/set-version': 8.2.14 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/priority-overflow@9.1.11: @@ -8635,7 +8638,7 @@ packages: '@fluentui/utilities': 8.14.0(@types/react@17.0.74)(react@17.0.2) '@types/react': 17.0.74 react: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/react-hooks@8.6.37(@types/react@17.0.74)(react@17.0.2): @@ -8649,7 +8652,7 @@ packages: '@fluentui/utilities': 8.14.0(@types/react@17.0.74)(react@17.0.2) '@types/react': 17.0.74 react: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/react-icons@2.0.232(react@17.0.2): @@ -8659,7 +8662,7 @@ packages: dependencies: '@griffel/react': 1.5.20(react@17.0.2) react: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/react-image@9.1.62(@types/react-dom@17.0.25)(@types/react@17.0.74)(react-dom@17.0.2)(react@17.0.2): @@ -9453,7 +9456,7 @@ packages: '@fluentui/set-version': 8.2.14 '@types/react': 17.0.74 react: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/react@8.115.7(@types/react-dom@17.0.25)(@types/react@17.0.74)(react-dom@17.0.2)(react@17.0.2): @@ -9481,13 +9484,13 @@ packages: '@types/react-dom': 17.0.25 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/set-version@8.2.14: resolution: {integrity: sha512-f/QWJnSeyfAjGAqq57yjMb6a5ejPlwfzdExPmzFBuEOuupi8hHbV8Yno12XJcTW4I0KXEQGw+PUaM1aOf/j7jw==} dependencies: - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/style-utilities@8.10.4(@types/react@17.0.74)(react@17.0.2): @@ -9498,7 +9501,7 @@ packages: '@fluentui/theme': 2.6.42(@types/react@17.0.74)(react@17.0.2) '@fluentui/utilities': 8.14.0(@types/react@17.0.74)(react@17.0.2) '@microsoft/load-themed-styles': 1.10.295 - tslib: 2.3.1 + tslib: 2.8.1 transitivePeerDependencies: - '@types/react' - react @@ -9515,7 +9518,7 @@ packages: '@fluentui/utilities': 8.14.0(@types/react@17.0.74)(react@17.0.2) '@types/react': 17.0.74 react: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@fluentui/tokens@1.0.0-alpha.16: @@ -9535,7 +9538,7 @@ packages: '@fluentui/set-version': 8.2.14 '@types/react': 17.0.74 react: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@gar/promisify@1.1.3: @@ -9550,7 +9553,7 @@ packages: csstype: 3.1.3 rtl-css-js: 1.16.1 stylis: 4.3.1 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@griffel/react@1.5.20(react@17.0.2): @@ -9560,7 +9563,7 @@ packages: dependencies: '@griffel/core': 1.15.2 react: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /@griffel/style-types@1.0.3: @@ -10133,33 +10136,33 @@ packages: dependencies: jsep: 1.4.0 - /@jsonjoy.com/base64@1.1.2(tslib@2.3.1): + /@jsonjoy.com/base64@1.1.2(tslib@2.8.1): resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' dependencies: - tslib: 2.3.1 + tslib: 2.8.1 - /@jsonjoy.com/json-pack@1.1.0(tslib@2.3.1): + /@jsonjoy.com/json-pack@1.1.0(tslib@2.8.1): resolution: {integrity: sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' dependencies: - '@jsonjoy.com/base64': 1.1.2(tslib@2.3.1) - '@jsonjoy.com/util': 1.3.0(tslib@2.3.1) + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) + '@jsonjoy.com/util': 1.3.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 1.21.0(tslib@2.3.1) - tslib: 2.3.1 + thingies: 1.21.0(tslib@2.8.1) + tslib: 2.8.1 - /@jsonjoy.com/util@1.3.0(tslib@2.3.1): + /@jsonjoy.com/util@1.3.0(tslib@2.8.1): resolution: {integrity: sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' dependencies: - tslib: 2.3.1 + tslib: 2.8.1 /@leichtgewicht/ip-codec@2.0.4: resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} @@ -11786,7 +11789,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/config-resolver@2.2.0: @@ -11797,7 +11800,7 @@ packages: '@smithy/types': 2.12.0 '@smithy/util-config-provider': 2.3.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/core@1.4.2: @@ -11811,7 +11814,7 @@ packages: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/credential-provider-imds@2.3.0: @@ -11822,7 +11825,7 @@ packages: '@smithy/property-provider': 2.2.0 '@smithy/types': 2.12.0 '@smithy/url-parser': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/fetch-http-handler@2.5.0: @@ -11832,7 +11835,7 @@ packages: '@smithy/querystring-builder': 2.2.0 '@smithy/types': 2.12.0 '@smithy/util-base64': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/hash-node@2.2.0: @@ -11842,21 +11845,21 @@ packages: '@smithy/types': 2.12.0 '@smithy/util-buffer-from': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/invalid-dependency@2.2.0: resolution: {integrity: sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/is-array-buffer@2.2.0: resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/middleware-content-length@2.2.0: @@ -11865,7 +11868,7 @@ packages: dependencies: '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/middleware-endpoint@2.5.1: @@ -11878,7 +11881,7 @@ packages: '@smithy/types': 2.12.0 '@smithy/url-parser': 2.2.0 '@smithy/util-middleware': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/middleware-retry@2.3.1: @@ -11892,7 +11895,7 @@ packages: '@smithy/types': 2.12.0 '@smithy/util-middleware': 2.2.0 '@smithy/util-retry': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 uuid: 9.0.1 dev: true @@ -11901,7 +11904,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/middleware-stack@2.2.0: @@ -11909,7 +11912,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/node-config-provider@2.3.0: @@ -11919,7 +11922,7 @@ packages: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/node-http-handler@2.5.0: @@ -11930,7 +11933,7 @@ packages: '@smithy/protocol-http': 3.3.0 '@smithy/querystring-builder': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/property-provider@2.2.0: @@ -11938,7 +11941,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/protocol-http@3.3.0: @@ -11946,7 +11949,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/querystring-builder@2.2.0: @@ -11955,7 +11958,7 @@ packages: dependencies: '@smithy/types': 2.12.0 '@smithy/util-uri-escape': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/querystring-parser@2.2.0: @@ -11963,7 +11966,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/service-error-classification@2.1.5: @@ -11978,7 +11981,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/signature-v4@2.3.0: @@ -11991,7 +11994,7 @@ packages: '@smithy/util-middleware': 2.2.0 '@smithy/util-uri-escape': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/smithy-client@2.5.1: @@ -12003,14 +12006,14 @@ packages: '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 '@smithy/util-stream': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/types@2.12.0: resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/url-parser@2.2.0: @@ -12018,7 +12021,7 @@ packages: dependencies: '@smithy/querystring-parser': 2.2.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-base64@2.3.0: @@ -12027,20 +12030,20 @@ packages: dependencies: '@smithy/util-buffer-from': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-body-length-browser@2.2.0: resolution: {integrity: sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-body-length-node@2.3.0: resolution: {integrity: sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-buffer-from@2.2.0: @@ -12048,14 +12051,14 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/is-array-buffer': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-config-provider@2.3.0: resolution: {integrity: sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-defaults-mode-browser@2.2.1: @@ -12066,7 +12069,7 @@ packages: '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-defaults-mode-node@2.3.1: @@ -12079,7 +12082,7 @@ packages: '@smithy/property-provider': 2.2.0 '@smithy/smithy-client': 2.5.1 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-endpoints@1.2.0: @@ -12088,14 +12091,14 @@ packages: dependencies: '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-hex-encoding@2.2.0: resolution: {integrity: sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-middleware@2.2.0: @@ -12103,7 +12106,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-retry@2.2.0: @@ -12112,7 +12115,7 @@ packages: dependencies: '@smithy/service-error-classification': 2.1.5 '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-stream@2.2.0: @@ -12126,14 +12129,14 @@ packages: '@smithy/util-buffer-from': 2.2.0 '@smithy/util-hex-encoding': 2.2.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-uri-escape@2.2.0: resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==} engines: {node: '>=14.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@smithy/util-utf8@2.3.0: @@ -12141,7 +12144,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: '@smithy/util-buffer-from': 2.2.0 - tslib: 2.6.2 + tslib: 2.8.1 dev: true /@storybook/addon-actions@6.4.22(@types/react@17.0.74)(react-dom@17.0.2)(react@17.0.2): @@ -13236,7 +13239,7 @@ packages: flat-cache: 3.2.0 micromatch: 4.0.5 react-docgen-typescript: 2.2.2(typescript@5.8.2) - tslib: 2.3.1 + tslib: 2.8.1 typescript: 5.8.2 webpack: 4.47.0 transitivePeerDependencies: @@ -13578,20 +13581,20 @@ packages: /@swc/helpers@0.4.14: resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@swc/helpers@0.4.36: resolution: {integrity: sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==} dependencies: legacy-swc-helpers: /@swc/helpers@0.4.14 - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@swc/helpers@0.5.7: resolution: {integrity: sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg==} dependencies: - tslib: 2.6.2 + tslib: 2.8.1 dev: false /@swc/types@0.1.19: @@ -15939,7 +15942,7 @@ packages: resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==} engines: {node: '>=4'} dependencies: - tslib: 2.3.1 + tslib: 2.8.1 dev: true /astral-regex@1.0.0: @@ -16880,7 +16883,7 @@ packages: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} dependencies: pascal-case: 3.1.2 - tslib: 2.3.1 + tslib: 2.8.1 /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} @@ -18448,7 +18451,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.3.1 + tslib: 2.8.1 /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} @@ -18491,7 +18494,7 @@ packages: prop-types: 15.8.1 react: 17.0.2 react-is: 17.0.2 - tslib: 2.3.1 + tslib: 2.8.1 dev: true /dunder-proto@1.0.1: @@ -23968,7 +23971,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.3.1 + tslib: 2.8.1 /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} @@ -24186,10 +24189,10 @@ packages: resolution: {integrity: sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==} engines: {node: '>= 4.0.0'} dependencies: - '@jsonjoy.com/json-pack': 1.1.0(tslib@2.3.1) - '@jsonjoy.com/util': 1.3.0(tslib@2.3.1) - tree-dump: 1.0.2(tslib@2.3.1) - tslib: 2.3.1 + '@jsonjoy.com/json-pack': 1.1.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.3.0(tslib@2.8.1) + tree-dump: 1.0.2(tslib@2.8.1) + tslib: 2.8.1 /memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} @@ -24677,7 +24680,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.3.1 + tslib: 2.8.1 /node-abi@3.56.0: resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==} @@ -25333,7 +25336,7 @@ packages: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: dot-case: 3.0.4 - tslib: 2.3.1 + tslib: 2.8.1 /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -25417,7 +25420,7 @@ packages: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 - tslib: 2.3.1 + tslib: 2.8.1 /pascalcase@0.1.1: resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} @@ -26862,7 +26865,7 @@ packages: ast-types: 0.14.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.3.1 + tslib: 2.8.1 dev: true /rechoir@0.6.2: @@ -27311,7 +27314,7 @@ packages: /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.3.1 + tslib: 2.8.1 dev: false /safe-array-concat@1.1.2: @@ -28828,7 +28831,7 @@ packages: resolution: {integrity: sha512-wTPy2d6WVmU/YjT0ERY9jc+et1P/B8FoSQ4qhr1xi7liwTezRbRV6yA1pKx8kdPWmLdIOBA4fn07x9c0x/wnow==} dependencies: keyborg: 2.5.0 - tslib: 2.3.1 + tslib: 2.8.1 dev: false /tapable@1.1.3: @@ -29044,13 +29047,13 @@ packages: any-promise: 1.3.0 dev: false - /thingies@1.21.0(tslib@2.3.1): + /thingies@1.21.0(tslib@2.8.1): resolution: {integrity: sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==} engines: {node: '>=10.18'} peerDependencies: tslib: ^2 dependencies: - tslib: 2.3.1 + tslib: 2.8.1 /throat@6.0.2: resolution: {integrity: sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==} @@ -29185,13 +29188,13 @@ packages: resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} dev: true - /tree-dump@1.0.2(tslib@2.3.1): + /tree-dump@1.0.2(tslib@2.8.1): resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' dependencies: - tslib: 2.3.1 + tslib: 2.8.1 /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} @@ -29284,15 +29287,12 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - /tslib@2.3.1: - resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} - /tslib@2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} dev: true - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} /tslint@5.20.1(typescript@2.9.2): resolution: {integrity: sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==} diff --git a/common/config/subspaces/default/repo-state.json b/common/config/subspaces/default/repo-state.json index eef32ca157f..b484328badb 100644 --- a/common/config/subspaces/default/repo-state.json +++ b/common/config/subspaces/default/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "6eb7f8d5c5f93d8b7af7074cd5b2d499e9f06a76", + "pnpmShrinkwrapHash": "0514179437811ed9b568114b19d8ecf0943f75dc", "preferredVersionsHash": "61cd419c533464b580f653eb5f5a7e27fe7055ca" } diff --git a/libraries/rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json b/libraries/rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json index 7f2663a0bd7..6d963c654d1 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json +++ b/libraries/rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json @@ -2,7 +2,7 @@ "name": "foo", "version": "1.0.0", "dependencies": { - "tslib": "~2.3.1" + "tslib": "~2.8.1" }, "devDependencies": { "typescript": "~5.0.4" diff --git a/vscode-extensions/debug-certificate-manager-vscode-extension/package.json b/vscode-extensions/debug-certificate-manager-vscode-extension/package.json index 83f51d8d6be..ab9fd0b720d 100644 --- a/vscode-extensions/debug-certificate-manager-vscode-extension/package.json +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/package.json @@ -114,7 +114,7 @@ "@rushstack/node-core-library": "workspace:*", "@rushstack/terminal": "workspace:*", "@rushstack/vscode-shared": "workspace:*", - "tslib": "~2.3.1" + "tslib": "~2.8.1" }, "devDependencies": { "@rushstack/heft-vscode-extension-rig": "workspace:*", diff --git a/vscode-extensions/rush-vscode-command-webview/package.json b/vscode-extensions/rush-vscode-command-webview/package.json index c43c71bc3be..cbe5138da99 100644 --- a/vscode-extensions/rush-vscode-command-webview/package.json +++ b/vscode-extensions/rush-vscode-command-webview/package.json @@ -27,7 +27,7 @@ "react": "~17.0.2", "redux": "~4.2.0", "scheduler": "0.19.0", - "tslib": "~2.3.1" + "tslib": "~2.8.1" }, "devDependencies": { "@rushstack/heft": "workspace:*", From 7e262c041e5bea5a685596432bc54e1215f08ae8 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:11:07 -0700 Subject: [PATCH 07/21] Extract the parsing/construction from LockfileEntry.ts and LockfileDependency.ts into lfxGraphLoader.ts (preparing to later move this code from client to server) --- .../src/parsing/LockfileDependency.ts | 112 +----- .../src/parsing/LockfileEntry.ts | 94 +---- .../src/parsing/lfxGraphLoader.ts | 364 ++++++++++++++++++ .../src/parsing/readLockfile.ts | 181 +-------- .../src/parsing/test/lockfile.test.ts | 4 +- 5 files changed, 387 insertions(+), 368 deletions(-) create mode 100644 apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts index 4ece5b79c49..9c2c622242f 100644 --- a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts +++ b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts @@ -1,27 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { Path } from '@lifaon/path'; import type { LockfileEntry } from './LockfileEntry'; -export interface ILockfileNode { - dependencies?: { - [key in string]: string; - }; - devDependencies?: { - [key in string]: string; - }; - peerDependencies?: { - [key in string]: string; - }; - peerDependenciesMeta?: { - [key in string]: { - optional: boolean; - }; - }; - transitivePeerDependencies?: string[]; -} - export enum IDependencyType { DEPENDENCY, DEV_DEPENDENCY, @@ -44,91 +25,24 @@ export class LockfileDependency { public entryId: string = ''; public dependencyType: IDependencyType; public containingEntry: LockfileEntry; + public resolvedEntry: LockfileEntry | undefined; public peerDependencyMeta: { name?: string; version?: string; optional?: boolean; - }; - - public constructor( - name: string, - version: string, - dependencyType: IDependencyType, - containingEntry: LockfileEntry, - node?: ILockfileNode - ) { - this.name = name; - this.version = version; - this.dependencyType = dependencyType; - this.containingEntry = containingEntry; - this.peerDependencyMeta = {}; - - if (this.version.startsWith('link:')) { - const relativePath = this.version.substring('link:'.length); - const rootRelativePath = new Path('.').relative( - new Path(containingEntry.packageJsonFolderPath).concat(relativePath) - ); - if (!rootRelativePath) { - // eslint-disable-next-line no-console - console.error('No root relative path for dependency!', name); - return; - } - this.entryId = 'project:' + rootRelativePath.toString(); - } else if (this.version.startsWith('/')) { - this.entryId = this.version; - } else if (this.dependencyType === IDependencyType.PEER_DEPENDENCY) { - if (node?.peerDependencies) { - this.peerDependencyMeta = { - name: this.name, - version: node.peerDependencies[this.name], - optional: - node.peerDependenciesMeta && node.peerDependenciesMeta[this.name] - ? node.peerDependenciesMeta[this.name].optional - : false - }; - this.entryId = 'Peer: ' + this.name; - } else { - // eslint-disable-next-line no-console - console.error('Peer dependencies info missing!', node); - } - } else { - this.entryId = '/' + this.name + '/' + this.version; - } - } - - // node is the yaml entry that we are trying to parse - public static parseDependencies( - dependencies: LockfileDependency[], - lockfileEntry: LockfileEntry, - node: ILockfileNode - ): void { - if (node.dependencies) { - for (const [pkgName, pkgVersion] of Object.entries(node.dependencies)) { - dependencies.push( - new LockfileDependency(pkgName, pkgVersion, IDependencyType.DEPENDENCY, lockfileEntry) - ); - } - } - if (node.devDependencies) { - for (const [pkgName, pkgVersion] of Object.entries(node.devDependencies)) { - dependencies.push( - new LockfileDependency(pkgName, pkgVersion, IDependencyType.DEV_DEPENDENCY, lockfileEntry) - ); - } - } - if (node.peerDependencies) { - for (const [pkgName, pkgVersion] of Object.entries(node.peerDependencies)) { - dependencies.push( - new LockfileDependency(pkgName, pkgVersion, IDependencyType.PEER_DEPENDENCY, lockfileEntry, node) - ); - } - } - if (node.transitivePeerDependencies) { - for (const dep of node.transitivePeerDependencies) { - lockfileEntry.transitivePeerDependencies.add(dep); - } - } + } = {}; + + public constructor(options: { + name: string; + version: string; + dependencyType: IDependencyType; + containingEntry: LockfileEntry; + }) { + this.name = options.name; + this.version = options.version; + this.dependencyType = options.dependencyType; + this.containingEntry = options.containingEntry; } } diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts b/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts index f8712e4e785..cbf3d4c5e51 100644 --- a/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts +++ b/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { Path } from '@lifaon/path'; -import { type ILockfileNode, LockfileDependency } from './LockfileDependency'; +import type { LockfileDependency } from './LockfileDependency'; export enum LockfileEntryFilter { Project, @@ -11,14 +10,6 @@ export enum LockfileEntryFilter { Doppelganger } -interface IProps { - rawEntryId: string; - kind: LockfileEntryFilter; - rawYamlData: ILockfileNode; - duplicates?: Set; - subspaceName?: string; -} - /** * Represents a project or package listed in the pnpm lockfile. * @@ -43,9 +34,9 @@ interface IProps { * */ export class LockfileEntry { + public readonly kind: LockfileEntryFilter; public entryId: string = ''; - public kind: LockfileEntryFilter; - public rawEntryId: string; + public rawEntryId: string = ''; public packageJsonFolderPath: string = ''; public entryPackageName: string = ''; @@ -55,87 +46,10 @@ export class LockfileEntry { public transitivePeerDependencies: Set = new Set(); public referrers: LockfileEntry[] = []; - private static _packageEntryIdRegex: RegExp = new RegExp('/(.*)/([^/]+)$'); - public entryPackageVersion: string = ''; public entrySuffix: string = ''; - public constructor(data: IProps) { - const { rawEntryId, kind, rawYamlData, duplicates, subspaceName } = data; - this.rawEntryId = rawEntryId; + public constructor(kind: LockfileEntryFilter) { this.kind = kind; - - if (rawEntryId === '.') { - // Project Root - return; - } - - if (kind === LockfileEntryFilter.Project) { - const rootPackageJsonFolderPath = new Path(`common/temp/${subspaceName}/package.json`).dirname() || ''; - const packageJsonFolderPath = new Path('.').relative( - new Path(rootPackageJsonFolderPath).concat(rawEntryId) - ); - const packageName = new Path(rawEntryId).basename(); - - if (!packageJsonFolderPath || !packageName) { - // eslint-disable-next-line no-console - console.error('Could not construct path for entry: ', rawEntryId); - return; - } - - this.packageJsonFolderPath = packageJsonFolderPath.toString(); - this.entryId = 'project:' + this.packageJsonFolderPath; - this.entryPackageName = packageName.toString(); - if (duplicates?.has(this.entryPackageName)) { - const fullPath = new Path(rawEntryId).makeAbsolute('/').toString().substring(1); - this.displayText = `Project: ${this.entryPackageName} (${fullPath})`; - this.entryPackageName = `${this.entryPackageName} (${fullPath})`; - } else { - this.displayText = 'Project: ' + this.entryPackageName; - } - } else { - this.displayText = rawEntryId; - - const match = LockfileEntry._packageEntryIdRegex.exec(rawEntryId); - - if (match) { - const [, packageName, versionPart] = match; - this.entryPackageName = packageName; - - const underscoreIndex = versionPart.indexOf('_'); - if (underscoreIndex >= 0) { - const version = versionPart.substring(0, underscoreIndex); - const suffix = versionPart.substring(underscoreIndex + 1); - - this.entryPackageVersion = version; - this.entrySuffix = suffix; - - // /@rushstack/eslint-config/3.0.1_eslint@8.21.0+typescript@4.7.4 - // --> @rushstack/eslint-config 3.0.1 (eslint@8.21.0+typescript@4.7.4) - this.displayText = packageName + ' ' + version + ' (' + suffix + ')'; - } else { - this.entryPackageVersion = versionPart; - - // /@rushstack/eslint-config/3.0.1 - // --> @rushstack/eslint-config 3.0.1 - this.displayText = packageName + ' ' + versionPart; - } - } - - // Example: - // common/temp/default/node_modules/.pnpm - // /@babel+register@7.17.7_@babel+core@7.17.12 - // /node_modules/@babel/register - this.packageJsonFolderPath = - `common/temp/${subspaceName}/node_modules/.pnpm/` + - this.entryPackageName.replace('/', '+') + - '@' + - this.entryPackageVersion + - (this.entrySuffix ? `_${this.entrySuffix}` : '') + - '/node_modules/' + - this.entryPackageName; - } - - LockfileDependency.parseDependencies(this.dependencies, this, rawYamlData); } } diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts new file mode 100644 index 00000000000..8a130bf59a2 --- /dev/null +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts @@ -0,0 +1,364 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { LockfileEntry, LockfileEntryFilter } from './LockfileEntry'; +import { IDependencyType, LockfileDependency } from './LockfileDependency'; +import { Path } from '@lifaon/path'; + +enum PnpmLockfileVersion { + V6, + V5 +} + +export interface ILockfileImporterV6 { + dependencies?: { + [key in string]: { + specifier: string; + version: string; + }; + }; + devDependencies?: { + [key in string]: { + specifier: string; + version: string; + }; + }; +} +export interface ILockfileImporterV5 { + specifiers?: Record; + dependencies?: Record; + devDependencies?: Record; +} +export interface ILockfilePackageType { + lockfileVersion: number | string; + importers?: { + [key in string]: ILockfileImporterV5 | ILockfileImporterV6; + }; + packages?: { + [key in string]: { + resolution: { + integrity: string; + }; + dependencies?: Record; + peerDependencies?: Record; + dev: boolean; + }; + }; +} + +export interface ILockfileNode { + dependencies?: { + [key in string]: string; + }; + devDependencies?: { + [key in string]: string; + }; + peerDependencies?: { + [key in string]: string; + }; + peerDependenciesMeta?: { + [key in string]: { + optional: boolean; + }; + }; + transitivePeerDependencies?: string[]; +} + +const packageEntryIdRegex: RegExp = new RegExp('/(.*)/([^/]+)$'); + +function createLockfileDependency( + name: string, + version: string, + dependencyType: IDependencyType, + containingEntry: LockfileEntry, + node?: ILockfileNode +): LockfileDependency { + const result: LockfileDependency = new LockfileDependency({ + name, + version, + dependencyType, + containingEntry + }); + + if (result.version.startsWith('link:')) { + const relativePath = result.version.substring('link:'.length); + const rootRelativePath = new Path('.').relative( + new Path(containingEntry.packageJsonFolderPath).concat(relativePath) + ); + if (!rootRelativePath) { + // eslint-disable-next-line no-console + console.error('No root relative path for dependency!', name); + return result; + } + result.entryId = 'project:' + rootRelativePath.toString(); + } else if (result.version.startsWith('/')) { + result.entryId = result.version; + } else if (result.dependencyType === IDependencyType.PEER_DEPENDENCY) { + if (node?.peerDependencies) { + result.peerDependencyMeta = { + name: result.name, + version: node.peerDependencies[result.name], + optional: + node.peerDependenciesMeta && node.peerDependenciesMeta[result.name] + ? node.peerDependenciesMeta[result.name].optional + : false + }; + result.entryId = 'Peer: ' + result.name; + } else { + // eslint-disable-next-line no-console + console.error('Peer dependencies info missing!', node); + } + } else { + result.entryId = '/' + result.name + '/' + result.version; + } + return result; +} + +// node is the yaml entry that we are trying to parse +function parseDependencies( + dependencies: LockfileDependency[], + lockfileEntry: LockfileEntry, + node: ILockfileNode +): void { + if (node.dependencies) { + for (const [pkgName, pkgVersion] of Object.entries(node.dependencies)) { + dependencies.push( + createLockfileDependency(pkgName, pkgVersion, IDependencyType.DEPENDENCY, lockfileEntry) + ); + } + } + if (node.devDependencies) { + for (const [pkgName, pkgVersion] of Object.entries(node.devDependencies)) { + dependencies.push( + createLockfileDependency(pkgName, pkgVersion, IDependencyType.DEV_DEPENDENCY, lockfileEntry) + ); + } + } + if (node.peerDependencies) { + for (const [pkgName, pkgVersion] of Object.entries(node.peerDependencies)) { + dependencies.push( + createLockfileDependency(pkgName, pkgVersion, IDependencyType.PEER_DEPENDENCY, lockfileEntry, node) + ); + } + } + if (node.transitivePeerDependencies) { + for (const dep of node.transitivePeerDependencies) { + lockfileEntry.transitivePeerDependencies.add(dep); + } + } +} + +function createLockfileEntry(options: { + rawEntryId: string; + kind: LockfileEntryFilter; + rawYamlData: ILockfileNode; + duplicates?: Set; + subspaceName?: string; +}): LockfileEntry { + const { rawEntryId, kind, rawYamlData, duplicates, subspaceName } = options; + + const result = new LockfileEntry(kind); + + result.rawEntryId = rawEntryId; + + if (rawEntryId === '.') { + // Project Root + return result; + } + + if (kind === LockfileEntryFilter.Project) { + const rootPackageJsonFolderPath = new Path(`common/temp/${subspaceName}/package.json`).dirname() || ''; + const packageJsonFolderPath = new Path('.').relative( + new Path(rootPackageJsonFolderPath).concat(rawEntryId) + ); + const packageName = new Path(rawEntryId).basename(); + + if (!packageJsonFolderPath || !packageName) { + // eslint-disable-next-line no-console + console.error('Could not construct path for entry: ', rawEntryId); + return result; + } + + result.packageJsonFolderPath = packageJsonFolderPath.toString(); + result.entryId = 'project:' + result.packageJsonFolderPath; + result.entryPackageName = packageName.toString(); + if (duplicates?.has(result.entryPackageName)) { + const fullPath = new Path(rawEntryId).makeAbsolute('/').toString().substring(1); + result.displayText = `Project: ${result.entryPackageName} (${fullPath})`; + result.entryPackageName = `${result.entryPackageName} (${fullPath})`; + } else { + result.displayText = 'Project: ' + result.entryPackageName; + } + } else { + result.displayText = rawEntryId; + + const match = packageEntryIdRegex.exec(rawEntryId); + + if (match) { + const [, packageName, versionPart] = match; + result.entryPackageName = packageName; + + const underscoreIndex = versionPart.indexOf('_'); + if (underscoreIndex >= 0) { + const version = versionPart.substring(0, underscoreIndex); + const suffix = versionPart.substring(underscoreIndex + 1); + + result.entryPackageVersion = version; + result.entrySuffix = suffix; + + // /@rushstack/eslint-config/3.0.1_eslint@8.21.0+typescript@4.7.4 + // --> @rushstack/eslint-config 3.0.1 (eslint@8.21.0+typescript@4.7.4) + result.displayText = packageName + ' ' + version + ' (' + suffix + ')'; + } else { + result.entryPackageVersion = versionPart; + + // /@rushstack/eslint-config/3.0.1 + // --> @rushstack/eslint-config 3.0.1 + result.displayText = packageName + ' ' + versionPart; + } + } + + // Example: + // common/temp/default/node_modules/.pnpm + // /@babel+register@7.17.7_@babel+core@7.17.12 + // /node_modules/@babel/register + result.packageJsonFolderPath = + `common/temp/${subspaceName}/node_modules/.pnpm/` + + result.entryPackageName.replace('/', '+') + + '@' + + result.entryPackageVersion + + (result.entrySuffix ? `_${result.entrySuffix}` : '') + + '/node_modules/' + + result.entryPackageName; + } + + parseDependencies(result.dependencies, result, rawYamlData); + + return result; +} + +/** + * Transform any newer lockfile formats to the following format: + * [packageName]: + * specifier: ... + * version: ... + */ +function getImporterValue( + importerValue: ILockfileImporterV5 | ILockfileImporterV6, + pnpmLockfileVersion: PnpmLockfileVersion +): ILockfileImporterV5 { + if (pnpmLockfileVersion === PnpmLockfileVersion.V6) { + const v6ImporterValue = importerValue as ILockfileImporterV6; + const v5ImporterValue: ILockfileImporterV5 = { + specifiers: {}, + dependencies: {}, + devDependencies: {} + }; + for (const [depName, depDetails] of Object.entries(v6ImporterValue.dependencies || {})) { + v5ImporterValue.specifiers![depName] = depDetails.specifier; + v5ImporterValue.dependencies![depName] = depDetails.version; + } + for (const [depName, depDetails] of Object.entries(v6ImporterValue.devDependencies || {})) { + v5ImporterValue.specifiers![depName] = depDetails.specifier; + v5ImporterValue.devDependencies![depName] = depDetails.version; + } + return v5ImporterValue; + } else { + return importerValue as ILockfileImporterV5; + } +} + +/** + * Parse through the lockfile and create all the corresponding LockfileEntries and LockfileDependencies + * to construct the lockfile graph. + * + * @returns A list of all the LockfileEntries in the lockfile. + */ +export function generateLockfileGraph( + lockfile: ILockfilePackageType, + subspaceName?: string +): LockfileEntry[] { + let pnpmLockfileVersion: PnpmLockfileVersion = PnpmLockfileVersion.V5; + if (`${lockfile.lockfileVersion}`.startsWith('6')) { + pnpmLockfileVersion = PnpmLockfileVersion.V6; + } + const allEntries: LockfileEntry[] = []; + const allEntriesById: { [key in string]: LockfileEntry } = {}; + + const allImporters = []; + if (lockfile.importers) { + // Find duplicate importer names + const baseNames = new Set(); + const duplicates = new Set(); + for (const importerKey of Object.keys(lockfile.importers)) { + const baseName = new Path(importerKey).basename(); + if (baseName) { + if (baseNames.has(baseName)) { + duplicates.add(baseName); + } + baseNames.add(baseName); + } + } + + for (const [importerKey, importerValue] of Object.entries(lockfile.importers)) { + // console.log('normalized importer key: ', new Path(importerKey).makeAbsolute('/').toString()); + + // const normalizedPath = new Path(importerKey).makeAbsolute('/').toString(); + const importer: LockfileEntry = createLockfileEntry({ + // entryId: normalizedPath, + rawEntryId: importerKey, + kind: LockfileEntryFilter.Project, + rawYamlData: getImporterValue(importerValue, pnpmLockfileVersion), + duplicates, + subspaceName + }); + allImporters.push(importer); + allEntries.push(importer); + allEntriesById[importer.entryId] = importer; + } + } + + const allPackages = []; + if (lockfile.packages) { + for (const [dependencyKey, dependencyValue] of Object.entries(lockfile.packages)) { + // const normalizedPath = new Path(dependencyKey).makeAbsolute('/').toString(); + + const currEntry: LockfileEntry = createLockfileEntry({ + // entryId: normalizedPath, + rawEntryId: dependencyKey, + kind: LockfileEntryFilter.Package, + rawYamlData: dependencyValue, + subspaceName + }); + + allPackages.push(currEntry); + allEntries.push(currEntry); + allEntriesById[dependencyKey] = currEntry; + } + } + + // Construct the graph + for (const entry of allEntries) { + for (const dependency of entry.dependencies) { + // Peer dependencies do not have a matching entry + if (dependency.dependencyType === IDependencyType.PEER_DEPENDENCY) { + continue; + } + + const matchedEntry = allEntriesById[dependency.entryId]; + if (matchedEntry) { + // Create a two-way link between the dependency and the entry + dependency.resolvedEntry = matchedEntry; + matchedEntry.referrers.push(entry); + } else { + if (dependency.entryId.startsWith('/')) { + // Local package + // eslint-disable-next-line no-console + console.error('Could not resolve dependency entryId: ', dependency.entryId, dependency); + } + } + } + } + + return allEntries; +} diff --git a/apps/lockfile-explorer-web/src/parsing/readLockfile.ts b/apps/lockfile-explorer-web/src/parsing/readLockfile.ts index ce78139f673..b7933a8cbed 100644 --- a/apps/lockfile-explorer-web/src/parsing/readLockfile.ts +++ b/apps/lockfile-explorer-web/src/parsing/readLockfile.ts @@ -1,187 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { LockfileEntry, LockfileEntryFilter } from './LockfileEntry'; -import { IDependencyType } from './LockfileDependency'; -import { Path } from '@lifaon/path'; +import type { LockfileEntry } from './LockfileEntry'; +import * as lfxGraphLoader from './lfxGraphLoader'; const serviceUrl: string = window.appContext.serviceUrl; -export enum PnpmLockfileVersion { - V6, - V5 -} - -export interface IPackageJsonType { - name: string; - dependencies: Record; - devDependencies: Record; -} -export interface ILockfileImporterV6 { - dependencies?: { - [key in string]: { - specifier: string; - version: string; - }; - }; - devDependencies?: { - [key in string]: { - specifier: string; - version: string; - }; - }; -} -export interface ILockfileImporterV5 { - specifiers?: Record; - dependencies?: Record; - devDependencies?: Record; -} -export interface ILockfilePackageType { - lockfileVersion: number | string; - importers?: { - [key in string]: ILockfileImporterV5 | ILockfileImporterV6; - }; - packages?: { - [key in string]: { - resolution: { - integrity: string; - }; - dependencies?: Record; - peerDependencies?: Record; - dev: boolean; - }; - }; -} - -/** - * Transform any newer lockfile formats to the following format: - * [packageName]: - * specifier: ... - * version: ... - */ -function getImporterValue( - importerValue: ILockfileImporterV5 | ILockfileImporterV6, - pnpmLockfileVersion: PnpmLockfileVersion -): ILockfileImporterV5 { - if (pnpmLockfileVersion === PnpmLockfileVersion.V6) { - const v6ImporterValue = importerValue as ILockfileImporterV6; - const v5ImporterValue: ILockfileImporterV5 = { - specifiers: {}, - dependencies: {}, - devDependencies: {} - }; - for (const [depName, depDetails] of Object.entries(v6ImporterValue.dependencies || {})) { - v5ImporterValue.specifiers![depName] = depDetails.specifier; - v5ImporterValue.dependencies![depName] = depDetails.version; - } - for (const [depName, depDetails] of Object.entries(v6ImporterValue.devDependencies || {})) { - v5ImporterValue.specifiers![depName] = depDetails.specifier; - v5ImporterValue.devDependencies![depName] = depDetails.version; - } - return v5ImporterValue; - } else { - return importerValue as ILockfileImporterV5; - } -} - -/** - * Parse through the lockfile and create all the corresponding LockfileEntries and LockfileDependencies - * to construct the lockfile graph. - * - * @returns A list of all the LockfileEntries in the lockfile. - */ -export function generateLockfileGraph( - lockfile: ILockfilePackageType, - subspaceName?: string -): LockfileEntry[] { - let pnpmLockfileVersion: PnpmLockfileVersion = PnpmLockfileVersion.V5; - if (`${lockfile.lockfileVersion}`.startsWith('6')) { - pnpmLockfileVersion = PnpmLockfileVersion.V6; - } - const allEntries: LockfileEntry[] = []; - const allEntriesById: { [key in string]: LockfileEntry } = {}; - - const allImporters = []; - if (lockfile.importers) { - // Find duplicate importer names - const baseNames = new Set(); - const duplicates = new Set(); - for (const importerKey of Object.keys(lockfile.importers)) { - const baseName = new Path(importerKey).basename(); - if (baseName) { - if (baseNames.has(baseName)) { - duplicates.add(baseName); - } - baseNames.add(baseName); - } - } - - for (const [importerKey, importerValue] of Object.entries(lockfile.importers)) { - // console.log('normalized importer key: ', new Path(importerKey).makeAbsolute('/').toString()); - - // const normalizedPath = new Path(importerKey).makeAbsolute('/').toString(); - const importer = new LockfileEntry({ - // entryId: normalizedPath, - rawEntryId: importerKey, - kind: LockfileEntryFilter.Project, - rawYamlData: getImporterValue(importerValue, pnpmLockfileVersion), - duplicates, - subspaceName - }); - allImporters.push(importer); - allEntries.push(importer); - allEntriesById[importer.entryId] = importer; - } - } - - const allPackages = []; - if (lockfile.packages) { - for (const [dependencyKey, dependencyValue] of Object.entries(lockfile.packages)) { - // const normalizedPath = new Path(dependencyKey).makeAbsolute('/').toString(); - - const currEntry = new LockfileEntry({ - // entryId: normalizedPath, - rawEntryId: dependencyKey, - kind: LockfileEntryFilter.Package, - rawYamlData: dependencyValue, - subspaceName - }); - - allPackages.push(currEntry); - allEntries.push(currEntry); - allEntriesById[dependencyKey] = currEntry; - } - } - - // Construct the graph - for (const entry of allEntries) { - for (const dependency of entry.dependencies) { - // Peer dependencies do not have a matching entry - if (dependency.dependencyType === IDependencyType.PEER_DEPENDENCY) { - continue; - } - - const matchedEntry = allEntriesById[dependency.entryId]; - if (matchedEntry) { - // Create a two-way link between the dependency and the entry - dependency.resolvedEntry = matchedEntry; - matchedEntry.referrers.push(entry); - } else { - if (dependency.entryId.startsWith('/')) { - // Local package - // eslint-disable-next-line no-console - console.error('Could not resolve dependency entryId: ', dependency.entryId, dependency); - } - } - } - } - - return allEntries; -} - export async function readLockfileAsync(): Promise { const response = await fetch(`${serviceUrl}/api/lockfile`); - const lockfile: { doc: ILockfilePackageType; subspaceName: string } = await response.json(); + const lockfile: { doc: lfxGraphLoader.ILockfilePackageType; subspaceName: string } = await response.json(); - return generateLockfileGraph(lockfile.doc, lockfile.subspaceName); + return lfxGraphLoader.generateLockfileGraph(lockfile.doc, lockfile.subspaceName); } diff --git a/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts b/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts index b2dbce9f2c7..efa76596cba 100644 --- a/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts +++ b/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts @@ -2,12 +2,12 @@ // See LICENSE in the project root for license information. import { TEST_LOCKFILE } from './testLockfile'; -import { generateLockfileGraph } from '../readLockfile'; +import * as lfxGraphLoader from '../lfxGraphLoader'; import type { LockfileEntry } from '../LockfileEntry'; describe('LockfileGeneration', () => { it('creates a valid bi-directional graph', () => { - const resolvedPackages = generateLockfileGraph(TEST_LOCKFILE); + const resolvedPackages = lfxGraphLoader.generateLockfileGraph(TEST_LOCKFILE); // Mapping of all the lockfile entries created by the lockfile const resolvedPackagesMap: { [key in string]: LockfileEntry } = {}; From 04d59819ec784bdcb2be116577ad8c3d478d4f06 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:14:09 -0700 Subject: [PATCH 08/21] Normalize TSDoc --- .../src/parsing/LockfileDependency.ts | 1 - .../src/parsing/LockfileEntry.ts | 52 +++++++++++++------ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts index 9c2c622242f..cb0361eedfd 100644 --- a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts +++ b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts @@ -17,7 +17,6 @@ export enum IDependencyType { * will link to the "containingEntry", which is the LockfileEntry that specified this dependency. * The "resolvedEntry" field is the corresponding LockfileEntry for this dependency, as all dependencies also have * their own entries in the pnpm lockfile. - * */ export class LockfileDependency { public name: string; diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts b/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts index cbf3d4c5e51..bb6a6dbd761 100644 --- a/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts +++ b/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts @@ -16,34 +16,54 @@ export enum LockfileEntryFilter { * @remarks * Each project or package will have its own LockfileEntry, which is created when the lockfile is first parsed. * The fields for the LockfileEntry are outlined below: - * - * @class LockfileEntry - * @property entryId {string} a unique (human-readable) identifier for this lockfile entry. For projects, this is just - * "Project:" + the package json path for this project. - * @property rawEntryId {string} the unique identifier assigned to this project/package in the lockfile. - * e.g. /@emotion/core/10.3.1_qjwx5m6wssz3lnb35xwkc3pz6q: - * @property kind {LockfileEntryFilter} Whether this entry is a project or a package (specified by importers or packages in the lockfile). - * @property packageJsonFolderPath {string} Where the package.json is for this project or package. - * @property entryPackageName {string} Just the name of the package with no specifiers. - * @property displayText {string} A human friendly name for the project or package. - * @property dependencies {LockfileDependency[]} A list of all the dependencies for this entry. - * Note that dependencies, dev dependencies, as well as peer dependencies are all included. - * @property transitivePeerDependencies {Set} A list of dependencies that are listed under the - * "transitivePeerDependencies" in the pnpm lockfile. - * @property referrers {LockfileEntry[]} a list of entries that specify this entry as a dependency. - * */ export class LockfileEntry { + /** + * Whether this entry is a project or a package (specified by importers or packages in the lockfile). + */ public readonly kind: LockfileEntryFilter; + + /** + * A unique (human-readable) identifier for this lockfile entry. For projects, this is just + * `Project:` + the package json path for this project. + */ public entryId: string = ''; + + /** + * The unique identifier assigned to this project/package in the lockfile. + * e.g. `/@emotion/core/10.3.1_qjwx5m6wssz3lnb35xwkc3pz6q:` + */ public rawEntryId: string = ''; + + /** + * Where the package.json is for this project or package. + */ public packageJsonFolderPath: string = ''; + /** + * Just the name of the package with no specifiers. + */ public entryPackageName: string = ''; + + /** + * A human friendly name for the project or package. + */ public displayText: string = ''; + /** + * A list of all the dependencies for this entry. + * Note that dependencies, dev dependencies, as well as peer dependencies are all included. + */ public dependencies: LockfileDependency[] = []; + + /** + * A list of dependencies that are listed under the "transitivePeerDependencies" in the pnpm lockfile. + */ public transitivePeerDependencies: Set = new Set(); + + /** + * A list of entries that specify this entry as a dependency. + */ public referrers: LockfileEntry[] = []; public entryPackageVersion: string = ''; From c8fcb5a1649e737f9eb8581f4b1494149a59f8eb Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:14:47 -0700 Subject: [PATCH 09/21] Rename IDependencyType -> DependencyKind --- .../containers/LockfileEntryDetailsView/index.tsx | 12 ++++++------ .../src/parsing/LockfileDependency.ts | 6 +++--- .../src/parsing/lfxGraphLoader.ts | 14 +++++++------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index b7b8315bab1..62c1cd553c7 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -5,7 +5,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { ScrollArea, Text } from '@rushstack/rush-themed-ui'; import styles from './styles.scss'; import appStyles from '../../App.scss'; -import { IDependencyType, type LockfileDependency } from '../../parsing/LockfileDependency'; +import { DependencyKind, type LockfileDependency } from '../../parsing/LockfileDependency'; import { readPackageJsonAsync } from '../../helpers/lfxApiClient'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { pushToStack, selectCurrentEntry } from '../../store/slices/entrySlice'; @@ -80,7 +80,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { // Check if we need to calculate influencers. // If the current dependencyToTrace is a peer dependency then we do - if (dependencyToTrace.dependencyType !== IDependencyType.PEER_DEPENDENCY) { + if (dependencyToTrace.dependencyType !== DependencyKind.PEER_DEPENDENCY) { return; } @@ -179,7 +179,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { package.json spec:{' '} - {inspectDependency.dependencyType === IDependencyType.PEER_DEPENDENCY + {inspectDependency.dependencyType === DependencyKind.PEER_DEPENDENCY ? `"${inspectDependency.peerDependencyMeta.version}" ${ inspectDependency.peerDependencyMeta.optional ? 'Optional' : 'Required' } Peer` @@ -204,7 +204,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { const renderPeerDependencies = (): JSX.Element | ReactNull => { if (!selectedEntry) return ReactNull; const peerDeps = selectedEntry.dependencies.filter( - (d) => d.dependencyType === IDependencyType.PEER_DEPENDENCY + (d) => d.dependencyType === DependencyKind.PEER_DEPENDENCY ); if (!peerDeps.length) { return ( @@ -213,7 +213,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { ); } - if (!inspectDependency || inspectDependency.dependencyType !== IDependencyType.PEER_DEPENDENCY) { + if (!inspectDependency || inspectDependency.dependencyType !== DependencyKind.PEER_DEPENDENCY) { return (
Select a peer dependency to view its influencers @@ -338,7 +338,7 @@ export const LockfileEntryDetailsView = (): JSX.Element | ReactNull => { > Name: {dependency.name}{' '} - {dependency.dependencyType === IDependencyType.PEER_DEPENDENCY + {dependency.dependencyType === DependencyKind.PEER_DEPENDENCY ? `${ dependency.peerDependencyMeta.optional ? '(Optional)' : '(Non-optional)' } Peer Dependency` diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts index cb0361eedfd..7e9226c4674 100644 --- a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts +++ b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts @@ -3,7 +3,7 @@ import type { LockfileEntry } from './LockfileEntry'; -export enum IDependencyType { +export enum DependencyKind { DEPENDENCY, DEV_DEPENDENCY, PEER_DEPENDENCY @@ -22,7 +22,7 @@ export class LockfileDependency { public name: string; public version: string; public entryId: string = ''; - public dependencyType: IDependencyType; + public dependencyType: DependencyKind; public containingEntry: LockfileEntry; public resolvedEntry: LockfileEntry | undefined; @@ -36,7 +36,7 @@ export class LockfileDependency { public constructor(options: { name: string; version: string; - dependencyType: IDependencyType; + dependencyType: DependencyKind; containingEntry: LockfileEntry; }) { this.name = options.name; diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts index 8a130bf59a2..bae11c4c948 100644 --- a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import { LockfileEntry, LockfileEntryFilter } from './LockfileEntry'; -import { IDependencyType, LockfileDependency } from './LockfileDependency'; +import { DependencyKind, LockfileDependency } from './LockfileDependency'; import { Path } from '@lifaon/path'; enum PnpmLockfileVersion { @@ -69,7 +69,7 @@ const packageEntryIdRegex: RegExp = new RegExp('/(.*)/([^/]+)$'); function createLockfileDependency( name: string, version: string, - dependencyType: IDependencyType, + dependencyType: DependencyKind, containingEntry: LockfileEntry, node?: ILockfileNode ): LockfileDependency { @@ -93,7 +93,7 @@ function createLockfileDependency( result.entryId = 'project:' + rootRelativePath.toString(); } else if (result.version.startsWith('/')) { result.entryId = result.version; - } else if (result.dependencyType === IDependencyType.PEER_DEPENDENCY) { + } else if (result.dependencyType === DependencyKind.PEER_DEPENDENCY) { if (node?.peerDependencies) { result.peerDependencyMeta = { name: result.name, @@ -123,21 +123,21 @@ function parseDependencies( if (node.dependencies) { for (const [pkgName, pkgVersion] of Object.entries(node.dependencies)) { dependencies.push( - createLockfileDependency(pkgName, pkgVersion, IDependencyType.DEPENDENCY, lockfileEntry) + createLockfileDependency(pkgName, pkgVersion, DependencyKind.DEPENDENCY, lockfileEntry) ); } } if (node.devDependencies) { for (const [pkgName, pkgVersion] of Object.entries(node.devDependencies)) { dependencies.push( - createLockfileDependency(pkgName, pkgVersion, IDependencyType.DEV_DEPENDENCY, lockfileEntry) + createLockfileDependency(pkgName, pkgVersion, DependencyKind.DEV_DEPENDENCY, lockfileEntry) ); } } if (node.peerDependencies) { for (const [pkgName, pkgVersion] of Object.entries(node.peerDependencies)) { dependencies.push( - createLockfileDependency(pkgName, pkgVersion, IDependencyType.PEER_DEPENDENCY, lockfileEntry, node) + createLockfileDependency(pkgName, pkgVersion, DependencyKind.PEER_DEPENDENCY, lockfileEntry, node) ); } } @@ -341,7 +341,7 @@ export function generateLockfileGraph( for (const entry of allEntries) { for (const dependency of entry.dependencies) { // Peer dependencies do not have a matching entry - if (dependency.dependencyType === IDependencyType.PEER_DEPENDENCY) { + if (dependency.dependencyType === DependencyKind.PEER_DEPENDENCY) { continue; } From ba8dbdf6bcafaad8fa8a78b77b2f6e9e1f781934 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:21:06 -0700 Subject: [PATCH 10/21] Consolidate LockfileDependency.ts and LockfileEntry.ts into LfxGraph.ts --- .../src/containers/BookmarksSidebar/index.tsx | 2 +- .../LockfileEntryDetailsView/index.tsx | 4 +- .../src/containers/LockfileViewer/index.tsx | 2 +- .../containers/PackageJsonViewer/index.tsx | 2 +- .../src/helpers/isEntryModified.ts | 2 +- .../src/helpers/localStorage.ts | 2 +- .../parsing/{LockfileEntry.ts => LfxGraph.ts} | 47 ++++++++++++++++++- .../src/parsing/LockfileDependency.ts | 47 ------------------- .../src/parsing/lfxGraphLoader.ts | 13 +++-- .../src/parsing/readLockfile.ts | 4 +- .../src/parsing/test/lockfile.test.ts | 4 +- .../src/store/slices/entrySlice.ts | 2 +- apps/lockfile-explorer/.vscode/launch.json | 4 +- 13 files changed, 66 insertions(+), 69 deletions(-) rename apps/lockfile-explorer-web/src/parsing/{LockfileEntry.ts => LfxGraph.ts} (61%) delete mode 100644 apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts diff --git a/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/index.tsx b/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/index.tsx index 28a2ac8eaff..3603d4dd56c 100644 --- a/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/BookmarksSidebar/index.tsx @@ -5,7 +5,7 @@ import React, { useCallback } from 'react'; import appStyles from '../../App.scss'; import styles from './styles.scss'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; -import type { LockfileEntry } from '../../parsing/LockfileEntry'; +import type { LockfileEntry } from '../../parsing/LfxGraph'; import { clearStackAndPush, removeBookmark } from '../../store/slices/entrySlice'; import { Button, ScrollArea, Text } from '@rushstack/rush-themed-ui'; diff --git a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx index 62c1cd553c7..8e9b9b83b81 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileEntryDetailsView/index.tsx @@ -5,12 +5,12 @@ import React, { useCallback, useEffect, useState } from 'react'; import { ScrollArea, Text } from '@rushstack/rush-themed-ui'; import styles from './styles.scss'; import appStyles from '../../App.scss'; -import { DependencyKind, type LockfileDependency } from '../../parsing/LockfileDependency'; +import { DependencyKind, type LockfileDependency } from '../../parsing/LfxGraph'; import { readPackageJsonAsync } from '../../helpers/lfxApiClient'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { pushToStack, selectCurrentEntry } from '../../store/slices/entrySlice'; import { ReactNull } from '../../types/ReactNull'; -import type { LockfileEntry } from '../../parsing/LockfileEntry'; +import type { LockfileEntry } from '../../parsing/LfxGraph'; import { logDiagnosticInfo } from '../../helpers/logDiagnosticInfo'; import { displaySpecChanges } from '../../helpers/displaySpecChanges'; import type { IPackageJson } from '../../types/IPackageJson'; diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index 05bc7b2ce8d..62428b7f49a 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import styles from './styles.scss'; -import { type LockfileEntry, LockfileEntryFilter } from '../../parsing/LockfileEntry'; +import { type LockfileEntry, LockfileEntryFilter } from '../../parsing/LfxGraph'; import { ReactNull } from '../../types/ReactNull'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index de3f7e1c1ed..42e97d82250 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -12,7 +12,7 @@ import { loadSpecChanges } from '../../store/slices/workspaceSlice'; import { displaySpecChanges } from '../../helpers/displaySpecChanges'; import { isEntryModified } from '../../helpers/isEntryModified'; import { ScrollArea, Tabs, Text } from '@rushstack/rush-themed-ui'; -import { LockfileEntryFilter } from '../../parsing/LockfileEntry'; +import { LockfileEntryFilter } from '../../parsing/LfxGraph'; const PackageView: { [key in string]: string } = { PACKAGE_JSON: 'PACKAGE_JSON', diff --git a/apps/lockfile-explorer-web/src/helpers/isEntryModified.ts b/apps/lockfile-explorer-web/src/helpers/isEntryModified.ts index 30c38e0830a..53cdff9e4c4 100644 --- a/apps/lockfile-explorer-web/src/helpers/isEntryModified.ts +++ b/apps/lockfile-explorer-web/src/helpers/isEntryModified.ts @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import type { ISpecChange } from '../parsing/compareSpec'; -import type { LockfileEntry } from '../parsing/LockfileEntry'; +import type { LockfileEntry } from '../parsing/LfxGraph'; export const isEntryModified = ( entry: LockfileEntry | undefined, diff --git a/apps/lockfile-explorer-web/src/helpers/localStorage.ts b/apps/lockfile-explorer-web/src/helpers/localStorage.ts index dadcc500ef8..77d703d859f 100644 --- a/apps/lockfile-explorer-web/src/helpers/localStorage.ts +++ b/apps/lockfile-explorer-web/src/helpers/localStorage.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { type LockfileEntry, LockfileEntryFilter } from '../parsing/LockfileEntry'; +import { type LockfileEntry, LockfileEntryFilter } from '../parsing/LfxGraph'; const BOOKMARK_KEY: string = 'LOCKFILE_EXPLORER_BOOKMARKS'; diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts b/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts similarity index 61% rename from apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts rename to apps/lockfile-explorer-web/src/parsing/LfxGraph.ts index bb6a6dbd761..37dbde23625 100644 --- a/apps/lockfile-explorer-web/src/parsing/LockfileEntry.ts +++ b/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts @@ -1,7 +1,48 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type { LockfileDependency } from './LockfileDependency'; +export enum DependencyKind { + DEPENDENCY, + DEV_DEPENDENCY, + PEER_DEPENDENCY +} + +/** + * Represents a dependency listed under a LockfileEntry + * + * @remarks + * Each dependency listed under a package in the lockfile should have a separate entry. These Dependencies + * will link to the "containingEntry", which is the LockfileEntry that specified this dependency. + * The "resolvedEntry" field is the corresponding LockfileEntry for this dependency, as all dependencies also have + * their own entries in the pnpm lockfile. + */ +export class LockfileDependency { + public name: string; + public version: string; + public entryId: string = ''; + public dependencyType: DependencyKind; + public containingEntry: LockfileEntry; + + public resolvedEntry: LockfileEntry | undefined; + + public peerDependencyMeta: { + name?: string; + version?: string; + optional?: boolean; + } = {}; + + public constructor(options: { + name: string; + version: string; + dependencyType: DependencyKind; + containingEntry: LockfileEntry; + }) { + this.name = options.name; + this.version = options.version; + this.dependencyType = options.dependencyType; + this.containingEntry = options.containingEntry; + } +} export enum LockfileEntryFilter { Project, @@ -73,3 +114,7 @@ export class LockfileEntry { this.kind = kind; } } + +export class LfxGraph { + public entries: LockfileEntry[] = []; +} diff --git a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts b/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts deleted file mode 100644 index 7e9226c4674..00000000000 --- a/apps/lockfile-explorer-web/src/parsing/LockfileDependency.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import type { LockfileEntry } from './LockfileEntry'; - -export enum DependencyKind { - DEPENDENCY, - DEV_DEPENDENCY, - PEER_DEPENDENCY -} - -/** - * Represents a dependency listed under a LockfileEntry - * - * @remarks - * Each dependency listed under a package in the lockfile should have a separate entry. These Dependencies - * will link to the "containingEntry", which is the LockfileEntry that specified this dependency. - * The "resolvedEntry" field is the corresponding LockfileEntry for this dependency, as all dependencies also have - * their own entries in the pnpm lockfile. - */ -export class LockfileDependency { - public name: string; - public version: string; - public entryId: string = ''; - public dependencyType: DependencyKind; - public containingEntry: LockfileEntry; - - public resolvedEntry: LockfileEntry | undefined; - - public peerDependencyMeta: { - name?: string; - version?: string; - optional?: boolean; - } = {}; - - public constructor(options: { - name: string; - version: string; - dependencyType: DependencyKind; - containingEntry: LockfileEntry; - }) { - this.name = options.name; - this.version = options.version; - this.dependencyType = options.dependencyType; - this.containingEntry = options.containingEntry; - } -} diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts index bae11c4c948..20328d431cb 100644 --- a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { LockfileEntry, LockfileEntryFilter } from './LockfileEntry'; -import { DependencyKind, LockfileDependency } from './LockfileDependency'; +import { LfxGraph, LockfileEntry, LockfileEntryFilter } from './LfxGraph'; +import { DependencyKind, LockfileDependency } from './LfxGraph'; import { Path } from '@lifaon/path'; enum PnpmLockfileVersion { @@ -274,10 +274,7 @@ function getImporterValue( * * @returns A list of all the LockfileEntries in the lockfile. */ -export function generateLockfileGraph( - lockfile: ILockfilePackageType, - subspaceName?: string -): LockfileEntry[] { +export function generateLockfileGraph(lockfile: ILockfilePackageType, subspaceName?: string): LfxGraph { let pnpmLockfileVersion: PnpmLockfileVersion = PnpmLockfileVersion.V5; if (`${lockfile.lockfileVersion}`.startsWith('6')) { pnpmLockfileVersion = PnpmLockfileVersion.V6; @@ -360,5 +357,7 @@ export function generateLockfileGraph( } } - return allEntries; + const lfxGraph: LfxGraph = new LfxGraph(); + lfxGraph.entries = allEntries; + return lfxGraph; } diff --git a/apps/lockfile-explorer-web/src/parsing/readLockfile.ts b/apps/lockfile-explorer-web/src/parsing/readLockfile.ts index b7933a8cbed..c68b9bb26d8 100644 --- a/apps/lockfile-explorer-web/src/parsing/readLockfile.ts +++ b/apps/lockfile-explorer-web/src/parsing/readLockfile.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type { LockfileEntry } from './LockfileEntry'; +import type { LockfileEntry } from './LfxGraph'; import * as lfxGraphLoader from './lfxGraphLoader'; const serviceUrl: string = window.appContext.serviceUrl; @@ -10,5 +10,5 @@ export async function readLockfileAsync(): Promise { const response = await fetch(`${serviceUrl}/api/lockfile`); const lockfile: { doc: lfxGraphLoader.ILockfilePackageType; subspaceName: string } = await response.json(); - return lfxGraphLoader.generateLockfileGraph(lockfile.doc, lockfile.subspaceName); + return lfxGraphLoader.generateLockfileGraph(lockfile.doc, lockfile.subspaceName).entries; } diff --git a/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts b/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts index efa76596cba..6ff0dd04736 100644 --- a/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts +++ b/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts @@ -3,11 +3,11 @@ import { TEST_LOCKFILE } from './testLockfile'; import * as lfxGraphLoader from '../lfxGraphLoader'; -import type { LockfileEntry } from '../LockfileEntry'; +import type { LockfileEntry } from '../LfxGraph'; describe('LockfileGeneration', () => { it('creates a valid bi-directional graph', () => { - const resolvedPackages = lfxGraphLoader.generateLockfileGraph(TEST_LOCKFILE); + const resolvedPackages = lfxGraphLoader.generateLockfileGraph(TEST_LOCKFILE).entries; // Mapping of all the lockfile entries created by the lockfile const resolvedPackagesMap: { [key in string]: LockfileEntry } = {}; diff --git a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts index 078c98cf8b6..8c6bd99c0fc 100644 --- a/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts +++ b/apps/lockfile-explorer-web/src/store/slices/entrySlice.ts @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import { createSlice, type PayloadAction, type Reducer } from '@reduxjs/toolkit'; -import { type LockfileEntry, LockfileEntryFilter } from '../../parsing/LockfileEntry'; +import { type LockfileEntry, LockfileEntryFilter } from '../../parsing/LfxGraph'; import type { RootState } from '../index'; import { getBookmarksFromStorage, diff --git a/apps/lockfile-explorer/.vscode/launch.json b/apps/lockfile-explorer/.vscode/launch.json index a0272b7d4ed..9fa0dbbad56 100644 --- a/apps/lockfile-explorer/.vscode/launch.json +++ b/apps/lockfile-explorer/.vscode/launch.json @@ -19,8 +19,8 @@ "request": "launch", "name": "Run in specified folder", "program": "${workspaceFolder}/lib/start-explorer.js", -// "cwd": "C:\\Git\\lockfile-explorer-demos", - "cwd": "(your project path)", + "cwd": "C:\\Git\\lockfile-explorer-demos", +// "cwd": "(your project path)", "args": ["--debug"], "sourceMaps": true } From aa24ba1297ed5ad865c709e21b1442b86b3feb87 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 16:33:07 -0700 Subject: [PATCH 11/21] Implement JSON serializer/deserializer --- .../src/parsing/JsonLfxGraph.ts | 48 +++++++ .../src/parsing/LfxGraph.ts | 11 +- .../src/parsing/lfxGraphSerializer.ts | 134 ++++++++++++++++++ .../src/parsing/test/serializeToJson.test.ts | 127 +++++++++++++++++ 4 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 apps/lockfile-explorer-web/src/parsing/JsonLfxGraph.ts create mode 100644 apps/lockfile-explorer-web/src/parsing/lfxGraphSerializer.ts create mode 100644 apps/lockfile-explorer-web/src/parsing/test/serializeToJson.test.ts diff --git a/apps/lockfile-explorer-web/src/parsing/JsonLfxGraph.ts b/apps/lockfile-explorer-web/src/parsing/JsonLfxGraph.ts new file mode 100644 index 00000000000..8d1c0f747ad --- /dev/null +++ b/apps/lockfile-explorer-web/src/parsing/JsonLfxGraph.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { DependencyKind, LockfileEntryFilter } from './LfxGraph'; + +export interface IJsonLfxDependency { + name: string; + version: string; + entryId: string; + dependencyType: DependencyKind; + + resolvedEntryJsonId?: number; + + peerDependencyMeta: { + name?: string; + version?: string; + optional?: boolean; + }; +} + +export interface IJsonLfxEntry { + /** + * A unique ID used when serializing graph links. + * + * @remarks + * This is just the `IJsonLfxGraph.entries` array index, but debugging is easier if we include + * it in the serialized representation. + */ + jsonId: number; + + kind: LockfileEntryFilter; + entryId: string; + rawEntryId: string; + packageJsonFolderPath: string; + entryPackageName: string; + displayText: string; + entryPackageVersion: string; + entrySuffix: string; + + // Lists + dependencies: IJsonLfxDependency[]; + transitivePeerDependencies: string[]; + referrerJsonIds: number[]; +} + +export interface IJsonLfxGraph { + entries: IJsonLfxEntry[]; +} diff --git a/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts b/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts index 37dbde23625..6919bc0f80c 100644 --- a/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts +++ b/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts @@ -19,11 +19,12 @@ export enum DependencyKind { export class LockfileDependency { public name: string; public version: string; - public entryId: string = ''; public dependencyType: DependencyKind; public containingEntry: LockfileEntry; - public resolvedEntry: LockfileEntry | undefined; + public entryId: string = ''; + + public resolvedEntry: LockfileEntry | undefined = undefined; public peerDependencyMeta: { name?: string; @@ -91,6 +92,9 @@ export class LockfileEntry { */ public displayText: string = ''; + public entryPackageVersion: string = ''; + public entrySuffix: string = ''; + /** * A list of all the dependencies for this entry. * Note that dependencies, dev dependencies, as well as peer dependencies are all included. @@ -107,9 +111,6 @@ export class LockfileEntry { */ public referrers: LockfileEntry[] = []; - public entryPackageVersion: string = ''; - public entrySuffix: string = ''; - public constructor(kind: LockfileEntryFilter) { this.kind = kind; } diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphSerializer.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphSerializer.ts new file mode 100644 index 00000000000..d7921fb9650 --- /dev/null +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphSerializer.ts @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { IJsonLfxDependency, IJsonLfxEntry, IJsonLfxGraph } from './JsonLfxGraph'; +import { LfxGraph, LockfileDependency, LockfileEntry } from './LfxGraph'; + +export function serializeToJson(graph: LfxGraph): IJsonLfxGraph { + const jsonLfxEntries: IJsonLfxEntry[] = []; + + const jsonIdByEntry: Map = new Map(); + + function toJsonId(entry: LockfileEntry): number { + const result: number | undefined = jsonIdByEntry.get(entry); + if (result === undefined) { + throw new Error('Attempt to serialize disconnected object'); + } + return result; + } + + for (const entry of graph.entries) { + const nextIndex: number = jsonLfxEntries.length; + + const jsonLfxEntry: IJsonLfxEntry = { + jsonId: nextIndex, + + kind: entry.kind, + entryId: entry.entryId, + rawEntryId: entry.rawEntryId, + packageJsonFolderPath: entry.packageJsonFolderPath, + entryPackageName: entry.entryPackageName, + displayText: entry.displayText, + entryPackageVersion: entry.entryPackageVersion, + entrySuffix: entry.entrySuffix, + + // Lists will be added in the second loop + dependencies: [], + transitivePeerDependencies: [], + referrerJsonIds: [] + }; + + jsonLfxEntries.push(jsonLfxEntry); + jsonIdByEntry.set(entry, jsonLfxEntry.jsonId); + } + + // Now that we built the jsonId lookup, we can serialize the lists. + for (let i: number = 0; i < jsonLfxEntries.length; ++i) { + const jsonLfxEntry: IJsonLfxEntry = jsonLfxEntries[i]; + const entry: LockfileEntry = graph.entries[i]; + + for (const dependency of entry.dependencies) { + const jsonLfxDependency: IJsonLfxDependency = { + name: dependency.name, + version: dependency.version, + entryId: dependency.entryId, + dependencyType: dependency.dependencyType, + peerDependencyMeta: { + name: dependency.peerDependencyMeta.name, + version: dependency.peerDependencyMeta.version, + optional: dependency.peerDependencyMeta.optional + } + }; + if (dependency.resolvedEntry) { + jsonLfxDependency.resolvedEntryJsonId = toJsonId(dependency.resolvedEntry); + } + + jsonLfxEntry.dependencies.push(jsonLfxDependency); + } + jsonLfxEntry.transitivePeerDependencies = Array.from(entry.transitivePeerDependencies); + jsonLfxEntry.referrerJsonIds = entry.referrers.map((x) => toJsonId(x)); + } + + return { entries: jsonLfxEntries }; +} + +export function deserializeFromJson(jsonLfxGraph: IJsonLfxGraph): LfxGraph { + const graph: LfxGraph = new LfxGraph(); + + const entries: LockfileEntry[] = graph.entries; + + function fromJsonId(jsonId: number): LockfileEntry { + const result: LockfileEntry | undefined = entries[jsonId]; + if (result === undefined) { + throw new Error('Invalid jsonId'); + } + return result; + } + + const jsonLfxEntries: IJsonLfxEntry[] = jsonLfxGraph.entries; + + // First create empty entries + for (const jsonLfxEntry of jsonLfxEntries) { + entries.push(new LockfileEntry(jsonLfxEntry.kind)); + } + for (let i: number = 0; i < jsonLfxEntries.length; ++i) { + const jsonLfxEntry: IJsonLfxEntry = jsonLfxEntries[i]; + const entry: LockfileEntry = graph.entries[i]; + + entry.entryId = jsonLfxEntry.entryId; + entry.rawEntryId = jsonLfxEntry.rawEntryId; + entry.packageJsonFolderPath = jsonLfxEntry.packageJsonFolderPath; + entry.entryPackageName = jsonLfxEntry.entryPackageName; + entry.displayText = jsonLfxEntry.displayText; + entry.entryPackageVersion = jsonLfxEntry.entryPackageVersion; + entry.entrySuffix = jsonLfxEntry.entrySuffix; + + for (const jsonLfxDependency of jsonLfxEntry.dependencies) { + const dependency: LockfileDependency = new LockfileDependency({ + name: jsonLfxDependency.name, + version: jsonLfxDependency.version, + dependencyType: jsonLfxDependency.dependencyType, + containingEntry: entry + }); + dependency.entryId = jsonLfxDependency.entryId; + if (jsonLfxDependency.resolvedEntryJsonId) { + dependency.resolvedEntry = fromJsonId(jsonLfxDependency.resolvedEntryJsonId); + } + dependency.peerDependencyMeta.name = jsonLfxDependency.peerDependencyMeta.name; + dependency.peerDependencyMeta.version = jsonLfxDependency.peerDependencyMeta.version; + dependency.peerDependencyMeta.optional = jsonLfxDependency.peerDependencyMeta.optional; + + entry.dependencies.push(dependency); + } + + for (const item of jsonLfxEntry.transitivePeerDependencies) { + entry.transitivePeerDependencies.add(item); + } + + for (const referrerJsonId of jsonLfxEntry.referrerJsonIds) { + entry.referrers.push(fromJsonId(referrerJsonId)); + } + } + + return graph; +} diff --git a/apps/lockfile-explorer-web/src/parsing/test/serializeToJson.test.ts b/apps/lockfile-explorer-web/src/parsing/test/serializeToJson.test.ts new file mode 100644 index 00000000000..15bb031376d --- /dev/null +++ b/apps/lockfile-explorer-web/src/parsing/test/serializeToJson.test.ts @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { TEST_LOCKFILE } from './testLockfile'; +import * as lfxGraphLoader from '../lfxGraphLoader'; +import * as lfxGraphSerializer from '../lfxGraphSerializer'; +import type { LfxGraph } from '../LfxGraph'; + +describe('serializeToJson', () => { + it('serializes a simple graph', () => { + const graph = lfxGraphLoader.generateLockfileGraph(TEST_LOCKFILE); + + expect(lfxGraphSerializer.serializeToJson(graph)).toMatchInlineSnapshot(` +Object { + "entries": Array [ + Object { + "dependencies": Array [], + "displayText": "", + "entryId": "", + "entryPackageName": "", + "entryPackageVersion": "", + "entrySuffix": "", + "jsonId": 0, + "kind": 0, + "packageJsonFolderPath": "", + "rawEntryId": ".", + "referrerJsonIds": Array [], + "transitivePeerDependencies": Array [], + }, + Object { + "dependencies": Array [ + Object { + "dependencyType": 0, + "entryId": "/@testPackage/core/1.7.1", + "name": "@testPackage/core", + "peerDependencyMeta": Object { + "name": undefined, + "optional": undefined, + "version": undefined, + }, + "resolvedEntryJsonId": 2, + "version": "1.7.1", + }, + Object { + "dependencyType": 0, + "entryId": "/@testPackage2/core/1.7.1", + "name": "@testPackage2/core", + "peerDependencyMeta": Object { + "name": undefined, + "optional": undefined, + "version": undefined, + }, + "resolvedEntryJsonId": 3, + "version": "1.7.1", + }, + ], + "displayText": "Project: testApp1", + "entryId": "project:./apps/testApp1", + "entryPackageName": "testApp1", + "entryPackageVersion": "", + "entrySuffix": "", + "jsonId": 1, + "kind": 0, + "packageJsonFolderPath": "./apps/testApp1", + "rawEntryId": "../../../apps/testApp1", + "referrerJsonIds": Array [], + "transitivePeerDependencies": Array [], + }, + Object { + "dependencies": Array [], + "displayText": "@testPackage/core 1.7.1", + "entryId": "", + "entryPackageName": "@testPackage/core", + "entryPackageVersion": "1.7.1", + "entrySuffix": "", + "jsonId": 2, + "kind": 1, + "packageJsonFolderPath": "common/temp/undefined/node_modules/.pnpm/@testPackage+core@1.7.1/node_modules/@testPackage/core", + "rawEntryId": "/@testPackage/core/1.7.1", + "referrerJsonIds": Array [ + 1, + ], + "transitivePeerDependencies": Array [], + }, + Object { + "dependencies": Array [], + "displayText": "@testPackage2/core 1.7.1", + "entryId": "", + "entryPackageName": "@testPackage2/core", + "entryPackageVersion": "1.7.1", + "entrySuffix": "", + "jsonId": 3, + "kind": 1, + "packageJsonFolderPath": "common/temp/undefined/node_modules/.pnpm/@testPackage2+core@1.7.1/node_modules/@testPackage2/core", + "rawEntryId": "/@testPackage2/core/1.7.1", + "referrerJsonIds": Array [ + 1, + ], + "transitivePeerDependencies": Array [], + }, + ], +} +`); + }); + + it('deserializes a simple graph', () => { + const originalGraph = lfxGraphLoader.generateLockfileGraph(TEST_LOCKFILE); + + const serialized: string = JSON.stringify( + lfxGraphSerializer.serializeToJson(originalGraph), + undefined, + 2 + ); + + const deserialized: LfxGraph = lfxGraphSerializer.deserializeFromJson(JSON.parse(serialized)); + + expect(deserialized.entries.length === originalGraph.entries.length); + + const reserialized: string = JSON.stringify( + lfxGraphSerializer.serializeToJson(deserialized), + undefined, + 2 + ); + + expect(reserialized).toEqual(serialized); + }); +}); From 349cead270c431f225f7f4492637cb454537c883 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 16:34:18 -0700 Subject: [PATCH 12/21] rush change --- .../rush/octogonz-lfx-fixes_2025-09-13-23-34.json | 10 ++++++++++ .../octogonz-lfx-fixes_2025-09-13-23-34.json | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 common/changes/@microsoft/rush/octogonz-lfx-fixes_2025-09-13-23-34.json create mode 100644 common/changes/@rushstack/lockfile-explorer/octogonz-lfx-fixes_2025-09-13-23-34.json diff --git a/common/changes/@microsoft/rush/octogonz-lfx-fixes_2025-09-13-23-34.json b/common/changes/@microsoft/rush/octogonz-lfx-fixes_2025-09-13-23-34.json new file mode 100644 index 00000000000..bd7ff97cb34 --- /dev/null +++ b/common/changes/@microsoft/rush/octogonz-lfx-fixes_2025-09-13-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@rushstack/lockfile-explorer/octogonz-lfx-fixes_2025-09-13-23-34.json b/common/changes/@rushstack/lockfile-explorer/octogonz-lfx-fixes_2025-09-13-23-34.json new file mode 100644 index 00000000000..d1f75fa99d2 --- /dev/null +++ b/common/changes/@rushstack/lockfile-explorer/octogonz-lfx-fixes_2025-09-13-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/lockfile-explorer", + "comment": "Refactoring to support future work", + "type": "patch" + } + ], + "packageName": "@rushstack/lockfile-explorer" +} \ No newline at end of file From b8c298b2024e229838c3a80ac32aeb0e06f6537d Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 16:42:38 -0700 Subject: [PATCH 13/21] Remove debug code --- apps/lockfile-explorer/.vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/lockfile-explorer/.vscode/launch.json b/apps/lockfile-explorer/.vscode/launch.json index 9fa0dbbad56..f1cdf885db1 100644 --- a/apps/lockfile-explorer/.vscode/launch.json +++ b/apps/lockfile-explorer/.vscode/launch.json @@ -19,8 +19,8 @@ "request": "launch", "name": "Run in specified folder", "program": "${workspaceFolder}/lib/start-explorer.js", - "cwd": "C:\\Git\\lockfile-explorer-demos", -// "cwd": "(your project path)", + //"cwd": "C:\\Git\\lockfile-explorer-demos", + "cwd": "(your project path)", "args": ["--debug"], "sourceMaps": true } From c0c6112bce6af898c567e58c32bce9af3f466db8 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 17:02:33 -0700 Subject: [PATCH 14/21] Revert version bump that broke test --- .../rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json b/libraries/rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json index 6d963c654d1..7f2663a0bd7 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json +++ b/libraries/rush-lib/src/logic/pnpm/test/repo/apps/foo/package.json @@ -2,7 +2,7 @@ "name": "foo", "version": "1.0.0", "dependencies": { - "tslib": "~2.8.1" + "tslib": "~2.3.1" }, "devDependencies": { "typescript": "~5.0.4" From b1b3e001714dd5e3fe19da74ef987d0593eb9ded Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:02:56 -0700 Subject: [PATCH 15/21] PR feedback --- .../src/helpers/lfxApiClient.ts | 18 +++++++++--------- .../src/parsing/LfxGraph.ts | 8 ++++---- .../src/parsing/lfxGraphLoader.ts | 4 ++-- .../src/types/AppContext.ts | 7 +------ apps/lockfile-explorer/src/utils/init.ts | 8 ++++---- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts b/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts index d48d4e4cd67..14d694c9785 100644 --- a/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts +++ b/apps/lockfile-explorer-web/src/helpers/lfxApiClient.ts @@ -4,11 +4,11 @@ import type { IPackageJson } from '../types/IPackageJson'; import type { ILfxWorkspace } from '../types/lfxProtocol'; -const serviceUrl: string = window.appContext.serviceUrl; +const SERVICE_URL: string = window.appContext.serviceUrl; export async function checkAliveAsync(): Promise { try { - await fetch(`${serviceUrl}/api/health`); + await fetch(`${SERVICE_URL}/api/health`); return true; } catch (e) { return false; @@ -23,20 +23,20 @@ export async function readWorkspaceConfigAsync(): Promise { let response: Response; try { - response = await fetch(`${serviceUrl}/api/workspace`); + response = await fetch(`${SERVICE_URL}/api/workspace`); if (!response.ok) { const responseText: string = await response.text(); const error = new Error( - 'The operation failed: ' + (responseText.trim() ?? 'An unknown error occurred') + 'The operation failed: ' + (responseText.trim() || 'An unknown error occurred') ); // eslint-disable-next-line no-console - console.error('readFileAsTextAsync() failed: ', error); + console.error('readWorkspaceConfigAsync() failed: ', error); throw error; } } catch (e) { // eslint-disable-next-line no-console console.error('Network error in readWorkspaceConfigAsync(): ', e); - throw new Error('Network error: ' + (e.message ?? 'An unknown error occurred')); + throw new Error('Network error: ' + (e.message || 'An unknown error occurred')); } const responseJson: ILfxWorkspace = await response.json(); @@ -50,7 +50,7 @@ export async function readWorkspaceConfigAsync(): Promise { */ export async function readPnpmfileAsync(): Promise { try { - const response = await fetch(`${serviceUrl}/api/pnpmfile`); + const response = await fetch(`${SERVICE_URL}/api/pnpmfile`); return await response.text(); } catch (e) { // eslint-disable-next-line no-console @@ -61,7 +61,7 @@ export async function readPnpmfileAsync(): Promise { export async function readPackageJsonAsync(projectPath: string): Promise { try { - const response = await fetch(`${serviceUrl}/api/package-json`, { + const response = await fetch(`${SERVICE_URL}/api/package-json`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -80,7 +80,7 @@ export async function readPackageJsonAsync(projectPath: string): Promise { try { - const response = await fetch(`${serviceUrl}/api/package-spec`, { + const response = await fetch(`${SERVICE_URL}/api/package-spec`, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts b/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts index 6919bc0f80c..f3914c07b69 100644 --- a/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts +++ b/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts @@ -17,10 +17,10 @@ export enum DependencyKind { * their own entries in the pnpm lockfile. */ export class LockfileDependency { - public name: string; - public version: string; - public dependencyType: DependencyKind; - public containingEntry: LockfileEntry; + public readonly name: string; + public readonly version: string; + public readonly dependencyType: DependencyKind; + public readonly containingEntry: LockfileEntry; public entryId: string = ''; diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts index 20328d431cb..47f90bf1666 100644 --- a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts @@ -254,11 +254,11 @@ function getImporterValue( dependencies: {}, devDependencies: {} }; - for (const [depName, depDetails] of Object.entries(v6ImporterValue.dependencies || {})) { + for (const [depName, depDetails] of Object.entries(v6ImporterValue.dependencies ?? {})) { v5ImporterValue.specifiers![depName] = depDetails.specifier; v5ImporterValue.dependencies![depName] = depDetails.version; } - for (const [depName, depDetails] of Object.entries(v6ImporterValue.devDependencies || {})) { + for (const [depName, depDetails] of Object.entries(v6ImporterValue.devDependencies ?? {})) { v5ImporterValue.specifiers![depName] = depDetails.specifier; v5ImporterValue.devDependencies![depName] = depDetails.version; } diff --git a/apps/lockfile-explorer-web/src/types/AppContext.ts b/apps/lockfile-explorer-web/src/types/AppContext.ts index 28e788cf31f..4135897126f 100644 --- a/apps/lockfile-explorer-web/src/types/AppContext.ts +++ b/apps/lockfile-explorer-web/src/types/AppContext.ts @@ -3,9 +3,4 @@ import type { IAppContext } from './IAppContext'; -declare global { - // eslint-disable-next-line @typescript-eslint/naming-convention - interface Window { - appContext: IAppContext; - } -} +declare const window: Window & { appContext: IAppContext }; diff --git a/apps/lockfile-explorer/src/utils/init.ts b/apps/lockfile-explorer/src/utils/init.ts index 0f466c20886..9ef96248623 100644 --- a/apps/lockfile-explorer/src/utils/init.ts +++ b/apps/lockfile-explorer/src/utils/init.ts @@ -18,14 +18,14 @@ export const init = (options: { subspaceName: string; }): IAppState => { const { lockfileExplorerProjectRoot, appVersion, debugMode, subspaceName } = options; - const currentWorkingDirectory = process.cwd(); + const currentWorkingDirectory: string = path.resolve(process.cwd()); let appState: IAppState | undefined; - let currentFolder = Path.convertToSlashes(currentWorkingDirectory); + let currentFolder: string = Path.convertToSlashes(currentWorkingDirectory); while (currentFolder.includes('/')) { // Look for a rush.json [rush project] or pnpm-lock.yaml file [regular pnpm workspace] - const rushJsonPath: string = path.resolve(currentFolder, 'rush.json'); - const pnpmLockPath: string = path.resolve(currentFolder, 'pnpm-lock.yaml'); + const rushJsonPath: string = currentFolder + '/rush.json'; + const pnpmLockPath: string = currentFolder + '/pnpm-lock.yaml'; if (FileSystem.exists(rushJsonPath)) { console.log('Found a Rush workspace: ', rushJsonPath); From 7f3fa162ff8a04f5b622463cf5080d396c4a4d57 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:22:19 -0700 Subject: [PATCH 16/21] PR feedback: eliminate path.resolve() and fix some Rush API docs --- apps/lockfile-explorer/src/utils/init.ts | 8 +++---- libraries/rush-lib/src/api/Subspace.ts | 27 +++++++++++++++--------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/lockfile-explorer/src/utils/init.ts b/apps/lockfile-explorer/src/utils/init.ts index 9ef96248623..7cf20374902 100644 --- a/apps/lockfile-explorer/src/utils/init.ts +++ b/apps/lockfile-explorer/src/utils/init.ts @@ -29,7 +29,7 @@ export const init = (options: { if (FileSystem.exists(rushJsonPath)) { console.log('Found a Rush workspace: ', rushJsonPath); - const rushConfiguration: RushConfiguration = RushConfiguration.tryLoadFromDefaultLocation()!; + const rushConfiguration: RushConfiguration = RushConfiguration.loadFromConfigurationFile(rushJsonPath); const subspace: Subspace = rushConfiguration.getSubspace(subspaceName); const workspaceFolder: string = subspace.getSubspaceTempFolderPath(); @@ -40,7 +40,7 @@ export const init = (options: { debugMode, lockfileExplorerProjectRoot, pnpmLockfileLocation, - pnpmfileLocation: path.resolve(workspaceFolder, '.pnpmfile.cjs'), + pnpmfileLocation: workspaceFolder + '/.pnpmfile.cjs', projectRoot: currentFolder, lfxWorkspace: { workspaceRootFolder: currentFolder, @@ -58,8 +58,8 @@ export const init = (options: { appVersion, debugMode, lockfileExplorerProjectRoot, - pnpmLockfileLocation: path.resolve(currentFolder, 'pnpm-lock.yaml'), - pnpmfileLocation: path.resolve(currentFolder, '.pnpmfile.cjs'), + pnpmLockfileLocation: currentFolder + '/pnpm-lock.yaml', + pnpmfileLocation: currentFolder + '/.pnpmfile.cjs', projectRoot: currentFolder, lfxWorkspace: { workspaceRootFolder: currentFolder, diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index 34476e5046c..7ec49d40558 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -198,7 +198,10 @@ export class Subspace { * Returns the full path of the folder containing this subspace's variant-dependent configuration files * such as `pnpm-lock.yaml`. * - * Example: `common/config/subspaces/my-subspace` or `common/config/subspaces/my-subspace/variants/my-variant` + * Example (variants): `C:\MyRepo\common\config\rush\variants\my-variant` + * Example (variants and subspaces): `C:\MyRepo\common\config\subspaces\my-subspace\variants\my-variant` + * Example (subspaces): `C:\MyRepo\common\config\subspaces\my-subspace` + * Example (neither): `C:\MyRepo\common\config\rush` * @beta * * @remarks @@ -219,7 +222,8 @@ export class Subspace { /** * Returns the full path of the folder containing this subspace's configuration files such as `pnpm-lock.yaml`. * - * Example: `common/config/subspaces/my-subspace` + * Example (subspaces feature enabled): `C:\MyRepo\common\config\subspaces\my-subspace` + * Example (subspaces feature disabled): `C:\MyRepo\common\config\rush` * @beta */ public getSubspaceConfigFolderPath(): string { @@ -229,8 +233,8 @@ export class Subspace { /** * Returns the full path of the folder containing this subspace's configuration files such as `pnpm-lock.yaml`. * - * Example: `common/config/subspaces/my-subspace/pnpm-patches` (subspaces feature enabled) - * Example: `common/config/pnpm-patches` (subspaces feature disabled) + * Example (subspaces feature enabled): `C:\MyRepo\common\config\subspaces\my-subspace\pnpm-patches` + * Example (subspaces feature disabled): `C:\MyRepo\common\pnpm-patches` * @beta */ public getSubspacePnpmPatchesFolderPath(): string { @@ -238,9 +242,10 @@ export class Subspace { } /** - * The folder where the subspace's node_modules and other temporary files will be stored. + * The full path of the folder where the subspace's node_modules and other temporary files will be stored. * - * Example: `common/temp/subspaces/my-subspace` + * Example (subspaces feature enabled): `C:\MyRepo\common\temp\subspaces\my-subspace` + * Example (subspaces feature disabled): `C:\MyRepo\common\temp` * @beta */ public getSubspaceTempFolderPath(): string { @@ -284,9 +289,10 @@ export class Subspace { } /** - * Gets the path to the common-versions.json config file for this subspace. + * Gets the full path to the common-versions.json config file for this subspace. * - * Example: `C:\MyRepo\common\subspaces\my-subspace\common-versions.json` + * Example (subspaces feature enabled): `C:\MyRepo\common\config\subspaces\my-subspace\common-versions.json` + * Example (subspaces feature disabled): `C:\MyRepo\common\config\rush\common-versions.json` * @beta */ public getCommonVersionsFilePath(variant?: string): string { @@ -296,9 +302,10 @@ export class Subspace { } /** - * Gets the path to the pnpm-config.json config file for this subspace. + * Gets the full path to the pnpm-config.json config file for this subspace. * - * Example: `C:\MyRepo\common\subspaces\my-subspace\pnpm-config.json` + * Example (subspaces feature enabled): `C:\MyRepo\common\config\subspaces\my-subspace\pnpm-config.json` + * Example (subspaces feature disabled): `C:\MyRepo\common\config\rush\pnpm-config.json` * @beta */ public getPnpmConfigFilePath(): string { From 0f53f013629d8506feea98371962da1a103ca6cf Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:23:41 -0700 Subject: [PATCH 17/21] Code cleanup, fix "key in string" mistake --- .../src/containers/LockfileViewer/index.tsx | 2 +- .../src/containers/PackageJsonViewer/index.tsx | 2 +- .../src/parsing/lfxGraphLoader.ts | 18 +++++++++--------- .../src/parsing/test/lockfile.test.ts | 2 +- .../src/types/IPackageJson.ts | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx index 62428b7f49a..17176f29f48 100644 --- a/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/LockfileViewer/index.tsx @@ -120,7 +120,7 @@ export const LockfileViewer = (): JSX.Element | ReactNull => { filteredEntries = entries.filter((entry) => entry.entryPackageName.indexOf(packageFilter) !== -1); } - const reducedEntries = filteredEntries.reduce((groups: { [key in string]: LockfileEntry[] }, item) => { + const reducedEntries = filteredEntries.reduce((groups: { [key: string]: LockfileEntry[] }, item) => { const group = groups[item.entryPackageName] || []; group.push(item); groups[item.entryPackageName] = group; diff --git a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx index 42e97d82250..071a3f20a8d 100644 --- a/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx +++ b/apps/lockfile-explorer-web/src/containers/PackageJsonViewer/index.tsx @@ -14,7 +14,7 @@ import { isEntryModified } from '../../helpers/isEntryModified'; import { ScrollArea, Tabs, Text } from '@rushstack/rush-themed-ui'; import { LockfileEntryFilter } from '../../parsing/LfxGraph'; -const PackageView: { [key in string]: string } = { +const PackageView: { [key: string]: string } = { PACKAGE_JSON: 'PACKAGE_JSON', PACKAGE_SPEC: 'PACKAGE_SPEC', PARSED_PACKAGE_JSON: 'PARSED_PACKAGE_JSON' diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts index 47f90bf1666..36b55a71ae9 100644 --- a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts @@ -12,13 +12,13 @@ enum PnpmLockfileVersion { export interface ILockfileImporterV6 { dependencies?: { - [key in string]: { + [key: string]: { specifier: string; version: string; }; }; devDependencies?: { - [key in string]: { + [key: string]: { specifier: string; version: string; }; @@ -32,10 +32,10 @@ export interface ILockfileImporterV5 { export interface ILockfilePackageType { lockfileVersion: number | string; importers?: { - [key in string]: ILockfileImporterV5 | ILockfileImporterV6; + [key: string]: ILockfileImporterV5 | ILockfileImporterV6; }; packages?: { - [key in string]: { + [key: string]: { resolution: { integrity: string; }; @@ -48,16 +48,16 @@ export interface ILockfilePackageType { export interface ILockfileNode { dependencies?: { - [key in string]: string; + [key: string]: string; }; devDependencies?: { - [key in string]: string; + [key: string]: string; }; peerDependencies?: { - [key in string]: string; + [key: string]: string; }; peerDependenciesMeta?: { - [key in string]: { + [key: string]: { optional: boolean; }; }; @@ -280,7 +280,7 @@ export function generateLockfileGraph(lockfile: ILockfilePackageType, subspaceNa pnpmLockfileVersion = PnpmLockfileVersion.V6; } const allEntries: LockfileEntry[] = []; - const allEntriesById: { [key in string]: LockfileEntry } = {}; + const allEntriesById: { [key: string]: LockfileEntry } = {}; const allImporters = []; if (lockfile.importers) { diff --git a/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts b/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts index 6ff0dd04736..d6cbc0626dc 100644 --- a/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts +++ b/apps/lockfile-explorer-web/src/parsing/test/lockfile.test.ts @@ -10,7 +10,7 @@ describe('LockfileGeneration', () => { const resolvedPackages = lfxGraphLoader.generateLockfileGraph(TEST_LOCKFILE).entries; // Mapping of all the lockfile entries created by the lockfile - const resolvedPackagesMap: { [key in string]: LockfileEntry } = {}; + const resolvedPackagesMap: { [key: string]: LockfileEntry } = {}; for (const resolvedPackage of resolvedPackages) { resolvedPackagesMap[resolvedPackage.entryPackageName] = resolvedPackage; } diff --git a/apps/lockfile-explorer-web/src/types/IPackageJson.ts b/apps/lockfile-explorer-web/src/types/IPackageJson.ts index 9ca29b24b7c..3f575a99f52 100644 --- a/apps/lockfile-explorer-web/src/types/IPackageJson.ts +++ b/apps/lockfile-explorer-web/src/types/IPackageJson.ts @@ -5,12 +5,12 @@ export interface IPackageJson { name: string; version: string; dependencies: { - [key in string]: string; + [key: string]: string; }; devDependencies: { - [key in string]: string; + [key: string]: string; }; peerDependencies: { - [key in string]: string; + [key: string]: string; }; } From 284abe6e65f8e1fa5602fa3c2b257953d890d5c2 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:34:18 -0700 Subject: [PATCH 18/21] Revert change that didn't compile --- apps/lockfile-explorer-web/src/types/AppContext.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/lockfile-explorer-web/src/types/AppContext.ts b/apps/lockfile-explorer-web/src/types/AppContext.ts index 4135897126f..28e788cf31f 100644 --- a/apps/lockfile-explorer-web/src/types/AppContext.ts +++ b/apps/lockfile-explorer-web/src/types/AppContext.ts @@ -3,4 +3,9 @@ import type { IAppContext } from './IAppContext'; -declare const window: Window & { appContext: IAppContext }; +declare global { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Window { + appContext: IAppContext; + } +} From ad80fc946be861baa5dce7c07dd88d1fcb32cb0b Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:57:35 -0700 Subject: [PATCH 19/21] PR feedback: Make Lfxgraph more readonly --- .../src/parsing/JsonLfxGraph.ts | 12 +-- .../src/parsing/LfxGraph.ts | 90 +++++++++++-------- .../src/parsing/lfxGraphLoader.ts | 52 +++++++---- .../src/parsing/lfxGraphSerializer.ts | 42 +++++---- .../src/parsing/test/serializeToJson.test.ts | 12 +-- 5 files changed, 127 insertions(+), 81 deletions(-) diff --git a/apps/lockfile-explorer-web/src/parsing/JsonLfxGraph.ts b/apps/lockfile-explorer-web/src/parsing/JsonLfxGraph.ts index 8d1c0f747ad..4bb82873b36 100644 --- a/apps/lockfile-explorer-web/src/parsing/JsonLfxGraph.ts +++ b/apps/lockfile-explorer-web/src/parsing/JsonLfxGraph.ts @@ -3,6 +3,12 @@ import type { DependencyKind, LockfileEntryFilter } from './LfxGraph'; +export interface IJsonPeerDependencyMeta { + name?: string; + version?: string; + optional?: boolean; +} + export interface IJsonLfxDependency { name: string; version: string; @@ -11,11 +17,7 @@ export interface IJsonLfxDependency { resolvedEntryJsonId?: number; - peerDependencyMeta: { - name?: string; - version?: string; - optional?: boolean; - }; + peerDependencyMeta: IJsonPeerDependencyMeta; } export interface IJsonLfxEntry { diff --git a/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts b/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts index f3914c07b69..8cbfd4ee7ea 100644 --- a/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts +++ b/apps/lockfile-explorer-web/src/parsing/LfxGraph.ts @@ -1,10 +1,21 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { IJsonPeerDependencyMeta } from './JsonLfxGraph'; + export enum DependencyKind { - DEPENDENCY, - DEV_DEPENDENCY, - PEER_DEPENDENCY + DEPENDENCY = 'regular', + DEV_DEPENDENCY = 'dev', + PEER_DEPENDENCY = 'peer' +} + +export interface ILockfileDependencyOptions { + name: string; + version: string; + dependencyType: DependencyKind; + containingEntry: LockfileEntry; + peerDependencyMeta: IJsonPeerDependencyMeta; + entryId: string; } /** @@ -21,35 +32,37 @@ export class LockfileDependency { public readonly version: string; public readonly dependencyType: DependencyKind; public readonly containingEntry: LockfileEntry; - - public entryId: string = ''; + public readonly entryId: string; + public readonly peerDependencyMeta: IJsonPeerDependencyMeta; public resolvedEntry: LockfileEntry | undefined = undefined; - public peerDependencyMeta: { - name?: string; - version?: string; - optional?: boolean; - } = {}; - - public constructor(options: { - name: string; - version: string; - dependencyType: DependencyKind; - containingEntry: LockfileEntry; - }) { + public constructor(options: ILockfileDependencyOptions) { this.name = options.name; this.version = options.version; this.dependencyType = options.dependencyType; this.containingEntry = options.containingEntry; + this.entryId = options.entryId; + this.peerDependencyMeta = options.peerDependencyMeta; } } export enum LockfileEntryFilter { - Project, - Package, - SideBySide, - Doppelganger + Project = 1, + Package = 2, + SideBySide = 3, + Doppelganger = 4 +} + +export interface ILockfileEntryOptions { + kind: LockfileEntryFilter; + entryId: string; + rawEntryId: string; + packageJsonFolderPath: string; + entryPackageName: string; + displayText: string; + entryPackageVersion: string; + entrySuffix: string; } /** @@ -69,53 +82,60 @@ export class LockfileEntry { * A unique (human-readable) identifier for this lockfile entry. For projects, this is just * `Project:` + the package json path for this project. */ - public entryId: string = ''; + public readonly entryId: string; /** * The unique identifier assigned to this project/package in the lockfile. * e.g. `/@emotion/core/10.3.1_qjwx5m6wssz3lnb35xwkc3pz6q:` */ - public rawEntryId: string = ''; + public readonly rawEntryId: string; /** * Where the package.json is for this project or package. */ - public packageJsonFolderPath: string = ''; + public readonly packageJsonFolderPath: string; /** * Just the name of the package with no specifiers. */ - public entryPackageName: string = ''; + public readonly entryPackageName: string; /** * A human friendly name for the project or package. */ - public displayText: string = ''; + public readonly displayText: string; - public entryPackageVersion: string = ''; - public entrySuffix: string = ''; + public readonly entryPackageVersion: string; + public readonly entrySuffix: string; /** * A list of all the dependencies for this entry. * Note that dependencies, dev dependencies, as well as peer dependencies are all included. */ - public dependencies: LockfileDependency[] = []; + public readonly dependencies: LockfileDependency[] = []; /** * A list of dependencies that are listed under the "transitivePeerDependencies" in the pnpm lockfile. */ - public transitivePeerDependencies: Set = new Set(); + public readonly transitivePeerDependencies: Set = new Set(); /** * A list of entries that specify this entry as a dependency. */ - public referrers: LockfileEntry[] = []; - - public constructor(kind: LockfileEntryFilter) { - this.kind = kind; + public readonly referrers: LockfileEntry[] = []; + + public constructor(options: ILockfileEntryOptions) { + this.kind = options.kind; + this.entryId = options.entryId; + this.rawEntryId = options.rawEntryId; + this.packageJsonFolderPath = options.packageJsonFolderPath; + this.entryPackageName = options.entryPackageName; + this.displayText = options.displayText; + this.entryPackageVersion = options.entryPackageVersion; + this.entrySuffix = options.entrySuffix; } } export class LfxGraph { - public entries: LockfileEntry[] = []; + public readonly entries: LockfileEntry[] = []; } diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts index 36b55a71ae9..1ae37e32d2f 100644 --- a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts @@ -1,7 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { LfxGraph, LockfileEntry, LockfileEntryFilter } from './LfxGraph'; +import { + type ILockfileDependencyOptions, + type ILockfileEntryOptions, + LfxGraph, + LockfileEntry, + LockfileEntryFilter +} from './LfxGraph'; import { DependencyKind, LockfileDependency } from './LfxGraph'; import { Path } from '@lifaon/path'; @@ -73,26 +79,28 @@ function createLockfileDependency( containingEntry: LockfileEntry, node?: ILockfileNode ): LockfileDependency { - const result: LockfileDependency = new LockfileDependency({ + const result: ILockfileDependencyOptions = { name, version, dependencyType, - containingEntry - }); + containingEntry, + entryId: '', + peerDependencyMeta: {} + }; - if (result.version.startsWith('link:')) { - const relativePath = result.version.substring('link:'.length); + if (version.startsWith('link:')) { + const relativePath = version.substring('link:'.length); const rootRelativePath = new Path('.').relative( new Path(containingEntry.packageJsonFolderPath).concat(relativePath) ); if (!rootRelativePath) { // eslint-disable-next-line no-console console.error('No root relative path for dependency!', name); - return result; + return new LockfileDependency(result); } result.entryId = 'project:' + rootRelativePath.toString(); } else if (result.version.startsWith('/')) { - result.entryId = result.version; + result.entryId = version; } else if (result.dependencyType === DependencyKind.PEER_DEPENDENCY) { if (node?.peerDependencies) { result.peerDependencyMeta = { @@ -111,7 +119,7 @@ function createLockfileDependency( } else { result.entryId = '/' + result.name + '/' + result.version; } - return result; + return new LockfileDependency(result); } // node is the yaml entry that we are trying to parse @@ -157,13 +165,22 @@ function createLockfileEntry(options: { }): LockfileEntry { const { rawEntryId, kind, rawYamlData, duplicates, subspaceName } = options; - const result = new LockfileEntry(kind); + const result: ILockfileEntryOptions = { + kind, + entryId: '', + rawEntryId: '', + packageJsonFolderPath: '', + entryPackageName: '', + displayText: '', + entryPackageVersion: '', + entrySuffix: '' + }; result.rawEntryId = rawEntryId; if (rawEntryId === '.') { // Project Root - return result; + return new LockfileEntry(result); } if (kind === LockfileEntryFilter.Project) { @@ -176,7 +193,7 @@ function createLockfileEntry(options: { if (!packageJsonFolderPath || !packageName) { // eslint-disable-next-line no-console console.error('Could not construct path for entry: ', rawEntryId); - return result; + return new LockfileEntry(result); } result.packageJsonFolderPath = packageJsonFolderPath.toString(); @@ -232,9 +249,9 @@ function createLockfileEntry(options: { result.entryPackageName; } - parseDependencies(result.dependencies, result, rawYamlData); - - return result; + const lockfileEntry = new LockfileEntry(result); + parseDependencies(lockfileEntry.dependencies, lockfileEntry, rawYamlData); + return lockfileEntry; } /** @@ -279,7 +296,8 @@ export function generateLockfileGraph(lockfile: ILockfilePackageType, subspaceNa if (`${lockfile.lockfileVersion}`.startsWith('6')) { pnpmLockfileVersion = PnpmLockfileVersion.V6; } - const allEntries: LockfileEntry[] = []; + const lfxGraph: LfxGraph = new LfxGraph(); + const allEntries: LockfileEntry[] = lfxGraph.entries; const allEntriesById: { [key: string]: LockfileEntry } = {}; const allImporters = []; @@ -357,7 +375,5 @@ export function generateLockfileGraph(lockfile: ILockfilePackageType, subspaceNa } } - const lfxGraph: LfxGraph = new LfxGraph(); - lfxGraph.entries = allEntries; return lfxGraph; } diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphSerializer.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphSerializer.ts index d7921fb9650..30a0e19fa53 100644 --- a/apps/lockfile-explorer-web/src/parsing/lfxGraphSerializer.ts +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphSerializer.ts @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import type { IJsonLfxDependency, IJsonLfxEntry, IJsonLfxGraph } from './JsonLfxGraph'; -import { LfxGraph, LockfileDependency, LockfileEntry } from './LfxGraph'; +import { type ILockfileEntryOptions, LfxGraph, LockfileDependency, LockfileEntry } from './LfxGraph'; export function serializeToJson(graph: LfxGraph): IJsonLfxGraph { const jsonLfxEntries: IJsonLfxEntry[] = []; @@ -17,6 +17,7 @@ export function serializeToJson(graph: LfxGraph): IJsonLfxGraph { return result; } + // First create the jsonId mapping for (const entry of graph.entries) { const nextIndex: number = jsonLfxEntries.length; @@ -42,7 +43,7 @@ export function serializeToJson(graph: LfxGraph): IJsonLfxGraph { jsonIdByEntry.set(entry, jsonLfxEntry.jsonId); } - // Now that we built the jsonId lookup, we can serialize the lists. + // Use the jsonId mapping to serialize the lists for (let i: number = 0; i < jsonLfxEntries.length; ++i) { const jsonLfxEntry: IJsonLfxEntry = jsonLfxEntries[i]; const entry: LockfileEntry = graph.entries[i]; @@ -87,36 +88,43 @@ export function deserializeFromJson(jsonLfxGraph: IJsonLfxGraph): LfxGraph { const jsonLfxEntries: IJsonLfxEntry[] = jsonLfxGraph.entries; - // First create empty entries + // First create the jsonId mapping for (const jsonLfxEntry of jsonLfxEntries) { - entries.push(new LockfileEntry(jsonLfxEntry.kind)); + const options: ILockfileEntryOptions = { + kind: jsonLfxEntry.kind, + entryId: jsonLfxEntry.entryId, + rawEntryId: jsonLfxEntry.rawEntryId, + packageJsonFolderPath: jsonLfxEntry.packageJsonFolderPath, + entryPackageName: jsonLfxEntry.entryPackageName, + displayText: jsonLfxEntry.displayText, + entryPackageVersion: jsonLfxEntry.entryPackageVersion, + entrySuffix: jsonLfxEntry.entrySuffix + }; + entries.push(new LockfileEntry(options)); } + + // Use the jsonId mapping to deserialize the lists for (let i: number = 0; i < jsonLfxEntries.length; ++i) { const jsonLfxEntry: IJsonLfxEntry = jsonLfxEntries[i]; const entry: LockfileEntry = graph.entries[i]; - entry.entryId = jsonLfxEntry.entryId; - entry.rawEntryId = jsonLfxEntry.rawEntryId; - entry.packageJsonFolderPath = jsonLfxEntry.packageJsonFolderPath; - entry.entryPackageName = jsonLfxEntry.entryPackageName; - entry.displayText = jsonLfxEntry.displayText; - entry.entryPackageVersion = jsonLfxEntry.entryPackageVersion; - entry.entrySuffix = jsonLfxEntry.entrySuffix; - for (const jsonLfxDependency of jsonLfxEntry.dependencies) { const dependency: LockfileDependency = new LockfileDependency({ name: jsonLfxDependency.name, version: jsonLfxDependency.version, dependencyType: jsonLfxDependency.dependencyType, - containingEntry: entry + containingEntry: entry, + entryId: jsonLfxDependency.entryId, + peerDependencyMeta: { + name: jsonLfxDependency.peerDependencyMeta.name, + version: jsonLfxDependency.peerDependencyMeta.version, + optional: jsonLfxDependency.peerDependencyMeta.optional + } }); - dependency.entryId = jsonLfxDependency.entryId; + if (jsonLfxDependency.resolvedEntryJsonId) { dependency.resolvedEntry = fromJsonId(jsonLfxDependency.resolvedEntryJsonId); } - dependency.peerDependencyMeta.name = jsonLfxDependency.peerDependencyMeta.name; - dependency.peerDependencyMeta.version = jsonLfxDependency.peerDependencyMeta.version; - dependency.peerDependencyMeta.optional = jsonLfxDependency.peerDependencyMeta.optional; entry.dependencies.push(dependency); } diff --git a/apps/lockfile-explorer-web/src/parsing/test/serializeToJson.test.ts b/apps/lockfile-explorer-web/src/parsing/test/serializeToJson.test.ts index 15bb031376d..2fe9915fbe8 100644 --- a/apps/lockfile-explorer-web/src/parsing/test/serializeToJson.test.ts +++ b/apps/lockfile-explorer-web/src/parsing/test/serializeToJson.test.ts @@ -21,7 +21,7 @@ Object { "entryPackageVersion": "", "entrySuffix": "", "jsonId": 0, - "kind": 0, + "kind": 1, "packageJsonFolderPath": "", "rawEntryId": ".", "referrerJsonIds": Array [], @@ -30,7 +30,7 @@ Object { Object { "dependencies": Array [ Object { - "dependencyType": 0, + "dependencyType": "regular", "entryId": "/@testPackage/core/1.7.1", "name": "@testPackage/core", "peerDependencyMeta": Object { @@ -42,7 +42,7 @@ Object { "version": "1.7.1", }, Object { - "dependencyType": 0, + "dependencyType": "regular", "entryId": "/@testPackage2/core/1.7.1", "name": "@testPackage2/core", "peerDependencyMeta": Object { @@ -60,7 +60,7 @@ Object { "entryPackageVersion": "", "entrySuffix": "", "jsonId": 1, - "kind": 0, + "kind": 1, "packageJsonFolderPath": "./apps/testApp1", "rawEntryId": "../../../apps/testApp1", "referrerJsonIds": Array [], @@ -74,7 +74,7 @@ Object { "entryPackageVersion": "1.7.1", "entrySuffix": "", "jsonId": 2, - "kind": 1, + "kind": 2, "packageJsonFolderPath": "common/temp/undefined/node_modules/.pnpm/@testPackage+core@1.7.1/node_modules/@testPackage/core", "rawEntryId": "/@testPackage/core/1.7.1", "referrerJsonIds": Array [ @@ -90,7 +90,7 @@ Object { "entryPackageVersion": "1.7.1", "entrySuffix": "", "jsonId": 3, - "kind": 1, + "kind": 2, "packageJsonFolderPath": "common/temp/undefined/node_modules/.pnpm/@testPackage2+core@1.7.1/node_modules/@testPackage2/core", "rawEntryId": "/@testPackage2/core/1.7.1", "referrerJsonIds": Array [ From a8378916605a3cce5d21a1cb83fdaf2664654f12 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 19:03:32 -0700 Subject: [PATCH 20/21] PR feedback: Detect "6.1" without accidentally detecting "60.0" --- apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts index 1ae37e32d2f..32b0061f612 100644 --- a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts @@ -293,7 +293,7 @@ function getImporterValue( */ export function generateLockfileGraph(lockfile: ILockfilePackageType, subspaceName?: string): LfxGraph { let pnpmLockfileVersion: PnpmLockfileVersion = PnpmLockfileVersion.V5; - if (`${lockfile.lockfileVersion}`.startsWith('6')) { + if (parseInt(lockfile.lockfileVersion.toString()) === 6) { pnpmLockfileVersion = PnpmLockfileVersion.V6; } const lfxGraph: LfxGraph = new LfxGraph(); From f2e332ff380bd9ba8ae8b4a829b780bd57d82446 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Sat, 13 Sep 2025 19:23:09 -0700 Subject: [PATCH 21/21] Fix lint error --- apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts index 32b0061f612..e9b02ec4e47 100644 --- a/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts +++ b/apps/lockfile-explorer-web/src/parsing/lfxGraphLoader.ts @@ -293,7 +293,7 @@ function getImporterValue( */ export function generateLockfileGraph(lockfile: ILockfilePackageType, subspaceName?: string): LfxGraph { let pnpmLockfileVersion: PnpmLockfileVersion = PnpmLockfileVersion.V5; - if (parseInt(lockfile.lockfileVersion.toString()) === 6) { + if (parseInt(lockfile.lockfileVersion.toString(), 10) === 6) { pnpmLockfileVersion = PnpmLockfileVersion.V6; } const lfxGraph: LfxGraph = new LfxGraph();