@rushstack/lockfile-explorer 2.1.7 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.json +38 -0
  2. package/CHANGELOG.md +15 -1
  3. package/bin/lockfile-explorer +1 -1
  4. package/bin/lockfile-lint +1 -1
  5. package/dist/app.488eeffc8a0c899a054c.css +1 -0
  6. package/dist/app.55bea5774c40f4ed1bf2.js +1 -0
  7. package/dist/index.html +1 -1
  8. package/dist/rush-themed-ui.js +2 -2
  9. package/lib-esm/assets/lint-init/lockfile-lint-template.json +42 -0
  10. package/lib-esm/cli/explorer/ExplorerCommandLineParser.js +204 -0
  11. package/lib-esm/cli/explorer/ExplorerCommandLineParser.js.map +1 -0
  12. package/lib-esm/cli/lint/LintCommandLineParser.js +31 -0
  13. package/lib-esm/cli/lint/LintCommandLineParser.js.map +1 -0
  14. package/lib-esm/cli/lint/actions/CheckAction.js +138 -0
  15. package/lib-esm/cli/lint/actions/CheckAction.js.map +1 -0
  16. package/lib-esm/cli/lint/actions/InitAction.js +38 -0
  17. package/lib-esm/cli/lint/actions/InitAction.js.map +1 -0
  18. package/lib-esm/constants/common.js +5 -0
  19. package/lib-esm/constants/common.js.map +1 -0
  20. package/lib-esm/graph/IPnpmfileModule.js +4 -0
  21. package/lib-esm/graph/IPnpmfileModule.js.map +1 -0
  22. package/lib-esm/graph/PnpmfileRunner.js +86 -0
  23. package/lib-esm/graph/PnpmfileRunner.js.map +1 -0
  24. package/lib-esm/graph/lfxGraphLoader.js +459 -0
  25. package/lib-esm/graph/lfxGraphLoader.js.map +1 -0
  26. package/lib-esm/graph/lockfilePath.js +117 -0
  27. package/lib-esm/graph/lockfilePath.js.map +1 -0
  28. package/lib-esm/graph/pnpmfileRunnerWorkerThread.js +59 -0
  29. package/lib-esm/graph/pnpmfileRunnerWorkerThread.js.map +1 -0
  30. package/lib-esm/schemas/lockfile-lint.schema.json +45 -0
  31. package/lib-esm/start-explorer.js +6 -0
  32. package/lib-esm/start-explorer.js.map +1 -0
  33. package/lib-esm/start-lint.js +6 -0
  34. package/lib-esm/start-lint.js.map +1 -0
  35. package/lib-esm/state/index.js +4 -0
  36. package/lib-esm/state/index.js.map +1 -0
  37. package/lib-esm/utils/init.js +77 -0
  38. package/lib-esm/utils/init.js.map +1 -0
  39. package/lib-esm/utils/shrinkwrap.js +44 -0
  40. package/lib-esm/utils/shrinkwrap.js.map +1 -0
  41. package/package.json +30 -8
  42. package/dist/app.c67214284d4725ea5913.js +0 -1
  43. package/dist/app.e74e7004c99e392a3c70.css +0 -1
  44. package/dist/initappcontext.js +0 -14
  45. /package/{lib → lib-commonjs}/assets/lint-init/lockfile-lint-template.json +0 -0
  46. /package/{lib → lib-commonjs}/cli/explorer/ExplorerCommandLineParser.js +0 -0
  47. /package/{lib → lib-commonjs}/cli/explorer/ExplorerCommandLineParser.js.map +0 -0
  48. /package/{lib → lib-commonjs}/cli/lint/LintCommandLineParser.js +0 -0
  49. /package/{lib → lib-commonjs}/cli/lint/LintCommandLineParser.js.map +0 -0
  50. /package/{lib → lib-commonjs}/cli/lint/actions/CheckAction.js +0 -0
  51. /package/{lib → lib-commonjs}/cli/lint/actions/CheckAction.js.map +0 -0
  52. /package/{lib → lib-commonjs}/cli/lint/actions/InitAction.js +0 -0
  53. /package/{lib → lib-commonjs}/cli/lint/actions/InitAction.js.map +0 -0
  54. /package/{lib → lib-commonjs}/constants/common.js +0 -0
  55. /package/{lib → lib-commonjs}/constants/common.js.map +0 -0
  56. /package/{lib → lib-commonjs}/graph/IPnpmfileModule.js +0 -0
  57. /package/{lib → lib-commonjs}/graph/IPnpmfileModule.js.map +0 -0
  58. /package/{lib → lib-commonjs}/graph/PnpmfileRunner.js +0 -0
  59. /package/{lib → lib-commonjs}/graph/PnpmfileRunner.js.map +0 -0
  60. /package/{lib → lib-commonjs}/graph/lfxGraphLoader.js +0 -0
  61. /package/{lib → lib-commonjs}/graph/lfxGraphLoader.js.map +0 -0
  62. /package/{lib → lib-commonjs}/graph/lockfilePath.js +0 -0
  63. /package/{lib → lib-commonjs}/graph/lockfilePath.js.map +0 -0
  64. /package/{lib → lib-commonjs}/graph/pnpmfileRunnerWorkerThread.js +0 -0
  65. /package/{lib → lib-commonjs}/graph/pnpmfileRunnerWorkerThread.js.map +0 -0
  66. /package/{lib → lib-commonjs}/schemas/lockfile-lint.schema.json +0 -0
  67. /package/{lib → lib-commonjs}/start-explorer.js +0 -0
  68. /package/{lib → lib-commonjs}/start-explorer.js.map +0 -0
  69. /package/{lib → lib-commonjs}/start-lint.js +0 -0
  70. /package/{lib → lib-commonjs}/start-lint.js.map +0 -0
  71. /package/{lib → lib-commonjs}/state/index.js +0 -0
  72. /package/{lib → lib-commonjs}/state/index.js.map +0 -0
  73. /package/{lib → lib-commonjs}/utils/init.js +0 -0
  74. /package/{lib → lib-commonjs}/utils/init.js.map +0 -0
  75. /package/{lib → lib-commonjs}/utils/shrinkwrap.js +0 -0
  76. /package/{lib → lib-commonjs}/utils/shrinkwrap.js.map +0 -0
  77. /package/{lib → lib-dts}/cli/explorer/ExplorerCommandLineParser.d.ts +0 -0
  78. /package/{lib → lib-dts}/cli/explorer/ExplorerCommandLineParser.d.ts.map +0 -0
  79. /package/{lib → lib-dts}/cli/lint/LintCommandLineParser.d.ts +0 -0
  80. /package/{lib → lib-dts}/cli/lint/LintCommandLineParser.d.ts.map +0 -0
  81. /package/{lib → lib-dts}/cli/lint/actions/CheckAction.d.ts +0 -0
  82. /package/{lib → lib-dts}/cli/lint/actions/CheckAction.d.ts.map +0 -0
  83. /package/{lib → lib-dts}/cli/lint/actions/InitAction.d.ts +0 -0
  84. /package/{lib → lib-dts}/cli/lint/actions/InitAction.d.ts.map +0 -0
  85. /package/{lib → lib-dts}/constants/common.d.ts +0 -0
  86. /package/{lib → lib-dts}/constants/common.d.ts.map +0 -0
  87. /package/{lib → lib-dts}/graph/IPnpmfileModule.d.ts +0 -0
  88. /package/{lib → lib-dts}/graph/IPnpmfileModule.d.ts.map +0 -0
  89. /package/{lib → lib-dts}/graph/PnpmfileRunner.d.ts +0 -0
  90. /package/{lib → lib-dts}/graph/PnpmfileRunner.d.ts.map +0 -0
  91. /package/{lib → lib-dts}/graph/lfxGraphLoader.d.ts +0 -0
  92. /package/{lib → lib-dts}/graph/lfxGraphLoader.d.ts.map +0 -0
  93. /package/{lib → lib-dts}/graph/lockfilePath.d.ts +0 -0
  94. /package/{lib → lib-dts}/graph/lockfilePath.d.ts.map +0 -0
  95. /package/{lib → lib-dts}/graph/pnpmfileRunnerWorkerThread.d.ts +0 -0
  96. /package/{lib → lib-dts}/graph/pnpmfileRunnerWorkerThread.d.ts.map +0 -0
  97. /package/{lib → lib-dts}/start-explorer.d.ts +0 -0
  98. /package/{lib → lib-dts}/start-explorer.d.ts.map +0 -0
  99. /package/{lib → lib-dts}/start-lint.d.ts +0 -0
  100. /package/{lib → lib-dts}/start-lint.d.ts.map +0 -0
  101. /package/{lib → lib-dts}/state/index.d.ts +0 -0
  102. /package/{lib → lib-dts}/state/index.d.ts.map +0 -0
  103. /package/{lib → lib-dts}/utils/init.d.ts +0 -0
  104. /package/{lib → lib-dts}/utils/init.d.ts.map +0 -0
  105. /package/{lib → lib-dts}/utils/shrinkwrap.d.ts +0 -0
  106. /package/{lib → lib-dts}/utils/shrinkwrap.d.ts.map +0 -0
