hardhat 2.19.0 → 2.19.2

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 (43) hide show
  1. package/builtin-tasks/compile.js +13 -8
  2. package/builtin-tasks/compile.js.map +1 -1
  3. package/builtin-tasks/test.js +2 -2
  4. package/builtin-tasks/test.js.map +1 -1
  5. package/internal/artifacts.d.ts +1 -0
  6. package/internal/artifacts.d.ts.map +1 -1
  7. package/internal/artifacts.js +8 -8
  8. package/internal/artifacts.js.map +1 -1
  9. package/internal/cli/autocomplete.d.ts.map +1 -1
  10. package/internal/cli/autocomplete.js +100 -39
  11. package/internal/cli/autocomplete.js.map +1 -1
  12. package/internal/cli/bootstrap.js +0 -0
  13. package/internal/cli/project-creation.js +6 -6
  14. package/internal/core/config/config-loading.d.ts.map +1 -1
  15. package/internal/core/config/config-loading.js +6 -3
  16. package/internal/core/config/config-loading.js.map +1 -1
  17. package/internal/core/project-structure.d.ts.map +1 -1
  18. package/internal/core/project-structure.js +6 -0
  19. package/internal/core/project-structure.js.map +1 -1
  20. package/internal/core/providers/gas-providers.d.ts.map +1 -1
  21. package/internal/core/providers/gas-providers.js +15 -1
  22. package/internal/core/providers/gas-providers.js.map +1 -1
  23. package/internal/core/typescript-support.d.ts +2 -0
  24. package/internal/core/typescript-support.d.ts.map +1 -1
  25. package/internal/core/typescript-support.js +12 -4
  26. package/internal/core/typescript-support.js.map +1 -1
  27. package/internal/solidity/compiler/downloader.d.ts +2 -2
  28. package/internal/solidity/compiler/downloader.d.ts.map +1 -1
  29. package/internal/solidity/compiler/downloader.js +11 -1
  30. package/internal/solidity/compiler/downloader.js.map +1 -1
  31. package/package.json +18 -19
  32. package/recommended-gitignore.txt +9 -6
  33. package/src/builtin-tasks/compile.ts +37 -36
  34. package/src/builtin-tasks/test.ts +9 -4
  35. package/src/internal/artifacts.ts +13 -16
  36. package/src/internal/cli/autocomplete.ts +136 -48
  37. package/src/internal/cli/cli.ts +0 -0
  38. package/src/internal/cli/project-creation.ts +6 -6
  39. package/src/internal/core/config/config-loading.ts +15 -4
  40. package/src/internal/core/project-structure.ts +7 -0
  41. package/src/internal/core/providers/gas-providers.ts +20 -1
  42. package/src/internal/core/typescript-support.ts +12 -4
  43. package/src/internal/solidity/compiler/downloader.ts +24 -2
@@ -7,7 +7,11 @@ import { HARDHAT_NETWORK_NAME } from "../internal/constants";
7
7
  import { subtask, task } from "../internal/core/config/config-env";
8
8
  import { HardhatError } from "../internal/core/errors";
9
9
  import { ERRORS } from "../internal/core/errors-list";
10
- import { isRunningWithTypescript } from "../internal/core/typescript-support";
10
+ import {
11
+ isJavascriptFile,
12
+ isRunningWithTypescript,
13
+ isTypescriptFile,
14
+ } from "../internal/core/typescript-support";
11
15
  import { getForkCacheDirPath } from "../internal/hardhat-network/provider/utils/disk-cache";
12
16
  import { showForkRecommendationsBannerIfNecessary } from "../internal/hardhat-network/provider/utils/fork-recomendations-banner";
13
17
  import { pluralize } from "../internal/util/strings";
@@ -40,15 +44,16 @@ subtask(TASK_TEST_GET_TEST_FILES)
40
44
 
41
45
  const jsFiles = await getAllFilesMatching(
42
46
  config.paths.tests,
43
- (f) => f.endsWith(".js") || f.endsWith(".cjs") || f.endsWith(".mjs")
47
+ isJavascriptFile
44
48
  );
