Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3e4fc31
Fix concurrency bug for max weighted operation scheduling
ethanburrelldd Sep 10, 2025
534076e
create Peekable iterator
ethanburrelldd Sep 10, 2025
ba1c694
changelog
ethanburrelldd Sep 11, 2025
c50ea4f
go back to singleton next iterator
ethanburrelldd Sep 11, 2025
5a84065
reviews
ethanburrelldd Sep 12, 2025
7688a32
reviews
ethanburrelldd Sep 16, 2025
1fce468
update changelogs
ethanburrelldd Sep 16, 2025
381b718
api reviews
ethanburrelldd Sep 16, 2025
18960cd
test cleanup before review
ethanburrelldd Sep 16, 2025
e8d82f4
Replace uuid package dependency with Node.js built-in crypto.randomUU…
Copilot Sep 16, 2025
b14a819
[lockfile-explorer] Rewrite lockfile parser to be more correct (#5363)
octogonz Sep 17, 2025
69c60b5
move allowOversubscription to command-line.json
ethanburrelldd Sep 17, 2025
5b20bcf
remove un-needed changes
ethanburrelldd Sep 17, 2025
e5e2acf
Update Node.js version in CI workflow (#5368)
bmiddha Sep 17, 2025
0cd8e4f
[rush-resolver-cache] Fix rush-lib reference (#5369)
dmichon-msft Sep 17, 2025
ec3116b
Update CI workflow: Run a second rush test step to verify build cache…
bmiddha Sep 17, 2025
db50a61
[rush-serve] Support dependencies, aborting (#5367)
dmichon-msft Sep 18, 2025
d1bd1f2
reviews
ethanburrelldd Sep 18, 2025
9bdaab2
pull bump type from version-policies
ethanburrelldd Sep 18, 2025
44f794f
change to none
ethanburrelldd Sep 19, 2025
c472ad5
[lockfile-explorer] Isolate .pnpmcfile.cjs execution and add syntax h…
octogonz Sep 21, 2025
39ed33b
Merge branch 'main' into eb/concurrency-bug-fix
octogonz Sep 25, 2025
2530f74
Change `IAsyncParallelismOptions.allowOversubscription` default to fa…
octogonz Sep 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
- NodeVersion: 20.18.x
NodeVersionDisplayName: 20
OS: ubuntu-latest
- NodeVersion: 22.12.x
- NodeVersion: 22.19.x
NodeVersionDisplayName: 22
OS: ubuntu-latest
- NodeVersion: 22.12.x
- NodeVersion: 22.19.x
NodeVersionDisplayName: 22
OS: windows-latest
name: Node.js v${{ matrix.NodeVersionDisplayName }} (${{ matrix.OS }})
Expand Down Expand Up @@ -95,3 +95,7 @@ jobs:
- name: Rush test (rush-lib)
run: node ${{ github.workspace }}/repo-a/apps/rush/lib/start-dev.js test --verbose --production --timeline
working-directory: repo-b

- name: Rush test (rush-lib) again to verify build cache hits
run: node ${{ github.workspace }}/repo-a/apps/rush/lib/start-dev.js test --verbose --production --timeline
working-directory: repo-b
1 change: 1 addition & 0 deletions apps/lockfile-explorer-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@reduxjs/toolkit": "~1.8.6",
"@rushstack/rush-themed-ui": "workspace:*",
"prism-react-renderer": "~2.4.1",
"react-dom": "~17.0.2",
"react-redux": "~8.0.4",
"react": "~17.0.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import React from 'react';

import { Highlight, themes } from 'prism-react-renderer';

// Generate this list by doing console.log(Object.keys(Prism.languages))
// BUT THEN DELETE the APIs that are bizarrely mixed into this namespace:
// "extend", "insertBefore", "DFS"
export type PrismLanguage =
| 'plain'
| 'plaintext'
| 'text'
| 'txt'
| 'markup'
| 'html'
| 'mathml'
| 'svg'
| 'xml'
| 'ssml'
| 'atom'
| 'rss'
| 'regex'
| 'clike'
| 'javascript'
| 'js'
| 'actionscript'
| 'coffeescript'
| 'coffee'
| 'javadoclike'
| 'css'
| 'yaml'
| 'yml'
| 'markdown'
| 'md'
| 'graphql'
| 'sql'
| 'typescript'
| 'ts'
| 'jsdoc'
| 'flow'
| 'n4js'
| 'n4jsd'
| 'jsx'
| 'tsx'
| 'swift'
| 'kotlin'
| 'kt'
| 'kts'
| 'c'
| 'objectivec'
| 'objc'
| 'reason'
| 'rust'
| 'go'
| 'cpp'
| 'python'
| 'py'
| 'json'
| 'webmanifest';

export const CodeBox = (props: { code: string; language: PrismLanguage }): JSX.Element => {
return (
<Highlight theme={themes.vsLight} code={props.code} language={props.language}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre style={style}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line })}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token })} />
))}
</div>
))}
</pre>
)}
</Highlight>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// See LICENSE in the project root for license information.