@@ -0,0 +1,204 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import process from 'node:process';
4
+ import * as path from 'node:path';
5
+ import express from 'express';
6
+ import yaml from 'js-yaml';
7
+ import cors from 'cors';
8
+ import open from 'open';
9
+ import updateNotifier from 'update-notifier';
10
+ import { FileSystem, JsonFile, PackageJsonLookup } from '@rushstack/node-core-library';
11
+ import { ConsoleTerminalProvider, Terminal, Colorize } from '@rushstack/terminal';
12
+ import { CommandLineParser } from '@rushstack/ts-command-line';
13
+ import { lfxGraphSerializer } from '../../../build/lfx-shared';
14
+ import * as lockfilePath from '../../graph/lockfilePath';
15
+ import { init } from '../../utils/init';
16
+ import { PnpmfileRunner } from '../../graph/PnpmfileRunner';
17
+ import * as lfxGraphLoader from '../../graph/lfxGraphLoader';
18
+ const EXPLORER_TOOL_FILENAME = 'lockfile-explorer';
19
+ export class ExplorerCommandLineParser extends CommandLineParser {
20
+ constructor() {
21
+ super({
22
+ toolFilename: EXPLORER_TOOL_FILENAME,
23
+ toolDescription: 'Lockfile Explorer is a desktop app for investigating and solving version conflicts in a PNPM workspace.'
24
+ });
25
+ this._debugParameter = this.defineFlagParameter({
26
+ parameterLongName: '--debug',
27
+ parameterShortName: '-d',
28
+ description: 'Show the full call stack if an error occurs while executing the tool'
29
+ });
30
+ this._subspaceParameter = this.defineStringParameter({
31
+ parameterLongName: '--subspace',
32
+ argumentName: 'SUBSPACE_NAME',
33
+ description: 'Specifies an individual Rush subspace to check.',
34
+ defaultValue: 'default'
35
+ });
36
+ this._terminalProvider = new ConsoleTerminalProvider();
37
+ this.globalTerminal = new Terminal(this._terminalProvider);
38
+ }
39
+ get isDebug() {
40
+ return this._debugParameter.value;
41
+ }
42
+ async onExecuteAsync() {
43
+ const lockfileExplorerProjectRoot = PackageJsonLookup.instance.tryGetPackageFolderFor(__dirname);
44
+ const lockfileExplorerPackageJson = JsonFile.load(`${lockfileExplorerProjectRoot}/package.json`);
45
+ const appVersion = lockfileExplorerPackageJson.version;
46
+ this.globalTerminal.writeLine(Colorize.bold(`\nRush Lockfile Explorer ${appVersion}`) +
47
+ Colorize.cyan(' - https://lfx.rushstack.io/\n'));
48
+ updateNotifier({
49
+ pkg: lockfileExplorerPackageJson,
50
+ // Normally update-notifier waits a day or so before it starts displaying upgrade notices.
51
+ // In debug mode, show the notice right away.
52
+ updateCheckInterval: this.isDebug ? 0 : undefined
53
+ }).notify({
54
+ // Make sure it says "-g" in the "npm install" example command line
55
+ isGlobal: true,
56
+ // Show the notice immediately, rather than waiting for process.onExit()
57
+ defer: false
58
+ });
59
+ const PORT = 8091;
60
+ // Must not have a trailing slash
61
+ const SERVICE_URL = `http://localhost:${PORT}`;
62
+ const appState = init({
63
+ lockfileExplorerProjectRoot,
64
+ appVersion,
65
+ debugMode: this.isDebug,
66
+ subspaceName: this._subspaceParameter.value
67
+ });
68
+ const lfxWorkspace = appState.lfxWorkspace;
69
+ // Important: This must happen after init() reads the current working directory
70
+ process.chdir(appState.lockfileExplorerProjectRoot);
71
+ const distFolderPath = `${appState.lockfileExplorerProjectRoot}/dist`;
72
+ const app = express();
73
+ app.use(express.json());
74
+ app.use(cors());
75
+ // Variable used to check if the front-end client is still connected
76
+ let awaitingFirstConnect = true;
77
+ let isClientConnected = false;
78
+ let disconnected = false;
79
+ setInterval(() => {
80
+ if (!isClientConnected && !awaitingFirstConnect && !disconnected) {
81
+ console.log(Colorize.red('The client has disconnected!'));
82
+ console.log(`Please open a browser window at http://localhost:${PORT}/app`);
83
+ disconnected = true;
84
+ }
85
+ else if (!awaitingFirstConnect) {
86
+ isClientConnected = false;
87
+ }
88
+ }, 4000);
89
+ // This takes precedence over the `/app` static route, which also has an `initappcontext.js` file.
90
+ app.get('/initappcontext.js', (req, res) => {
91
+ const appContext = {
92
+ serviceUrl: SERVICE_URL,
93
+ appVersion: appState.appVersion,
94
+ debugMode: this.isDebug
95
+ };
96
+ const sourceCode = [
97
+ `console.log('Loaded initappcontext.js');`,
98
+ `appContext = ${JSON.stringify(appContext)}`
99
+ ].join('\n');
100
+ res.type('application/javascript').send(sourceCode);
101
+ });
102
+ app.use('/', express.static(distFolderPath));
103
+ app.use('/favicon.ico', express.static(distFolderPath, { index: 'favicon.ico' }));
104
+ app.get('/api/health', (req, res) => {
105
+ awaitingFirstConnect = false;
106
+ isClientConnected = true;
107
+ if (disconnected) {
108
+ disconnected = false;
109
+ console.log(Colorize.green('The client has reconnected!'));
110
+ }
111
+ res.status(200).send();
112
+ });
113
+ app.get('/api/graph', async (req, res) => {
114
+ const pnpmLockfileText = await FileSystem.readFileAsync(appState.pnpmLockfileLocation);
115
+ const lockfile = yaml.load(pnpmLockfileText);
116
+ const graph = lfxGraphLoader.generateLockfileGraph(lockfile, lfxWorkspace);
117
+ const jsonGraph = lfxGraphSerializer.serializeToJson(graph);
118
+ res.type('application/json').send(jsonGraph);
119
+ });
120
+ app.post('/api/package-json', async (req, res) => {
121
+ const { projectPath } = req.body;
122
+ const fileLocation = `${appState.projectRoot}/${projectPath}/package.json`;
123
+ let packageJsonText;
124
+ try {
125
+ packageJsonText = await FileSystem.readFileAsync(fileLocation);
126
+ }
127
+ catch (e) {
128
+ if (FileSystem.isNotExistError(e)) {
129
+ return res.status(404).send({
130
+ message: `Could not load package.json file for this package. Have you installed all the dependencies for this workspace?`,
131
+ error: `No package.json in location: ${projectPath}`
132
+ });
133
+ }
134
+ else {
135
+ throw e;
136
+ }
137
+ }
138
+ res.send(packageJsonText);
139
+ });
140
+ app.get('/api/pnpmfile', async (req, res) => {
141
+ var _a, _b;
142
+ const pnpmfilePath = lockfilePath.join(lfxWorkspace.workspaceRootFullPath, (_b = (_a = lfxWorkspace.rushConfig) === null || _a === void 0 ? void 0 : _a.rushPnpmfilePath) !== null && _b !== void 0 ? _b : lfxWorkspace.pnpmfilePath);
143
+ let pnpmfile;
144
+ try {
145
+ pnpmfile = await FileSystem.readFileAsync(pnpmfilePath);
146
+ }
147
+ catch (e) {
148
+ if (FileSystem.isNotExistError(e)) {
149
+ return res.status(404).send({
150
+ message: `Could not load .pnpmfile.cjs file in this repo: "${pnpmfilePath}"`,
151
+ error: `No .pnpmifile.cjs found.`
152
+ });
153
+ }
154
+ else {
155
+ throw e;
156
+ }
157
+ }
158
+ res.send(pnpmfile);
159
+ });
160
+ app.post('/api/package-spec', async (req, res) => {
161
+ const { projectPath } = req.body;
162
+ const fileLocation = `${appState.projectRoot}/${projectPath}/package.json`;
163
+ let packageJson;
164
+ try {
165
+ packageJson = await JsonFile.loadAsync(fileLocation);
166
+ }
167
+ catch (e) {
168
+ if (FileSystem.isNotExistError(e)) {
169
+ return res.status(404).send({
170
+ message: `Could not load package.json file in location: ${projectPath}`
171
+ });
172
+ }
173
+ else {
174
+ throw e;
175
+ }
176
+ }
177
+ let parsedPackage = packageJson;
178
+ const pnpmfilePath = path.join(lfxWorkspace.workspaceRootFullPath, lfxWorkspace.pnpmfilePath);
179
+ if (await FileSystem.existsAsync(pnpmfilePath)) {
180
+ const pnpmFileRunner = new PnpmfileRunner(pnpmfilePath);
181
+ try {
182
+ parsedPackage = await pnpmFileRunner.transformPackageAsync(packageJson, fileLocation);
183
+ }
184
+ finally {
185
+ await pnpmFileRunner.disposeAsync();
186
+ }
187
+ }
188
+ res.send(parsedPackage);
189
+ });
190
+ app.listen(PORT, async () => {
191
+ console.log(`App launched on ${SERVICE_URL}`);
192
+ if (!appState.debugMode) {
193
+ try {
194
+ // Launch the web browser
195
+ await open(SERVICE_URL);
196
+ }
197
+ catch (e) {
198
+ this.globalTerminal.writeError('Error launching browser: ' + e.toString());
199
+ }
200
+ }
201
+ });
202
+ }
203
+ }
204
+ //# sourceMappingURL=ExplorerCommandLineParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplorerCommandLineParser.js","sourceRoot":"","sources":["../../../src/cli/explorer/ExplorerCommandLineParser.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport process from 'node:process';\nimport * as path from 'node:path';\n\nimport express from 'express';\nimport yaml from 'js-yaml';\nimport cors from 'cors';\nimport open from 'open';\nimport updateNotifier from 'update-notifier';\n\nimport { FileSystem, type IPackageJson, JsonFile, PackageJsonLookup } from '@rushstack/node-core-library';\nimport { ConsoleTerminalProvider, type ITerminal, Terminal, Colorize } from '@rushstack/terminal';\nimport {\n type CommandLineFlagParameter,\n CommandLineParser,\n type IRequiredCommandLineStringParameter\n} from '@rushstack/ts-command-line';\n\nimport {\n type LfxGraph,\n lfxGraphSerializer,\n type IAppContext,\n type IJsonLfxGraph,\n type IJsonLfxWorkspace\n} from '../../../build/lfx-shared';\nimport * as lockfilePath from '../../graph/lockfilePath';\nimport type { IAppState } from '../../state';\nimport { init } from '../../utils/init';\nimport { PnpmfileRunner } from '../../graph/PnpmfileRunner';\nimport * as lfxGraphLoader from '../../graph/lfxGraphLoader';\n\nconst EXPLORER_TOOL_FILENAME: 'lockfile-explorer' = 'lockfile-explorer';\n\nexport class ExplorerCommandLineParser extends CommandLineParser {\n public readonly globalTerminal: ITerminal;\n private readonly _terminalProvider: ConsoleTerminalProvider;\n private readonly _debugParameter: CommandLineFlagParameter;\n\n private readonly _subspaceParameter: IRequiredCommandLineStringParameter;\n\n public constructor() {\n super({\n toolFilename: EXPLORER_TOOL_FILENAME,\n toolDescription:\n 'Lockfile Explorer is a desktop app for investigating and solving version conflicts in a PNPM workspace.'\n });\n\n this._debugParameter = this.defineFlagParameter({\n parameterLongName: '--debug',\n parameterShortName: '-d',\n description: 'Show the full call stack if an error occurs while executing the tool'\n });\n\n this._subspaceParameter = this.defineStringParameter({\n parameterLongName: '--subspace',\n argumentName: 'SUBSPACE_NAME',\n description: 'Specifies an individual Rush subspace to check.',\n defaultValue: 'default'\n });\n\n this._terminalProvider = new ConsoleTerminalProvider();\n this.globalTerminal = new Terminal(this._terminalProvider);\n }\n\n public get isDebug(): boolean {\n return this._debugParameter.value;\n }\n\n protected override async onExecuteAsync(): Promise<void> {\n const lockfileExplorerProjectRoot: string = PackageJsonLookup.instance.tryGetPackageFolderFor(__dirname)!;\n const lockfileExplorerPackageJson: IPackageJson = JsonFile.load(\n `${lockfileExplorerProjectRoot}/package.json`\n );\n const appVersion: string = lockfileExplorerPackageJson.version;\n\n this.globalTerminal.writeLine(\n Colorize.bold(`\\nRush Lockfile Explorer ${appVersion}`) +\n Colorize.cyan(' - https://lfx.rushstack.io/\\n')\n );\n\n updateNotifier({\n pkg: lockfileExplorerPackageJson,\n // Normally update-notifier waits a day or so before it starts displaying upgrade notices.\n // In debug mode, show the notice right away.\n updateCheckInterval: this.isDebug ? 0 : undefined\n }).notify({\n // Make sure it says \"-g\" in the \"npm install\" example command line\n isGlobal: true,\n // Show the notice immediately, rather than waiting for process.onExit()\n defer: false\n });\n\n const PORT: number = 8091;\n // Must not have a trailing slash\n const SERVICE_URL: string = `http://localhost:${PORT}`;\n\n const appState: IAppState = init({\n lockfileExplorerProjectRoot,\n appVersion,\n debugMode: this.isDebug,\n subspaceName: this._subspaceParameter.value\n });\n\n const lfxWorkspace: IJsonLfxWorkspace = appState.lfxWorkspace;\n\n // Important: This must happen after init() reads the current working directory\n process.chdir(appState.lockfileExplorerProjectRoot);\n\n const distFolderPath: string = `${appState.lockfileExplorerProjectRoot}/dist`;\n const app: express.Application = express();\n app.use(express.json());\n app.use(cors());\n\n // Variable used to check if the front-end client is still connected\n let awaitingFirstConnect: boolean = true;\n let isClientConnected: boolean = false;\n let disconnected: boolean = false;\n setInterval(() => {\n if (!isClientConnected && !awaitingFirstConnect && !disconnected) {\n console.log(Colorize.red('The client has disconnected!'));\n console.log(`Please open a browser window at http://localhost:${PORT}/app`);\n disconnected = true;\n } else if (!awaitingFirstConnect) {\n isClientConnected = false;\n }\n }, 4000);\n\n // This takes precedence over the `/app` static route, which also has an `initappcontext.js` file.\n app.get('/initappcontext.js', (req: express.Request, res: express.Response) => {\n const appContext: IAppContext = {\n serviceUrl: SERVICE_URL,\n appVersion: appState.appVersion,\n debugMode: this.isDebug\n };\n const sourceCode: string = [\n `console.log('Loaded initappcontext.js');`,\n `appContext = ${JSON.stringify(appContext)}`\n ].join('\\n');\n\n res.type('application/javascript').send(sourceCode);\n });\n\n app.use('/', express.static(distFolderPath));\n\n app.use('/favicon.ico', express.static(distFolderPath, { index: 'favicon.ico' }));\n\n app.get('/api/health', (req: express.Request, res: express.Response) => {\n awaitingFirstConnect = false;\n isClientConnected = true;\n if (disconnected) {\n disconnected = false;\n console.log(Colorize.green('The client has reconnected!'));\n }\n res.status(200).send();\n });\n\n app.get('/api/graph', async (req: express.Request, res: express.Response) => {\n const pnpmLockfileText: string = await FileSystem.readFileAsync(appState.pnpmLockfileLocation);\n const lockfile: unknown = yaml.load(pnpmLockfileText) as unknown;\n\n const graph: LfxGraph = lfxGraphLoader.generateLockfileGraph(lockfile, lfxWorkspace);\n\n const jsonGraph: IJsonLfxGraph = lfxGraphSerializer.serializeToJson(graph);\n res.type('application/json').send(jsonGraph);\n });\n\n app.post(\n '/api/package-json',\n async (req: express.Request<{}, {}, { projectPath: string }, {}>, res: express.Response) => {\n const { projectPath } = req.body;\n const fileLocation: string = `${appState.projectRoot}/${projectPath}/package.json`;\n let packageJsonText: string;\n try {\n packageJsonText = await FileSystem.readFileAsync(fileLocation);\n } catch (e) {\n if (FileSystem.isNotExistError(e)) {\n return res.status(404).send({\n message: `Could not load package.json file for this package. Have you installed all the dependencies for this workspace?`,\n error: `No package.json in location: ${projectPath}`\n });\n } else {\n throw e;\n }\n }\n\n res.send(packageJsonText);\n }\n );\n\n app.get('/api/pnpmfile', async (req: express.Request, res: express.Response) => {\n const pnpmfilePath: string = lockfilePath.join(\n lfxWorkspace.workspaceRootFullPath,\n lfxWorkspace.rushConfig?.rushPnpmfilePath ?? lfxWorkspace.pnpmfilePath\n );\n\n let pnpmfile: string;\n try {\n pnpmfile = await FileSystem.readFileAsync(pnpmfilePath);\n } catch (e) {\n if (FileSystem.isNotExistError(e)) {\n return res.status(404).send({\n message: `Could not load .pnpmfile.cjs file in this repo: \"${pnpmfilePath}\"`,\n error: `No .pnpmifile.cjs found.`\n });\n } else {\n throw e;\n }\n }\n\n res.send(pnpmfile);\n });\n\n app.post(\n '/api/package-spec',\n async (req: express.Request<{}, {}, { projectPath: string }, {}>, res: express.Response) => {\n const { projectPath } = req.body;\n const fileLocation: string = `${appState.projectRoot}/${projectPath}/package.json`;\n let packageJson: IPackageJson;\n try {\n packageJson = await JsonFile.loadAsync(fileLocation);\n } catch (e) {\n if (FileSystem.isNotExistError(e)) {\n return res.status(404).send({\n message: `Could not load package.json file in location: ${projectPath}`\n });\n } else {\n throw e;\n }\n }\n\n let parsedPackage: IPackageJson = packageJson;\n\n const pnpmfilePath: string = path.join(lfxWorkspace.workspaceRootFullPath, lfxWorkspace.pnpmfilePath);\n if (await FileSystem.existsAsync(pnpmfilePath)) {\n const pnpmFileRunner: PnpmfileRunner = new PnpmfileRunner(pnpmfilePath);\n try {\n parsedPackage = await pnpmFileRunner.transformPackageAsync(packageJson, fileLocation);\n } finally {\n await pnpmFileRunner.disposeAsync();\n }\n }\n\n res.send(parsedPackage);\n }\n );\n\n app.listen(PORT, async () => {\n console.log(`App launched on ${SERVICE_URL}`);\n\n if (!appState.debugMode) {\n try {\n // Launch the web browser\n await open(SERVICE_URL);\n } catch (e) {\n this.globalTerminal.writeError('Error launching browser: ' + e.toString());\n }\n }\n });\n }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { ConsoleTerminalProvider, Terminal, Colorize } from '@rushstack/terminal';
4
+ import { CommandLineParser } from '@rushstack/ts-command-line';
5
+ import { JsonFile, PackageJsonLookup } from '@rushstack/node-core-library';
6
+ import { InitAction } from './actions/InitAction';
7
+ import { CheckAction } from './actions/CheckAction';
8
+ const LINT_TOOL_FILENAME = 'lockfile-lint';
9
+ export class LintCommandLineParser extends CommandLineParser {
10
+ constructor() {
11
+ super({
12
+ toolFilename: LINT_TOOL_FILENAME,
13
+ toolDescription: 'Lockfile Lint applies configured policies to find and report dependency issues in your PNPM workspace.'
14
+ });
15
+ this._terminalProvider = new ConsoleTerminalProvider();
16
+ this.globalTerminal = new Terminal(this._terminalProvider);
17
+ this._populateActions();
18
+ }
19
+ async onExecuteAsync() {
20
+ const lockfileExplorerProjectRoot = PackageJsonLookup.instance.tryGetPackageFolderFor(__dirname);
21
+ const lockfileExplorerPackageJson = JsonFile.load(`${lockfileExplorerProjectRoot}/package.json`);
22
+ const appVersion = lockfileExplorerPackageJson.version;
23
+ this.globalTerminal.writeLine(Colorize.bold(`\nRush Lockfile Lint ${appVersion}`) + Colorize.cyan(' - https://lfx.rushstack.io/\n'));
24
+ await super.onExecuteAsync();
25
+ }
26
+ _populateActions() {
27
+ this.addAction(new InitAction(this));
28
+ this.addAction(new CheckAction(this));
29
+ }
30
+ }
31
+ //# sourceMappingURL=LintCommandLineParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LintCommandLineParser.js","sourceRoot":"","sources":["../../../src/cli/lint/LintCommandLineParser.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,EAAE,uBAAuB,EAAkB,QAAQ,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAqB,QAAQ,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAE9F,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,MAAM,kBAAkB,GAAoB,eAAe,CAAC;AAE5D,MAAM,OAAO,qBAAsB,SAAQ,iBAAiB;IAI1D;QACE,KAAK,CAAC;YACJ,YAAY,EAAE,kBAAkB;YAChC,eAAe,EACb,wGAAwG;SAC3G,CAAC,CAAC;QAEH,IAAI,CAAC,iBAAiB,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACvD,IAAI,CAAC,cAAc,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAE3D,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAC1B,CAAC;IAEkB,KAAK,CAAC,cAAc;QACrC,MAAM,2BAA2B,GAAW,iBAAiB,CAAC,QAAQ,CAAC,sBAAsB,CAAC,SAAS,CAAE,CAAC;QAC1G,MAAM,2BAA2B,GAAiB,QAAQ,CAAC,IAAI,CAC7D,GAAG,2BAA2B,eAAe,CAC9C,CAAC;QACF,MAAM,UAAU,GAAW,2BAA2B,CAAC,OAAO,CAAC;QAE/D,IAAI,CAAC,cAAc,CAAC,SAAS,CAC3B,QAAQ,CAAC,IAAI,CAAC,wBAAwB,UAAU,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,gCAAgC,CAAC,CACtG,CAAC;QAEF,MAAM,KAAK,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IAEO,gBAAgB;QACtB,IAAI,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,SAAS,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IACxC,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { ConsoleTerminalProvider, type ITerminal, Terminal, Colorize } from '@rushstack/terminal';\nimport { CommandLineParser } from '@rushstack/ts-command-line';\nimport { type IPackageJson, JsonFile, PackageJsonLookup } from '@rushstack/node-core-library';\n\nimport { InitAction } from './actions/InitAction';\nimport { CheckAction } from './actions/CheckAction';\n\nconst LINT_TOOL_FILENAME: 'lockfile-lint' = 'lockfile-lint';\n\nexport class LintCommandLineParser extends CommandLineParser {\n public readonly globalTerminal: ITerminal;\n private readonly _terminalProvider: ConsoleTerminalProvider;\n\n public constructor() {\n super({\n toolFilename: LINT_TOOL_FILENAME,\n toolDescription:\n 'Lockfile Lint applies configured policies to find and report dependency issues in your PNPM workspace.'\n });\n\n this._terminalProvider = new ConsoleTerminalProvider();\n this.globalTerminal = new Terminal(this._terminalProvider);\n\n this._populateActions();\n }\n\n protected override async onExecuteAsync(): Promise<void> {\n const lockfileExplorerProjectRoot: string = PackageJsonLookup.instance.tryGetPackageFolderFor(__dirname)!;\n const lockfileExplorerPackageJson: IPackageJson = JsonFile.load(\n `${lockfileExplorerProjectRoot}/package.json`\n );\n const appVersion: string = lockfileExplorerPackageJson.version;\n\n this.globalTerminal.writeLine(\n Colorize.bold(`\\nRush Lockfile Lint ${appVersion}`) + Colorize.cyan(' - https://lfx.rushstack.io/\\n')\n );\n\n await super.onExecuteAsync();\n }\n\n private _populateActions(): void {\n this.addAction(new InitAction(this));\n this.addAction(new CheckAction(this));\n }\n}\n"]}
@@ -0,0 +1,138 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import path from 'node:path';
4
+ import yaml from 'js-yaml';
5
+ import semver from 'semver';
6
+ import { RushConfiguration } from '@rushstack/rush-sdk';
7
+ import { CommandLineAction } from '@rushstack/ts-command-line';
8
+ import { Colorize } from '@rushstack/terminal';
9
+ import { AlreadyReportedError, Async, FileSystem, JsonFile, JsonSchema } from '@rushstack/node-core-library';
10
+ import lockfileLintSchema from '../../../schemas/lockfile-lint.schema.json';
11
+ import { LOCKFILE_EXPLORER_FOLDERNAME, LOCKFILE_LINT_JSON_FILENAME } from '../../../constants/common';
12
+ import { getShrinkwrapFileMajorVersion, parseDependencyPath, splicePackageWithVersion } from '../../../utils/shrinkwrap';
13
+ export class CheckAction extends CommandLineAction {
14
+ constructor(parser) {
15
+ super({
16
+ actionName: 'check',
17
+ summary: 'Check and report dependency issues in your workspace',
18
+ documentation: 'This command applies the policies that are configured in ' +
19
+ LOCKFILE_LINT_JSON_FILENAME +
20
+ ', reporting any problems found in your PNPM workspace.'
21
+ });
22
+ this._terminal = parser.globalTerminal;
23
+ this._checkedProjects = new Set();
24
+ this._docMap = new Map();
25
+ }
26
+ async _checkVersionCompatibilityAsync(shrinkwrapFileMajorVersion, packages, dependencyPath, requiredVersions, checkedDependencyPaths) {
27
+ var _a;
28
+ if (packages && packages[dependencyPath] && !checkedDependencyPaths.has(dependencyPath)) {
29
+ checkedDependencyPaths.add(dependencyPath);
30
+ const { name, version } = parseDependencyPath(shrinkwrapFileMajorVersion, dependencyPath);
31
+ if (name in requiredVersions && !semver.satisfies(version, requiredVersions[name])) {
32
+ throw new Error(`The version of "${name}" should match "${requiredVersions[name]}";` +
33
+ ` actual version is "${version}"`);
34
+ }
35
+ await Promise.all(Object.entries((_a = packages[dependencyPath].dependencies) !== null && _a !== void 0 ? _a : {}).map(async ([dependencyPackageName, dependencyPackageVersion]) => {
36
+ await this._checkVersionCompatibilityAsync(shrinkwrapFileMajorVersion, packages, splicePackageWithVersion(shrinkwrapFileMajorVersion, dependencyPackageName, dependencyPackageVersion), requiredVersions, checkedDependencyPaths);
37
+ }));
38
+ }
39
+ }
40
+ async _searchAndValidateDependenciesAsync(project, requiredVersions) {
41
+ this._terminal.writeLine(`Checking project "${project.packageName}"`);
42
+ const projectFolder = project.projectFolder;
43
+ const subspace = project.subspace;
44
+ const shrinkwrapFilename = subspace.getCommittedShrinkwrapFilePath();
45
+ let doc;
46
+ if (this._docMap.has(shrinkwrapFilename)) {
47
+ doc = this._docMap.get(shrinkwrapFilename);
48
+ }
49
+ else {
50
+ const pnpmLockfileText = await FileSystem.readFileAsync(shrinkwrapFilename);
51
+ doc = yaml.load(pnpmLockfileText);
52
+ this._docMap.set(shrinkwrapFilename, doc);
53
+ }
54
+ const { importers, lockfileVersion, packages } = doc;
55
+ const shrinkwrapFileMajorVersion = getShrinkwrapFileMajorVersion(lockfileVersion);
56
+ const checkedDependencyPaths = new Set();
57
+ await Promise.all(Object.entries(importers).map(async ([relativePath, { dependencies }]) => {
58
+ var _a;
59
+ if (path.resolve(projectFolder, relativePath) === projectFolder) {
60
+ const dependenciesEntries = Object.entries(dependencies !== null && dependencies !== void 0 ? dependencies : {});
61
+ for (const [dependencyName, dependencyValue] of dependenciesEntries) {
62
+ const fullDependencyPath = splicePackageWithVersion(shrinkwrapFileMajorVersion, dependencyName, typeof dependencyValue === 'string'
63
+ ? dependencyValue
64
+ : dependencyValue.version);
65
+ if (fullDependencyPath.includes('link:')) {
66
+ const dependencyProject = this._rushConfiguration.getProjectByName(dependencyName);
67
+ if (dependencyProject && !((_a = this._checkedProjects) === null || _a === void 0 ? void 0 : _a.has(dependencyProject))) {
68
+ this._checkedProjects.add(project);
69
+ await this._searchAndValidateDependenciesAsync(dependencyProject, requiredVersions);
70
+ }
71
+ }
72
+ else {
73
+ await this._checkVersionCompatibilityAsync(shrinkwrapFileMajorVersion, packages, fullDependencyPath, requiredVersions, checkedDependencyPaths);
74
+ }
75
+ }
76
+ }
77
+ }));
78
+ }
79
+ async _performVersionRestrictionCheckAsync(requiredVersions, projectName) {
80
+ var _a;
81
+ try {
82
+ const project = (_a = this._rushConfiguration) === null || _a === void 0 ? void 0 : _a.getProjectByName(projectName);
83
+ if (!project) {
84
+ throw new Error(`Specified project "${projectName}" does not exist in ${LOCKFILE_LINT_JSON_FILENAME}`);
85
+ }
86
+ this._checkedProjects.add(project);
87
+ await this._searchAndValidateDependenciesAsync(project, requiredVersions);
88
+ return undefined;
89
+ }
90
+ catch (e) {
91
+ return e.message;
92
+ }
93
+ }
94
+ async onExecuteAsync() {
95
+ const rushConfiguration = RushConfiguration.tryLoadFromDefaultLocation();
96
+ if (!rushConfiguration) {
97
+ throw new Error('The "lockfile-explorer check" must be executed in a folder that is under a Rush workspace folder');
98
+ }
99
+ this._rushConfiguration = rushConfiguration;
100
+ const lintingFile = path.resolve(this._rushConfiguration.commonFolder, 'config', LOCKFILE_EXPLORER_FOLDERNAME, LOCKFILE_LINT_JSON_FILENAME);
101
+ const { rules } = await JsonFile.loadAndValidateAsync(lintingFile, JsonSchema.fromLoadedObject(lockfileLintSchema));
102
+ const issues = [];
103
+ await Async.forEachAsync(rules, async ({ requiredVersions, project, rule }) => {
104
+ switch (rule) {
105
+ case 'restrict-versions': {
106
+ const message = await this._performVersionRestrictionCheckAsync(requiredVersions, project);
107
+ if (message) {
108
+ issues.push({ project, rule, message });
109
+ }
110
+ break;
111
+ }
112
+ default: {
113
+ throw new Error('Unsupported rule name: ' + rule);
114
+ }
115
+ }
116
+ }, { concurrency: 50 });
117
+ if (issues.length > 0) {
118
+ this._terminal.writeLine();
119
+ // Deterministic order
120
+ for (const issue of issues.sort((a, b) => {
121
+ let diff = a.project.localeCompare(b.project);
122
+ if (diff !== 0) {
123
+ return diff;
124
+ }
125
+ diff = a.rule.localeCompare(b.rule);
126
+ if (diff !== 0) {
127
+ return diff;
128
+ }
129
+ return a.message.localeCompare(b.message);
130
+ })) {
131
+ this._terminal.writeLine(Colorize.red('PROBLEM: ') + Colorize.cyan(`[${issue.rule}] `) + issue.message + '\n');
132
+ }
133
+ throw new AlreadyReportedError();
134
+ }
135
+ this._terminal.writeLine(Colorize.green('SUCCESS: ') + 'All checks passed.');
136
+ }
137
+ }
138
+ //# sourceMappingURL=CheckAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckAction.js","sourceRoot":"","sources":["../../../../src/cli/lint/actions/CheckAction.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport path from 'node:path';\n\nimport yaml from 'js-yaml';\nimport semver from 'semver';\nimport type * as lockfileTypes from '@pnpm/lockfile.types';\nimport type * as pnpmTypes from '@pnpm/types';\n\nimport { RushConfiguration, type RushConfigurationProject, type Subspace } from '@rushstack/rush-sdk';\nimport { CommandLineAction } from '@rushstack/ts-command-line';\nimport { Colorize, type ITerminal } from '@rushstack/terminal';\nimport { AlreadyReportedError, Async, FileSystem, JsonFile, JsonSchema } from '@rushstack/node-core-library';\n\nimport lockfileLintSchema from '../../../schemas/lockfile-lint.schema.json';\nimport { LOCKFILE_EXPLORER_FOLDERNAME, LOCKFILE_LINT_JSON_FILENAME } from '../../../constants/common';\nimport type { LintCommandLineParser } from '../LintCommandLineParser';\nimport {\n getShrinkwrapFileMajorVersion,\n parseDependencyPath,\n splicePackageWithVersion\n} from '../../../utils/shrinkwrap';\n\nexport interface ILintRule {\n rule: 'restrict-versions';\n project: string;\n requiredVersions: Record<string, string>;\n}\n\nexport interface ILockfileLint {\n rules: ILintRule[];\n}\n\nexport interface ILintIssue {\n project: string;\n rule: string;\n message: string;\n}\n\nexport class CheckAction extends CommandLineAction {\n private readonly _terminal: ITerminal;\n\n private _rushConfiguration!: RushConfiguration;\n private _checkedProjects: Set<RushConfigurationProject>;\n private _docMap: Map<string, lockfileTypes.LockfileObject>;\n\n public constructor(parser: LintCommandLineParser) {\n super({\n actionName: 'check',\n summary: 'Check and report dependency issues in your workspace',\n documentation:\n 'This command applies the policies that are configured in ' +\n LOCKFILE_LINT_JSON_FILENAME +\n ', reporting any problems found in your PNPM workspace.'\n });\n\n this._terminal = parser.globalTerminal;\n this._checkedProjects = new Set();\n this._docMap = new Map();\n }\n\n private async _checkVersionCompatibilityAsync(\n shrinkwrapFileMajorVersion: number,\n packages: lockfileTypes.PackageSnapshots | undefined,\n dependencyPath: pnpmTypes.DepPath,\n requiredVersions: Record<string, string>,\n checkedDependencyPaths: Set<string>\n ): Promise<void> {\n if (packages && packages[dependencyPath] && !checkedDependencyPaths.has(dependencyPath)) {\n checkedDependencyPaths.add(dependencyPath);\n const { name, version } = parseDependencyPath(shrinkwrapFileMajorVersion, dependencyPath);\n if (name in requiredVersions && !semver.satisfies(version, requiredVersions[name])) {\n throw new Error(\n `The version of \"${name}\" should match \"${requiredVersions[name]}\";` +\n ` actual version is \"${version}\"`\n );\n }\n\n await Promise.all(\n Object.entries(packages[dependencyPath].dependencies ?? {}).map(\n async ([dependencyPackageName, dependencyPackageVersion]) => {\n await this._checkVersionCompatibilityAsync(\n shrinkwrapFileMajorVersion,\n packages,\n splicePackageWithVersion(\n shrinkwrapFileMajorVersion,\n dependencyPackageName,\n dependencyPackageVersion\n ) as pnpmTypes.DepPath,\n requiredVersions,\n checkedDependencyPaths\n );\n }\n )\n );\n }\n }\n\n private async _searchAndValidateDependenciesAsync(\n project: RushConfigurationProject,\n requiredVersions: Record<string, string>\n ): Promise<void> {\n this._terminal.writeLine(`Checking project \"${project.packageName}\"`);\n\n const projectFolder: string = project.projectFolder;\n const subspace: Subspace = project.subspace;\n const shrinkwrapFilename: string = subspace.getCommittedShrinkwrapFilePath();\n let doc: lockfileTypes.LockfileObject;\n if (this._docMap.has(shrinkwrapFilename)) {\n doc = this._docMap.get(shrinkwrapFilename)!;\n } else {\n const pnpmLockfileText: string = await FileSystem.readFileAsync(shrinkwrapFilename);\n doc = yaml.load(pnpmLockfileText) as lockfileTypes.LockfileObject;\n this._docMap.set(shrinkwrapFilename, doc);\n }\n const { importers, lockfileVersion, packages } = doc;\n const shrinkwrapFileMajorVersion: number = getShrinkwrapFileMajorVersion(lockfileVersion);\n const checkedDependencyPaths: Set<string> = new Set<string>();\n\n await Promise.all(\n Object.entries(importers).map(async ([relativePath, { dependencies }]) => {\n if (path.resolve(projectFolder, relativePath) === projectFolder) {\n const dependenciesEntries: [string, unknown][] = Object.entries(dependencies ?? {});\n for (const [dependencyName, dependencyValue] of dependenciesEntries) {\n const fullDependencyPath: pnpmTypes.DepPath = splicePackageWithVersion(\n shrinkwrapFileMajorVersion,\n dependencyName,\n typeof dependencyValue === 'string'\n ? dependencyValue\n : (\n dependencyValue as {\n version: string;\n specifier: string;\n }\n ).version\n ) as pnpmTypes.DepPath;\n if (fullDependencyPath.includes('link:')) {\n const dependencyProject: RushConfigurationProject | undefined =\n this._rushConfiguration.getProjectByName(dependencyName);\n if (dependencyProject && !this._checkedProjects?.has(dependencyProject)) {\n this._checkedProjects!.add(project);\n await this._searchAndValidateDependenciesAsync(dependencyProject, requiredVersions);\n }\n } else {\n await this._checkVersionCompatibilityAsync(\n shrinkwrapFileMajorVersion,\n packages,\n fullDependencyPath,\n requiredVersions,\n checkedDependencyPaths\n );\n }\n }\n }\n })\n );\n }\n\n private async _performVersionRestrictionCheckAsync(\n requiredVersions: Record<string, string>,\n projectName: string\n ): Promise<string | undefined> {\n try {\n const project: RushConfigurationProject | undefined =\n this._rushConfiguration?.getProjectByName(projectName);\n if (!project) {\n throw new Error(\n `Specified project \"${projectName}\" does not exist in ${LOCKFILE_LINT_JSON_FILENAME}`\n );\n }\n this._checkedProjects.add(project);\n await this._searchAndValidateDependenciesAsync(project, requiredVersions);\n return undefined;\n } catch (e) {\n return e.message;\n }\n }\n\n protected override async onExecuteAsync(): Promise<void> {\n const rushConfiguration: RushConfiguration | undefined = RushConfiguration.tryLoadFromDefaultLocation();\n if (!rushConfiguration) {\n throw new Error(\n 'The \"lockfile-explorer check\" must be executed in a folder that is under a Rush workspace folder'\n );\n }\n this._rushConfiguration = rushConfiguration!;\n\n const lintingFile: string = path.resolve(\n this._rushConfiguration.commonFolder,\n 'config',\n LOCKFILE_EXPLORER_FOLDERNAME,\n LOCKFILE_LINT_JSON_FILENAME\n );\n const { rules }: ILockfileLint = await JsonFile.loadAndValidateAsync(\n lintingFile,\n JsonSchema.fromLoadedObject(lockfileLintSchema)\n );\n const issues: ILintIssue[] = [];\n await Async.forEachAsync(\n rules,\n async ({ requiredVersions, project, rule }) => {\n switch (rule) {\n case 'restrict-versions': {\n const message: string | undefined = await this._performVersionRestrictionCheckAsync(\n requiredVersions,\n project\n );\n if (message) {\n issues.push({ project, rule, message });\n }\n break;\n }\n\n default: {\n throw new Error('Unsupported rule name: ' + rule);\n }\n }\n },\n { concurrency: 50 }\n );\n if (issues.length > 0) {\n this._terminal.writeLine();\n\n // Deterministic order\n for (const issue of issues.sort((a, b): number => {\n let diff: number = a.project.localeCompare(b.project);\n if (diff !== 0) {\n return diff;\n }\n diff = a.rule.localeCompare(b.rule);\n if (diff !== 0) {\n return diff;\n }\n return a.message.localeCompare(b.message);\n })) {\n this._terminal.writeLine(\n Colorize.red('PROBLEM: ') + Colorize.cyan(`[${issue.rule}] `) + issue.message + '\\n'\n );\n }\n\n throw new AlreadyReportedError();\n }\n this._terminal.writeLine(Colorize.green('SUCCESS: ') + 'All checks passed.');\n }\n}\n"]}
@@ -0,0 +1,38 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import path from 'node:path';
4
+ import { CommandLineAction } from '@rushstack/ts-command-line';
5
+ import { Colorize } from '@rushstack/terminal';
6
+ import { RushConfiguration } from '@rushstack/rush-sdk';
7
+ import { FileSystem } from '@rushstack/node-core-library';
8
+ import { LOCKFILE_EXPLORER_FOLDERNAME, LOCKFILE_LINT_JSON_FILENAME } from '../../../constants/common';
9
+ export class InitAction extends CommandLineAction {
10
+ constructor(parser) {
11
+ super({
12
+ actionName: 'init',
13
+ summary: `Create a new ${LOCKFILE_LINT_JSON_FILENAME} config file`,
14
+ documentation: `This command initializes a new ${LOCKFILE_LINT_JSON_FILENAME} config file.` +
15
+ ` The created template file includes source code comments that document the settings.`
16
+ });
17
+ this._terminal = parser.globalTerminal;
18
+ }
19
+ async onExecuteAsync() {
20
+ const rushConfiguration = RushConfiguration.tryLoadFromDefaultLocation();
21
+ if (!rushConfiguration) {
22
+ throw new Error('The "lockfile-explorer check" must be executed in a folder that is under a Rush workspace folder');
23
+ }
24
+ const inputFilePath = path.resolve(__dirname, '../../../assets/lint-init/lockfile-lint-template.json');
25
+ const outputFilePath = path.resolve(rushConfiguration.commonFolder, 'config', LOCKFILE_EXPLORER_FOLDERNAME, LOCKFILE_LINT_JSON_FILENAME);
26
+ if (await FileSystem.existsAsync(outputFilePath)) {
27
+ this._terminal.writeError('The output file already exists:');
28
+ this._terminal.writeLine('\n ' + outputFilePath + '\n');
29
+ throw new Error('Unable to write output file');
30
+ }
31
+ this._terminal.writeLine(Colorize.green('Writing file: ') + outputFilePath);
32
+ await FileSystem.copyFileAsync({
33
+ sourcePath: inputFilePath,
34
+ destinationPath: outputFilePath
35
+ });
36
+ }
37
+ }
38
+ //# sourceMappingURL=InitAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InitAction.js","sourceRoot":"","sources":["../../../../src/cli/lint/actions/InitAction.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAkB,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAG1D,OAAO,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAEtG,MAAM,OAAO,UAAW,SAAQ,iBAAiB;IAG/C,YAAmB,MAA6B;QAC9C,KAAK,CAAC;YACJ,UAAU,EAAE,MAAM;YAClB,OAAO,EAAE,gBAAgB,2BAA2B,cAAc;YAClE,aAAa,EACX,kCAAkC,2BAA2B,eAAe;gBAC5E,uFAAuF;SAC1F,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC;IACzC,CAAC;IAEkB,KAAK,CAAC,cAAc;QACrC,MAAM,iBAAiB,GAAkC,iBAAiB,CAAC,0BAA0B,EAAE,CAAC;QACxG,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,kGAAkG,CACnG,CAAC;QACJ,CAAC;QACD,MAAM,aAAa,GAAW,IAAI,CAAC,OAAO,CACxC,SAAS,EACT,uDAAuD,CACxD,CAAC;QACF,MAAM,cAAc,GAAW,IAAI,CAAC,OAAO,CACzC,iBAAiB,CAAC,YAAY,EAC9B,QAAQ,EACR,4BAA4B,EAC5B,2BAA2B,CAC5B,CAAC;QAEF,IAAI,MAAM,UAAU,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,iCAAiC,CAAC,CAAC;YAC7D,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC;YACzD,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,cAAc,CAAC,CAAC;QAC5E,MAAM,UAAU,CAAC,aAAa,CAAC;YAC7B,UAAU,EAAE,aAAa;YACzB,eAAe,EAAE,cAAc;SAChC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport path from 'node:path';\n\nimport { CommandLineAction } from '@rushstack/ts-command-line';\nimport { Colorize, type ITerminal } from '@rushstack/terminal';\nimport { RushConfiguration } from '@rushstack/rush-sdk';\nimport { FileSystem } from '@rushstack/node-core-library';\n\nimport type { LintCommandLineParser } from '../LintCommandLineParser';\nimport { LOCKFILE_EXPLORER_FOLDERNAME, LOCKFILE_LINT_JSON_FILENAME } from '../../../constants/common';\n\nexport class InitAction extends CommandLineAction {\n private readonly _terminal: ITerminal;\n\n public constructor(parser: LintCommandLineParser) {\n super({\n actionName: 'init',\n summary: `Create a new ${LOCKFILE_LINT_JSON_FILENAME} config file`,\n documentation:\n `This command initializes a new ${LOCKFILE_LINT_JSON_FILENAME} config file.` +\n ` The created template file includes source code comments that document the settings.`\n });\n this._terminal = parser.globalTerminal;\n }\n\n protected override async onExecuteAsync(): Promise<void> {\n const rushConfiguration: RushConfiguration | undefined = RushConfiguration.tryLoadFromDefaultLocation();\n if (!rushConfiguration) {\n throw new Error(\n 'The \"lockfile-explorer check\" must be executed in a folder that is under a Rush workspace folder'\n );\n }\n const inputFilePath: string = path.resolve(\n __dirname,\n '../../../assets/lint-init/lockfile-lint-template.json'\n );\n const outputFilePath: string = path.resolve(\n rushConfiguration.commonFolder,\n 'config',\n LOCKFILE_EXPLORER_FOLDERNAME,\n LOCKFILE_LINT_JSON_FILENAME\n );\n\n if (await FileSystem.existsAsync(outputFilePath)) {\n this._terminal.writeError('The output file already exists:');\n this._terminal.writeLine('\\n ' + outputFilePath + '\\n');\n throw new Error('Unable to write output file');\n }\n\n this._terminal.writeLine(Colorize.green('Writing file: ') + outputFilePath);\n await FileSystem.copyFileAsync({\n sourcePath: inputFilePath,\n destinationPath: outputFilePath\n });\n }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ export const LOCKFILE_LINT_JSON_FILENAME = 'lockfile-lint.json';
4
+ export const LOCKFILE_EXPLORER_FOLDERNAME = 'lockfile-explorer';
5
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/constants/common.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D;AAE3D,MAAM,CAAC,MAAM,2BAA2B,GAAyB,oBAAoB,CAAC;AAEtF,MAAM,CAAC,MAAM,4BAA4B,GAAwB,mBAAmB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nexport const LOCKFILE_LINT_JSON_FILENAME: 'lockfile-lint.json' = 'lockfile-lint.json';\n\nexport const LOCKFILE_EXPLORER_FOLDERNAME: 'lockfile-explorer' = 'lockfile-explorer';\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ export {};
4
+ //# sourceMappingURL=IPnpmfileModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IPnpmfileModule.js","sourceRoot":"","sources":["../../src/graph/IPnpmfileModule.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2DAA2D","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { IPackageJson } from '@rushstack/node-core-library';\n\nexport interface IReadPackageContext {\n log: (message: string) => void;\n}\n\nexport type IReadPackageHook = (\n packageJson: IPackageJson,\n context: IReadPackageContext\n) => IPackageJson | Promise<IPackageJson>;\n\nexport interface IPnpmHooks {\n readPackage?: IReadPackageHook;\n}\n\n/**\n * Type of the `.pnpmfile.cjs` module.\n */\nexport interface IPnpmfileModule {\n hooks?: IPnpmHooks;\n}\n"]}
@@ -0,0 +1,86 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2
+ // See LICENSE in the project root for license information.
3
+ import { Worker } from 'node:worker_threads';
4
+ import * as path from 'node:path';
5
+ /**
6
+ * Evals `.pnpmfile.cjs` in an isolated thread, so `transformPackageAsync()` can be used to rewrite
7
+ * package.json files. Calling `disposeAsync()` will free the loaded modules.
8
+ */
9
+ export class PnpmfileRunner {
10
+ constructor(pnpmfilePath) {
11
+ this._nextId = 1000;
12
+ this._promisesById = new Map();
13
+ this._disposed = false;
14
+ this.logger = undefined;
15
+ this._worker = new Worker(path.join(`${__dirname}/pnpmfileRunnerWorkerThread.js`), {
16
+ workerData: { pnpmfilePath }
17
+ });
18
+ this._worker.on('message', (message) => {
19
+ const id = message.id;
20
+ const promise = this._promisesById.get(id);
21
+ if (!promise) {
22
+ return;
23
+ }
24
+ if (message.kind === 'return') {
25
+ this._promisesById.delete(id);
26
+ // TODO: Validate the user's readPackage() return value
27
+ const result = message.result;
28
+ promise.resolve(result);
29
+ }
30
+ else if (message.kind === 'log') {
31
+ // No this._promisesById.delete(id) for this case
32
+ if (this.logger) {
33
+ this.logger(message.log);
34
+ }
35
+ else {
36
+ console.log('.pnpmfile.cjs: ' + message.log);
37
+ }
38
+ }
39
+ else {
40
+ this._promisesById.delete(id);
41
+ promise.reject(new Error(message.error || 'An unknown error occurred'));
42
+ }
43
+ });
44
+ this._worker.on('error', (err) => {
45
+ for (const promise of this._promisesById.values()) {
46
+ promise.reject(err);
47
+ }
48
+ this._promisesById.clear();
49
+ });
50
+ this._worker.on('exit', (code) => {
51
+ if (!this._disposed) {
52
+ const error = new Error(`PnpmfileRunner worker thread terminated unexpectedly with exit code ${code}`);
53
+ console.error(error);
54
+ for (const promise of this._promisesById.values()) {
55
+ promise.reject(error);
56
+ }
57
+ this._promisesById.clear();
58
+ }
59
+ });
60
+ }
61
+ /**
62
+ * Invokes the readPackage() hook from .pnpmfile.cjs
63
+ */
64
+ transformPackageAsync(packageJson, packageJsonFullPath) {
65
+ if (this._disposed) {
66
+ return Promise.reject(new Error('The operation failed because PnpmfileRunner has been disposed'));
67
+ }
68
+ const id = this._nextId++;
69
+ return new Promise((resolve, reject) => {
70
+ this._promisesById.set(id, { resolve, reject });
71
+ this._worker.postMessage({ id, packageJson, packageJsonFullPath });
72
+ });
73
+ }
74
+ async disposeAsync() {
75
+ if (this._disposed) {
76
+ return;
77
+ }
78
+ for (const pending of this._promisesById.values()) {
79
+ pending.reject(new Error('Aborted because PnpmfileRunner was disposed'));
80
+ }
81
+ this._promisesById.clear();
82
+ this._disposed = true;
83
+ await this._worker.terminate();
84
+ }
85
+ }
86
+ //# sourceMappingURL=PnpmfileRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PnpmfileRunner.js","sourceRoot":"","sources":["../../src/graph/PnpmfileRunner.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { Worker } from 'node:worker_threads';\nimport * as path from 'node:path';\n\nimport type { IPackageJson } from '@rushstack/node-core-library';\n\nimport type { IRequestMessage, ResponseMessage } from './pnpmfileRunnerWorkerThread';\n\ninterface IPromise {\n resolve: (r: IPackageJson) => void;\n reject: (e: Error) => void;\n}\n\n/**\n * Evals `.pnpmfile.cjs` in an isolated thread, so `transformPackageAsync()` can be used to rewrite\n * package.json files. Calling `disposeAsync()` will free the loaded modules.\n */\nexport class PnpmfileRunner {\n private _worker: Worker;\n private _nextId: number = 1000;\n private _promisesById: Map<number, IPromise> = new Map();\n private _disposed: boolean = false;\n\n public logger: ((message: string) => void) | undefined = undefined;\n\n public constructor(pnpmfilePath: string) {\n this._worker = new Worker(path.join(`${__dirname}/pnpmfileRunnerWorkerThread.js`), {\n workerData: { pnpmfilePath }\n });\n\n this._worker.on('message', (message: ResponseMessage) => {\n const id: number = message.id;\n const promise: IPromise | undefined = this._promisesById.get(id);\n if (!promise) {\n return;\n }\n\n if (message.kind === 'return') {\n this._promisesById.delete(id);\n // TODO: Validate the user's readPackage() return value\n const result: IPackageJson = message.result as IPackageJson;\n promise.resolve(result);\n } else if (message.kind === 'log') {\n // No this._promisesById.delete(id) for this case\n if (this.logger) {\n this.logger(message.log);\n } else {\n console.log('.pnpmfile.cjs: ' + message.log);\n }\n } else {\n this._promisesById.delete(id);\n promise.reject(new Error(message.error || 'An unknown error occurred'));\n }\n });\n\n this._worker.on('error', (err) => {\n for (const promise of this._promisesById.values()) {\n promise.reject(err);\n }\n this._promisesById.clear();\n });\n\n this._worker.on('exit', (code) => {\n if (!this._disposed) {\n const error: Error = new Error(\n `PnpmfileRunner worker thread terminated unexpectedly with exit code ${code}`\n );\n console.error(error);\n for (const promise of this._promisesById.values()) {\n promise.reject(error);\n }\n this._promisesById.clear();\n }\n });\n }\n\n /**\n * Invokes the readPackage() hook from .pnpmfile.cjs\n */\n public transformPackageAsync(\n packageJson: IPackageJson,\n packageJsonFullPath: string\n ): Promise<IPackageJson> {\n if (this._disposed) {\n return Promise.reject(new Error('The operation failed because PnpmfileRunner has been disposed'));\n }\n\n const id: number = this._nextId++;\n return new Promise((resolve, reject) => {\n this._promisesById.set(id, { resolve, reject });\n this._worker.postMessage({ id, packageJson, packageJsonFullPath } satisfies IRequestMessage);\n });\n }\n\n public async disposeAsync(): Promise<void> {\n if (this._disposed) {\n return;\n }\n for (const pending of this._promisesById.values()) {\n pending.reject(new Error('Aborted because PnpmfileRunner was disposed'));\n }\n this._promisesById.clear();\n this._disposed = true;\n await this._worker.terminate();\n }\n}\n"]}