45
49
 
46
50
  if (!isRunningWithTypescript(config)) {
47
51
  return jsFiles;
48
52
  }
49
53
 
50
- const tsFiles = await getAllFilesMatching(config.paths.tests, (f) =>
51
- f.endsWith(".ts")
54
+ const tsFiles = await getAllFilesMatching(
55
+ config.paths.tests,
56
+ isTypescriptFile
52
57
  );
53
58
 
54
59
  return [...jsFiles, ...tsFiles];
@@ -154,14 +154,8 @@ export class Artifacts implements IArtifacts {
154
154
  return cached;
155
155
  }
156
156
 
157
- const buildInfosDir = path.join(this._artifactsPath, BUILD_INFO_DIR_NAME);
158
-
159
- const paths = await getAllFilesMatching(
160
- this._artifactsPath,
161
- (f) =>
162
- f.endsWith(".json") &&
163
- !f.startsWith(buildInfosDir) &&
164
- !f.endsWith(".dbg.json")
157
+ const paths = await getAllFilesMatching(this._artifactsPath, (f) =>
158
+ this._isArtifactPath(f)
165
159
  );
166
160
 
167
161
  const result = paths.sort();
@@ -563,14 +557,8 @@ export class Artifacts implements IArtifacts {
563
557
  return cached;
564
558
  }
565
559
 
566
- const buildInfosDir = path.join(this._artifactsPath, BUILD_INFO_DIR_NAME);
567
-
568
- const paths = getAllFilesMatchingSync(
569
- this._artifactsPath,
570
- (f) =>
571
- f.endsWith(".json") &&
572
- !f.startsWith(buildInfosDir) &&
573
- !f.endsWith(".dbg.json")
560
+ const paths = getAllFilesMatchingSync(this._artifactsPath, (f) =>
561
+ this._isArtifactPath(f)
574
562
  );
575
563
 
576
564
  const result = paths.sort();
@@ -934,6 +922,15 @@ Please replace "${contractName}" for the correct contract name wherever you are
934
922
 
935
923
  return undefined;
936
924
  }
925
+
926
+ private _isArtifactPath(file: string) {
927
+ return (
928
+ file.endsWith(".json") &&
929
+ file !== path.join(this._artifactsPath, "package.json") &&
930
+ !file.startsWith(path.join(this._artifactsPath, BUILD_INFO_DIR_NAME)) &&
931
+ !file.endsWith(".dbg.json")
932
+ );
933
+ }
937
934
  }
938
935
 
939
936
  /**
@@ -2,7 +2,7 @@ import findup from "find-up";
2
2
  import * as fs from "fs-extra";
3
3
  import * as path from "path";
4
4
 
5
- import { HardhatRuntimeEnvironment } from "../../types";
5
+ import { HardhatRuntimeEnvironment, TaskDefinition } from "../../types";
6
6
  import { HARDHAT_PARAM_DEFINITIONS } from "../core/params/hardhat-params";
7
7
  import { getCacheDir } from "../util/global-dir";
8
8
  import { createNonCryptographicHashBasedIdentifier } from "../util/hash";
@@ -22,19 +22,30 @@ interface CompletionEnv {
22
22
  point: number;
23
23
  }
24
24
 
25
+ interface Task {
26
+ name: string;
27
+ description: string;
28
+ isSubtask: boolean;
29
+ paramDefinitions: {
30
+ [paramName: string]: {
31
+ name: string;
32
+ description: string;
33
+ isFlag: boolean;
34
+ };
35
+ };
36
+ }
37
+
25
38
  interface CompletionData {
26
39
  networks: string[];
27
40
  tasks: {
28
- [taskName: string]: {
41
+ [taskName: string]: Task;
42
+ };
43
+ scopes: {
44
+ [scopeName: string]: {
29
45
  name: string;
30
46
  description: string;
31
- isSubtask: boolean;
32
- paramDefinitions: {
33
- [paramName: string]: {
34
- name: string;
35
- description: string;
36
- isFlag: boolean;
37
- };
47
+ tasks: {
48
+ [taskName: string]: Task;
38
49
  };
39
50
  };
40
51
  };
@@ -63,12 +74,12 @@ export async function complete({
63
74
  return [];
64
75
  }
65
76
 
66
- const { networks, tasks } = completionData;
77
+ const { networks, tasks, scopes } = completionData;
67
78
 
68
79
  const words = line.split(/\s+/).filter((x) => x.length > 0);
69
80
 
70
81
  const wordsBeforeCursor = line.slice(0, point).split(/\s+/);
71
- // examples:
82
+ // 'prev' and 'last' variables examples:
72
83
  // `hh compile --network|` => prev: "compile" last: "--network"
73
84
  // `hh compile --network |` => prev: "--network" last: ""
74
85
  // `hh compile --network ha|` => prev: "--network" last: "ha"
@@ -83,28 +94,58 @@ export async function complete({
83
94
  }))
84
95
  .filter((x) => !words.includes(x.name));
85
96
 
86
- // check if the user entered a task
87
- let task: string | undefined;
97
+ // Get the task or scope if the user has entered one
98
+ let taskName: string | undefined;
99
+ let scopeName: string | undefined;
100
+
88
101
  let index = 1;
89
102
  while (index < words.length) {
90
- if (isGlobalFlag(words[index])) {
103
+ const word = words[index];
104
+
105
+ if (isGlobalFlag(word)) {
91
106
  index += 1;
92
- } else if (isGlobalParam(words[index])) {
107
+ } else if (isGlobalParam(word)) {
93
108
  index += 2;
94
- } else if (words[index].startsWith("--")) {
109
+ } else if (word.startsWith("--")) {
95
110
  index += 1;
96
111
  } else {
97
- task = words[index];
98
- break;
112
+ // Possible scenarios:
113
+ // - no task or scope: `hh `
114
+ // - only a task: `hh task `
115
+ // - only a scope: `hh scope `
116
+ // - both a scope and a task (the task always follow the scope): `hh scope task `
117
+ // Between a scope and a task there could be other words, e.g.: `hh scope --flag task `
118
+ if (scopeName === undefined) {
119
+ if (tasks[word] !== undefined) {
120
+ taskName = word;
121
+ break;
122
+ } else if (scopes[word] !== undefined) {
123
+ scopeName = word;
124
+ }
125
+ } else {
126
+ taskName = word;
127
+ break;
128
+ }
129
+
130
+ index += 1;
99
131
  }
100
132
  }
101
133
 
102
- // if a task was found but it's equal to the last word, it means
103
- // that the cursor is after the task, we ignore the task in this
104
- // case because if you have a task `foo` and `foobar` and the
105
- // line is: `hh foo|`, we want tasks to be suggested
106
- if (task === last) {
107
- task = undefined;
134
+ // If a task or a scope is found and it is equal to the last word,
135
+ // this indicates that the cursor is positioned after the task or scope.
136
+ // In this case, we ignore the task or scope. For instance, if you have a task or a scope named 'foo' and 'foobar',
137
+ // and the line is 'hh foo|', we want to suggest the value for 'foo' and 'foobar'.
138
+ // Possible scenarios:
139
+ // - no task or scope: `hh ` -> task and scope already undefined
140
+ // - only a task: `hh task ` -> task set to undefined, scope already undefined
141
+ // - only a scope: `hh scope ` -> scope set to undefined, task already undefined
142
+ // - both a scope and a task (the task always follow the scope): `hh scope task ` -> task set to undefined, scope stays defined
143
+ if (taskName === last || scopeName === last) {
144
+ if (taskName !== undefined && scopeName !== undefined) {
145
+ [taskName, scopeName] = [undefined, scopeName];
146
+ } else {
147
+ [taskName, scopeName] = [undefined, undefined];
148
+ }
108
149
  }
109
150
 
110
151
  if (prev === "--network") {
@@ -114,6 +155,16 @@ export async function complete({
114
155
  }));
115
156
  }
116
157
 
158
+ const scopeDefinition =
159
+ scopeName === undefined ? undefined : scopes[scopeName];
160
+
161
+ const taskDefinition =
162
+ taskName === undefined
163
+ ? undefined
164
+ : scopeDefinition === undefined
165
+ ? tasks[taskName]
166
+ : scopeDefinition.tasks[taskName];
167
+
117
168
  // if the previous word is a param, then a value is expected
118
169
  // we don't complete anything here
119
170
  if (prev.startsWith("-")) {
@@ -125,39 +176,60 @@ export async function complete({
125
176
  }
126
177
 
127
178
  const isTaskParam =
128
- task !== undefined &&
129
- tasks[task]?.paramDefinitions[paramName]?.isFlag === false;
179
+ taskDefinition?.paramDefinitions[paramName]?.isFlag === false;
130
180
 
131
181
  if (isTaskParam) {
132
182
  return HARDHAT_COMPLETE_FILES;
133
183
  }
134
184
  }
135
185
 
136
- // if there's no task, we complete either tasks or params
137
- if (task === undefined || tasks[task] === undefined) {
186
+ // If there's no task or scope, we complete either tasks and scopes or params
187
+ if (taskDefinition === undefined && scopeDefinition === undefined) {
188
+ if (last.startsWith("-")) {
189
+ return coreParams.filter((param) => startsWithLast(param.name));
190
+ }
191
+
138
192
  const taskSuggestions = Object.values(tasks)
139
193
  .filter((x) => !x.isSubtask)
140
194
  .map((x) => ({
141
195
  name: x.name,
142
196
  description: x.description,
143
197
  }));
144
- if (last.startsWith("-")) {
145
- return coreParams.filter((param) => startsWithLast(param.name));
146
- }
147
- return taskSuggestions.filter((x) => startsWithLast(x.name));
198
+
199
+ const scopeSuggestions = Object.values(scopes).map((x) => ({
200
+ name: x.name,
201
+ description: x.description,
202
+ }));
203
+
204
+ return taskSuggestions
205
+ .concat(scopeSuggestions)
206
+ .filter((x) => startsWithLast(x.name));
207
+ }
208
+
209
+ // If there's a scope but not a task, we complete with the scopes'tasks
210
+ if (taskDefinition === undefined && scopeDefinition !== undefined) {
211
+ return Object.values(scopes[scopeName!].tasks)
212
+ .filter((x) => !x.isSubtask)
213
+ .map((x) => ({
214
+ name: x.name,
215
+ description: x.description,
216
+ }))
217
+ .filter((x) => startsWithLast(x.name));
148
218
  }
149
219
 
150
220
  if (!last.startsWith("-")) {
151
221
  return HARDHAT_COMPLETE_FILES;
152
222
  }
153
223
 
154
- // if there's a task and the last word starts with -, we complete its params and the global params
155
- const taskParams = Object.values(tasks[task].paramDefinitions)
156
- .map((param) => ({
157
- name: ArgumentsParser.paramNameToCLA(param.name),
158
- description: param.description,
159
- }))
160
- .filter((x) => !words.includes(x.name));
224
+ const taskParams =
225
+ taskDefinition === undefined
226
+ ? []
227
+ : Object.values(taskDefinition.paramDefinitions)
228
+ .map((param) => ({
229
+ name: ArgumentsParser.paramNameToCLA(param.name),
230
+ description: param.description,
231
+ }))
232
+ .filter((x) => !words.includes(x.name));
161
233
 
162
234
  return [...taskParams, ...coreParams].filter((suggestion) =>
163
235
  startsWithLast(suggestion.name)
@@ -195,20 +267,20 @@ async function getCompletionData(): Promise<CompletionData | undefined> {
195
267
 
196
268
  // we extract the tasks data explicitly to make sure everything
197
269
  // is serializable and to avoid saving unnecessary things from the HRE
198
- const tasks: CompletionData["tasks"] = mapValues(hre.tasks, (task) => ({
199
- name: task.name,
200
- description: task.description ?? "",
201
- isSubtask: task.isSubtask,
202
- paramDefinitions: mapValues(task.paramDefinitions, (paramDefinition) => ({
203
- name: paramDefinition.name,
204
- description: paramDefinition.description ?? "",
205
- isFlag: paramDefinition.isFlag,
206
- })),
270
+ const tasks: CompletionData["tasks"] = mapValues(hre.tasks, (task) =>
271
+ getTaskFromTaskDefinition(task)
272
+ );
273
+
274
+ const scopes: CompletionData["scopes"] = mapValues(hre.scopes, (scope) => ({
275
+ name: scope.name,
276
+ description: scope.description ?? "",
277
+ tasks: mapValues(scope.tasks, (task) => getTaskFromTaskDefinition(task)),
207
278
  }));
208
279
 
209
280
  const completionData: CompletionData = {
210
281
  networks,
211
282
  tasks,
283
+ scopes,
212
284
  };
213
285
 
214
286
  await saveCachedCompletionData(projectId, completionData, mtimes);
@@ -216,6 +288,22 @@ async function getCompletionData(): Promise<CompletionData | undefined> {
216
288
  return completionData;
217
289
  }
218
290
 
291
+ function getTaskFromTaskDefinition(taskDef: TaskDefinition): Task {
292
+ return {
293
+ name: taskDef.name,
294
+ description: taskDef.description ?? "",
295
+ isSubtask: taskDef.isSubtask,
296
+ paramDefinitions: mapValues(
297
+ taskDef.paramDefinitions,
298
+ (paramDefinition) => ({
299
+ name: paramDefinition.name,
300
+ description: paramDefinition.description ?? "",
301
+ isFlag: paramDefinition.isFlag,
302
+ })
303
+ ),
304
+ };
305
+ }
306
+
219
307
  function getProjectId(): string | undefined {
220
308
  const packageJsonPath = findup.sync("package.json");
221
309
 
File without changes
@@ -45,17 +45,17 @@ const HARDHAT_PACKAGE_NAME = "hardhat";
45
45
  const PROJECT_DEPENDENCIES: Dependencies = {};
46
46
 
47
47
  const ETHERS_PROJECT_DEPENDENCIES: Dependencies = {
48
- "@nomicfoundation/hardhat-toolbox": "^3.0.0",
48
+ "@nomicfoundation/hardhat-toolbox": "^4.0.0",
49
49
  };
50
50
 
51
51
  const VIEM_PROJECT_DEPENDENCIES: Dependencies = {
52
- "@nomicfoundation/hardhat-toolbox-viem": "^1.0.0",
52
+ "@nomicfoundation/hardhat-toolbox-viem": "^2.0.0",
53
53
  };
54
54
 
55
55
  const PEER_DEPENDENCIES: Dependencies = {
56
56
  hardhat: "^2.14.0",
57
57
  "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
58
- "@nomicfoundation/hardhat-verify": "^1.0.0",
58
+ "@nomicfoundation/hardhat-verify": "^2.0.0",
59
59
  chai: "^4.2.0",
60
60
  "hardhat-gas-reporter": "^1.0.8",
61
61
  "solidity-coverage": "^0.8.0",
@@ -65,9 +65,9 @@ const ETHERS_PEER_DEPENDENCIES: Dependencies = {
65
65
  "@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
66
66
  "@nomicfoundation/hardhat-ethers": "^3.0.0",
67
67
  ethers: "^6.4.0",
68
- "@typechain/hardhat": "^8.0.0",
69
- typechain: "^8.1.0",
70
- "@typechain/ethers-v6": "^0.4.0",
68
+ "@typechain/hardhat": "^9.0.0",
69
+ typechain: "^8.3.0",
70
+ "@typechain/ethers-v6": "^0.5.0",
71
71
  };
72
72
 
73
73
  const VIEM_PEER_DEPENDENCIES: Dependencies = {
@@ -218,7 +218,10 @@ export function analyzeModuleNotFoundError(error: any, configPath: string) {
218
218
 
219
219
  const missingPeerDependencies: { [name: string]: string } = {};
220
220
  for (const [peerDependency, version] of Object.entries(peerDependencies)) {
221
- const peerDependencyPackageJson = readPackageJson(peerDependency);
221
+ const peerDependencyPackageJson = readPackageJson(
222
+ peerDependency,
223
+ configPath
224
+ );
222
225
  if (peerDependencyPackageJson === undefined) {
223
226
  missingPeerDependencies[peerDependency] = version;
224
227
  }
@@ -244,10 +247,18 @@ interface PackageJson {
244
247
  };
245
248
  }
246
249
 
247
- function readPackageJson(packageName: string): PackageJson | undefined {
250
+ function readPackageJson(
251
+ packageName: string,
252
+ configPath: string
253
+ ): PackageJson | undefined {
254
+ const resolve = require("resolve") as typeof import("resolve");
255
+
248
256
  try {
249
- const packageJsonPath = require.resolve(
250
- path.join(packageName, "package.json")
257
+ const packageJsonPath = resolve.sync(
258
+ path.join(packageName, "package.json"),
259
+ {
260
+ basedir: path.dirname(configPath),
261
+ }
251
262
  );
252
263
 
253
264
  return require(packageJsonPath);
@@ -10,10 +10,12 @@ import { ERRORS } from "./errors-list";
10
10
  const JS_CONFIG_FILENAME = "hardhat.config.js";
11
11
  const CJS_CONFIG_FILENAME = "hardhat.config.cjs";
12
12
  const TS_CONFIG_FILENAME = "hardhat.config.ts";
13
+ const CTS_CONFIG_FILENAME = "hardhat.config.cts";
13
14
 
14
15
  export function isCwdInsideProject() {
15
16
  return (
16
17
  findUp.sync(TS_CONFIG_FILENAME) !== null ||
18
+ findUp.sync(CTS_CONFIG_FILENAME) !== null ||
17
19
  findUp.sync(CJS_CONFIG_FILENAME) !== null ||
18
20
  findUp.sync(JS_CONFIG_FILENAME) !== null
19
21
  );
@@ -25,6 +27,11 @@ export function getUserConfigPath() {
25
27
  return tsConfigPath;
26
28
  }
27
29
 
30
+ const ctsConfigPath = findUp.sync(CTS_CONFIG_FILENAME);
31
+ if (ctsConfigPath !== null) {
32
+ return ctsConfigPath;
33
+ }
34
+
28
35
  const cjsConfigPath = findUp.sync(CJS_CONFIG_FILENAME);
29
36
  if (cjsConfigPath !== null) {
30
37
  return cjsConfigPath;
@@ -254,6 +254,25 @@ export class AutomaticGasPriceProvider extends ProviderWrapper {
254
254
  ],
255
255
  })) as { baseFeePerGas: string[]; reward: string[][] };
256
256
 
257
+ let maxPriorityFeePerGas = rpcQuantityToBigInt(response.reward[0][0]);
258
+
259
+ if (maxPriorityFeePerGas === 0n) {
260
+ try {
261
+ const suggestedMaxPriorityFeePerGas =
262
+ (await this._wrappedProvider.request({
263
+ method: "eth_maxPriorityFeePerGas",
264
+ params: [],
265
+ })) as string;
266
+
267
+ maxPriorityFeePerGas = rpcQuantityToBigInt(
268
+ suggestedMaxPriorityFeePerGas
269
+ );
270
+ } catch {
271
+ // if eth_maxPriorityFeePerGas does not exist, use 1 wei
272
+ maxPriorityFeePerGas = 1n;
273
+ }
274
+ }
275
+
257
276
  return {
258
277
  // Each block increases the base fee by 1/8 at most, when full.
259
278
  // We have the next block's base fee, so we compute a cap for the
@@ -268,7 +287,7 @@ export class AutomaticGasPriceProvider extends ProviderWrapper {
268
287
  (AutomaticGasPriceProvider.EIP1559_BASE_FEE_MAX_FULL_BLOCKS_PREFERENCE -
269
288
  1n),
270
289
 
271
- maxPriorityFeePerGas: rpcQuantityToBigInt(response.reward[0][0]),
290
+ maxPriorityFeePerGas,
272
291
  };
273
292
  } catch {
274
293
  this._nodeHasFeeHistory = false;
@@ -13,14 +13,14 @@ let cachedIsTypescriptSupported: boolean | undefined;
13
13
  */
14
14
  export function willRunWithTypescript(configPath?: string): boolean {
15
15
  const config = resolveConfigPath(configPath);
16
- return isTypescriptFile(config);
16
+ return isNonEsmTypescriptFile(config);
17
17
  }
18
18
 
19
19
  /**
20
20
  * Returns true if an Hardhat is already running with typescript.
21
21
  */
22
22
  export function isRunningWithTypescript(config: HardhatConfig): boolean {
23
- return isTypescriptFile(config.paths.configFile);
23
+ return isNonEsmTypescriptFile(config.paths.configFile);
24
24
  }
25
25
 
26
26
  export function isTypescriptSupported() {
@@ -80,6 +80,14 @@ export function loadTsNode(
80
80
  require(tsNodeRequirement);
81
81
  }
82
82
 
83
- function isTypescriptFile(path: string): boolean {
84
- return path.endsWith(".ts");
83
+ function isNonEsmTypescriptFile(path: string): boolean {
84
+ return /\.(ts|cts)$/i.test(path);
85
+ }
86
+
87
+ export function isTypescriptFile(path: string): boolean {
88
+ return /\.(ts|cts|mts)$/i.test(path);
89
+ }
90
+
91
+ export function isJavascriptFile(path: string): boolean {
92
+ return /\.(js|cjs|mjs)$/i.test(path);
85
93
  }
@@ -60,7 +60,11 @@ export interface ICompilerDownloader {
60
60
  * Downloads the compiler for a given version, which can later be obtained
61
61
  * with getCompiler.
62
62
  */
63
- downloadCompiler(version: string): Promise<void>;
63
+ downloadCompiler(
64
+ version: string,
65
+ downloadStartedCb: (isCompilerDownloaded: boolean) => Promise<any>,
66
+ downloadEndedCb: (isCompilerDownloaded: boolean) => Promise<any>
67
+ ): Promise<void>;
64
68
 
65
69
  /**
66
70
  * Returns the compiler, which MUST be downloaded before calling this function.
@@ -146,8 +150,24 @@ export class CompilerDownloader implements ICompilerDownloader {
146
150
  return fsExtra.pathExists(downloadPath);
147
151
  }
148
152
 
149
- public async downloadCompiler(version: string): Promise<void> {
153
+ public async downloadCompiler(
154
+ version: string,
155
+ downloadStartedCb: (isCompilerDownloaded: boolean) => Promise<any>,
156
+ downloadEndedCb: (isCompilerDownloaded: boolean) => Promise<any>
157
+ ): Promise<void> {
158
+ // Since only one process at a time can acquire the mutex, we avoid the risk of downloading the same compiler multiple times.
159
+ // This is because the mutex blocks access until a compiler has been fully downloaded, preventing any new process
160
+ // from checking whether that version of the compiler exists. Without mutex it might incorrectly
161
+ // return false, indicating that the compiler isn't present, even though it is currently being downloaded.
150
162
  await this._mutex.use(async () => {
163
+ const isCompilerDownloaded = await this.isCompilerDownloaded(version);
164
+
165
+ if (isCompilerDownloaded === true) {
166
+ return;
167
+ }
168
+
169
+ await downloadStartedCb(isCompilerDownloaded);
170
+
151
171
  let build = await this._getCompilerBuild(version);
152
172
 
153
173
  if (build === undefined && (await this._shouldDownloadCompilerList())) {
@@ -189,6 +209,8 @@ export class CompilerDownloader implements ICompilerDownloader {
189
209
  }
190
210
 
191
211
  await this._postProcessCompilerDownload(build, downloadPath);
212
+
213
+ await downloadEndedCb(isCompilerDownloaded);
192
214
  });
193
215
  }
194
216