import React, { useCallback, useEffect, useState } from 'react';

import { readPnpmfileAsync, readPackageSpecAsync, readPackageJsonAsync } from '../../helpers/lfxApiClient';
import styles from './styles.scss';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { selectCurrentEntry } from '../../store/slices/entrySlice';
import type { IPackageJson } from '../../types/IPackageJson';
Expand All @@ -13,6 +13,9 @@ import { displaySpecChanges } from '../../helpers/displaySpecChanges';
import { isEntryModified } from '../../helpers/isEntryModified';
import { ScrollArea, Tabs, Text } from '@rushstack/rush-themed-ui';
import { LfxGraphEntryKind } from '../../packlets/lfx-shared';
import { CodeBox } from './CodeBox';

import styles from './styles.scss';

const PackageView: { [key: string]: string } = {
PACKAGE_JSON: 'PACKAGE_JSON',
Expand Down Expand Up @@ -48,9 +51,9 @@ export const PackageJsonViewer = (): JSX.Element => {

useEffect(() => {
async function loadPackageDetailsAsync(packageName: string): Promise<void> {
const packageJSONFile = await readPackageJsonAsync(packageName);
const packageJSONFile: IPackageJson | undefined = await readPackageJsonAsync(packageName);
setPackageJSON(packageJSONFile);
const parsedJSON = await readPackageSpecAsync(packageName);
const parsedJSON: IPackageJson | undefined = await readPackageSpecAsync(packageName);
setParsedPackageJSON(parsedJSON);

if (packageJSONFile && parsedJSON) {
Expand Down Expand Up @@ -161,7 +164,7 @@ export const PackageJsonViewer = (): JSX.Element => {
Please select a Project or Package to view it&apos;s package.json
</Text>
);
return <pre>{JSON.stringify(packageJSON, null, 2)}</pre>;
return <CodeBox code={JSON.stringify(packageJSON, null, 2)} language="json" />;
case PackageView.PACKAGE_SPEC:
if (!pnpmfile) {
return (
Expand All @@ -171,7 +174,7 @@ export const PackageJsonViewer = (): JSX.Element => {
</Text>
);
}
return <pre>{pnpmfile}</pre>;
return <CodeBox code={pnpmfile} language="js" />;
case PackageView.PARSED_PACKAGE_JSON:
if (!parsedPackageJSON)
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

export interface IJsonLfxWorkspaceRushConfig {
/**
* The rushVersion from rush.json.
* The `rushVersion` field from rush.json.
*/
readonly rushVersion: string;

Expand All @@ -12,23 +12,57 @@ export interface IJsonLfxWorkspaceRushConfig {
* Otherwise this will be an empty string.
*/
readonly subspaceName: string;

/**
* The path to Rush's input file `.pnpmfile.cjs`, relative to `workspaceRootFullPath`
* and normalized to use forward slashes without a leading slash. In a Rush workspace,
* {@link IJsonLfxWorkspace.pnpmfilePath} is a temporary file that is generated from `rushPnpmfilePath`.
*
* @example `"common/config/my-subspace/pnpm-lock.yaml"`
*/
readonly rushPnpmfilePath: string;
}

export interface IJsonLfxWorkspace {
/**
* Absolute path to the workspace folder that is opened by the app.
* Relative paths are generally relative to this path.
* Absolute path to the workspace folder that is opened by the app, normalized to use forward slashes
* without a trailing slash.
*
* @example `"C:/path/to/MyRepo"`
*/
readonly workspaceRootFolder: string;
readonly workspaceRootFullPath: string;

/**
* The path to the pnpm-lock.yaml file.
* The path to the "pnpm-lock.yaml" file, relative to `workspaceRootFullPath`
* and normalized to use forward slashes without a leading slash.
*
* @example `"common/temp/my-subspace/pnpm-lock.yaml"`
* @example `"pnpm-lock.yaml"`
*/
readonly pnpmLockfilePath: string;

/**
* If this is a Rush workspace (versus a plain PNPM workspace), then
* this section will be defined.
* The path to the folder of "pnpm-lock.yaml" file, relative to `workspaceRootFullPath`
* and normalized to use forward slashes without a leading slash.
*
* If `pnpm-lack.yaml` is in the `workspaceRootFullPath` folder, then pnpmLockfileFolder
* is the empty string.
*
* @example `"common/temp/my-subspace"`
* @example `""`
*/
readonly pnpmLockfileFolder: string;

/**
* The path to the `.pnpmfile.cjs` file that is loaded by PNPM. In a Rush workspace,
* this is a temporary file that is generated from `rushPnpmfilePath`.
*
* @example `"common/temp/my-subspace/.pnpmfile.cjs"`
*/
readonly pnpmfilePath: string;

/**
* This section will be defined only if this is a Rush workspace (versus a plain PNPM workspace).
*/
readonly rushConfig: IJsonLfxWorkspaceRushConfig | undefined;
}
6 changes: 3 additions & 3 deletions apps/lockfile-explorer-web/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ module.exports = function createConfig(env, argv) {
}
},
performance: {
hints: env.production ? 'error' : false
hints: env.production ? 'error' : false,
// This specifies the bundle size limit that will trigger Webpack's warning saying:
// "The following entrypoint(s) combined asset size exceeds the recommended limit."
// maxEntrypointSize: 500000,
// maxAssetSize: 500000
maxEntrypointSize: Infinity,
maxAssetSize: Infinity
},
devServer: {
port: 8096,
Expand Down
2 changes: 1 addition & 1 deletion apps/lockfile-explorer/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"name": "Single Jest test",
"program": "${workspaceFolder}/node_modules/@rushstack/heft/lib/start.js",
"cwd": "${workspaceFolder}",
"args": ["--debug", "test", "--clean", "-u", "--test-path-pattern", "lfxGraphLoader60"],
"args": ["--debug", "test", "--clean", "-u", "--test-path-pattern", "lfxGraph-website-sample-1-v6.0.test"],
"console": "integratedTerminal",
"sourceMaps": true
},
Expand Down
6 changes: 3 additions & 3 deletions apps/lockfile-explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"_phase:test": "heft run --only test -- --clean"
},
"peerDependencies": {
"@types/express": "^4.17.21"
"@types/express": "^5.0.3"
},
"peerDependenciesMeta": {
"@types/express": {
Expand All @@ -55,12 +55,12 @@
"@types/update-notifier": "~6.0.1",
"eslint": "~9.25.1",
"local-node-rig": "workspace:*",
"@pnpm/lockfile-types": "^5.1.5",
"@pnpm/lockfile.types": "1002.0.1",
"@pnpm/types": "1000.8.0",
"@types/semver": "7.5.0"
},
"dependencies": {
"tslib": "~2.8.1",
"@lifaon/path": "~2.1.0",
"@microsoft/rush-lib": "workspace:*",
"@pnpm/dependency-path-lockfile-pre-v9": "npm:@pnpm/dependency-path@~2.1.2",
"@rushstack/node-core-library": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,27 @@ import cors from 'cors';
import process from 'process';
import open from 'open';
import updateNotifier from 'update-notifier';

import * as path from 'node:path';
import { FileSystem, type IPackageJson, JsonFile, PackageJsonLookup } from '@rushstack/node-core-library';
import { ConsoleTerminalProvider, type ITerminal, Terminal, Colorize } from '@rushstack/terminal';
import {
type CommandLineFlagParameter,
CommandLineParser,
type IRequiredCommandLineStringParameter
} from '@rushstack/ts-command-line';
import type { Lockfile } from '@pnpm/lockfile-types';

import {
type LfxGraph,
lfxGraphSerializer,
type IAppContext,
type IJsonLfxGraph
type IJsonLfxGraph,
type IJsonLfxWorkspace
} from '../../../build/lfx-shared';
import * as lockfilePath from '../../graph/lockfilePath';

import type { IAppState } from '../../state';
import { init } from '../../utils/init';
import { PnpmfileRunner } from '../../graph/PnpmfileRunner';
import * as lfxGraphLoader from '../../graph/lfxGraphLoader';

const EXPLORER_TOOL_FILENAME: 'lockfile-explorer' = 'lockfile-explorer';
Expand Down Expand Up @@ -99,6 +102,8 @@ export class ExplorerCommandLineParser extends CommandLineParser {
subspaceName: this._subspaceParameter.value
});

const lfxWorkspace: IJsonLfxWorkspace = appState.lfxWorkspace;

// Important: This must happen after init() reads the current working directory
process.chdir(appState.lockfileExplorerProjectRoot);

Expand Down Expand Up @@ -152,13 +157,9 @@ export class ExplorerCommandLineParser extends CommandLineParser {

app.get('/api/graph', async (req: express.Request, res: express.Response) => {
const pnpmLockfileText: string = await FileSystem.readFileAsync(appState.pnpmLockfileLocation);
const lockfile: Lockfile = yaml.load(pnpmLockfileText) as Lockfile;
const lockfile: unknown = yaml.load(pnpmLockfileText) as unknown;

const graph: LfxGraph = lfxGraphLoader.generateLockfileGraph(
appState.lfxWorkspace,
lockfile as lfxGraphLoader.ILockfilePackageType,
appState.lfxWorkspace.rushConfig?.subspaceName ?? ''
);
const graph: LfxGraph = lfxGraphLoader.generateLockfileGraph(lockfile, lfxWorkspace);

const jsonGraph: IJsonLfxGraph = lfxGraphSerializer.serializeToJson(graph);
res.type('application/json').send(jsonGraph);
Expand Down Expand Up @@ -188,13 +189,18 @@ export class ExplorerCommandLineParser extends CommandLineParser {
);

app.get('/api/pnpmfile', async (req: express.Request, res: express.Response) => {
const pnpmfilePath: string = lockfilePath.join(
lfxWorkspace.workspaceRootFullPath,
lfxWorkspace.rushConfig?.rushPnpmfilePath ?? lfxWorkspace.pnpmfilePath
);

let pnpmfile: string;
try {
pnpmfile = await FileSystem.readFileAsync(appState.pnpmfileLocation);
pnpmfile = await FileSystem.readFileAsync(pnpmfilePath);
} catch (e) {
if (FileSystem.isNotExistError(e)) {
return res.status(404).send({
message: `Could not load pnpmfile file in this repo.`,
message: `Could not load .pnpmfile.cjs file in this repo: "${pnpmfilePath}"`,
error: `No .pnpmifile.cjs found.`
});
} else {
Expand Down Expand Up @@ -223,10 +229,18 @@ export class ExplorerCommandLineParser extends CommandLineParser {
}
}

const {
hooks: { readPackage }
} = require(appState.pnpmfileLocation);
const parsedPackage: {} = readPackage(packageJson, {});
let parsedPackage: IPackageJson = packageJson;

const pnpmfilePath: string = path.join(lfxWorkspace.workspaceRootFullPath, lfxWorkspace.pnpmfilePath);
if (await FileSystem.existsAsync(pnpmfilePath)) {
const pnpmFileRunner: PnpmfileRunner = new PnpmfileRunner(pnpmfilePath);
try {
parsedPackage = await pnpmFileRunner.transformPackageAsync(packageJson, fileLocation);
} finally {
await pnpmFileRunner.disposeAsync();
}
}

res.send(parsedPackage);
}
);
Expand Down
Loading