deslop-js 0.0.16-dev.e558efe → 0.0.17-dev.7acf549

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.
package/dist/index.cjs CHANGED
@@ -35,6 +35,10 @@ let node_fs_promises = require("node:fs/promises");
35
35
  let oxc_parser = require("oxc-parser");
36
36
  let typescript = require("typescript");
37
37
  typescript = __toESM(typescript, 1);
38
+ let node_worker_threads = require("node:worker_threads");
39
+ let node_url = require("node:url");
40
+ let node_os = require("node:os");
41
+ node_os = __toESM(node_os, 1);
38
42
  let oxc_resolver = require("oxc-resolver");
39
43
  let minimatch = require("minimatch");
40
44
 
@@ -6141,6 +6145,148 @@ const discoverToolingEntryPoints = (rootDir, workspacePackages) => {
6141
6145
  };
6142
6146
  };
6143
6147
 
6148
+ //#endregion
6149
+ //#region src/utils/resolve-available-concurrency.ts
6150
+ const resolveAvailableConcurrency = () => {
6151
+ const available = node_os.default.availableParallelism();
6152
+ if (!Number.isFinite(available) || available < 1) return 1;
6153
+ return Math.max(1, Math.min(Math.floor(available), 16));
6154
+ };
6155
+
6156
+ //#endregion
6157
+ //#region src/collect/parallel-parse.ts
6158
+ const deserializeErrors = (serializedErrors) => serializedErrors.map((errorJson) => new DeslopError({
6159
+ code: errorJson.code,
6160
+ module: errorJson.module,
6161
+ severity: errorJson.severity,
6162
+ message: errorJson.message,
6163
+ path: errorJson.path,
6164
+ detail: errorJson.detail
6165
+ }));
6166
+ const deserializeParsedSource = (serialized) => ({
6167
+ imports: serialized.imports,
6168
+ exports: serialized.exports,
6169
+ memberAccesses: serialized.memberAccesses,
6170
+ wholeObjectUses: serialized.wholeObjectUses,
6171
+ localIdentifierReferences: serialized.localIdentifierReferences,
6172
+ referencedFilenames: serialized.referencedFilenames,
6173
+ redundantTypePatterns: serialized.redundantTypePatterns,
6174
+ identityWrappers: serialized.identityWrappers,
6175
+ typeDefinitionHashes: serialized.typeDefinitionHashes,
6176
+ inlineTypeLiterals: serialized.inlineTypeLiterals,
6177
+ simplifiableFunctions: serialized.simplifiableFunctions,
6178
+ simplifiableExpressions: serialized.simplifiableExpressions,
6179
+ duplicateConstantCandidates: serialized.duplicateConstantCandidates,
6180
+ errors: deserializeErrors(serialized.errors)
6181
+ });
6182
+ const resolveWorkerPath = () => {
6183
+ const currentUrl = require("url").pathToFileURL(__filename).href;
6184
+ if (currentUrl.endsWith(".ts")) return (0, node_url.fileURLToPath)(new URL("./parse-worker.ts", currentUrl));
6185
+ return (0, node_url.fileURLToPath)(new URL("./parse-worker.mjs", currentUrl));
6186
+ };
6187
+ const createWorker = (workerPath) => {
6188
+ return new node_worker_threads.Worker(workerPath, { ...workerPath.endsWith(".ts") ? { execArgv: ["--import", "tsx"] } : {} });
6189
+ };
6190
+ const waitForReady = (worker) => new Promise((resolve, reject) => {
6191
+ const onMessage = (message) => {
6192
+ if (message.type === "ready") {
6193
+ worker.off("message", onMessage);
6194
+ worker.off("error", onError);
6195
+ resolve();
6196
+ }
6197
+ };
6198
+ const onError = (error) => {
6199
+ worker.off("message", onMessage);
6200
+ worker.off("error", onError);
6201
+ reject(error);
6202
+ };
6203
+ worker.on("message", onMessage);
6204
+ worker.on("error", onError);
6205
+ });
6206
+ const parseFilesWithWorkerPool = async (files, workerCount) => {
6207
+ const workerPath = resolveWorkerPath();
6208
+ const results = new Array(files.length);
6209
+ const workers = [];
6210
+ try {
6211
+ for (let workerIndex = 0; workerIndex < workerCount; workerIndex++) workers.push(createWorker(workerPath));
6212
+ await Promise.all(workers.map(waitForReady));
6213
+ } catch {
6214
+ for (const worker of workers) worker.terminate();
6215
+ return files.map((file) => parseSourceFile(file.path));
6216
+ }
6217
+ let nextFileIndex = 0;
6218
+ let completedCount = 0;
6219
+ return new Promise((resolve, reject) => {
6220
+ const dispatchNext = (worker) => {
6221
+ if (nextFileIndex >= files.length) return;
6222
+ const fileIndex = nextFileIndex;
6223
+ nextFileIndex += 1;
6224
+ worker.postMessage({
6225
+ type: "parse",
6226
+ filePath: files[fileIndex].path,
6227
+ fileIndex
6228
+ });
6229
+ };
6230
+ const onWorkerMessage = (worker) => (message) => {
6231
+ if (message.type === "result") {
6232
+ results[message.fileIndex] = deserializeParsedSource(message.parsed);
6233
+ completedCount += 1;
6234
+ if (completedCount === files.length) {
6235
+ cleanup();
6236
+ resolve(results);
6237
+ } else dispatchNext(worker);
6238
+ } else if (message.type === "error") {
6239
+ results[message.fileIndex] = {
6240
+ imports: [],
6241
+ exports: [],
6242
+ memberAccesses: [],
6243
+ wholeObjectUses: [],
6244
+ localIdentifierReferences: [],
6245
+ referencedFilenames: [],
6246
+ redundantTypePatterns: [],
6247
+ identityWrappers: [],
6248
+ typeDefinitionHashes: [],
6249
+ inlineTypeLiterals: [],
6250
+ simplifiableFunctions: [],
6251
+ simplifiableExpressions: [],
6252
+ duplicateConstantCandidates: [],
6253
+ errors: [new ParseError({
6254
+ code: "parse-failed",
6255
+ message: `Worker parse failed: ${message.errorMessage}`,
6256
+ path: message.filePath
6257
+ })]
6258
+ };
6259
+ completedCount += 1;
6260
+ if (completedCount === files.length) {
6261
+ cleanup();
6262
+ resolve(results);
6263
+ } else dispatchNext(worker);
6264
+ }
6265
+ };
6266
+ const cleanup = () => {
6267
+ for (const worker of workers) worker.terminate();
6268
+ };
6269
+ for (const worker of workers) {
6270
+ worker.on("message", onWorkerMessage(worker));
6271
+ worker.on("error", (error) => {
6272
+ cleanup();
6273
+ reject(error);
6274
+ });
6275
+ }
6276
+ for (const worker of workers) dispatchNext(worker);
6277
+ });
6278
+ };
6279
+ const parseFilesInParallel = async (files) => {
6280
+ if (files.length <= 50) return files.map((file) => parseSourceFile(file.path));
6281
+ const concurrency = resolveAvailableConcurrency();
6282
+ if (concurrency <= 1) return files.map((file) => parseSourceFile(file.path));
6283
+ try {
6284
+ return await parseFilesWithWorkerPool(files, concurrency);
6285
+ } catch {
6286
+ return files.map((file) => parseSourceFile(file.path));
6287
+ }
6288
+ };
6289
+
6144
6290
  //#endregion
6145
6291
  //#region src/utils/is-platform-builtin-or-virtual.ts
6146
6292
  const BUILTIN_SUBPATH_NODE_MODULES = new Set([
@@ -12590,8 +12736,23 @@ const analyze = async (config) => {
12590
12736
  ignorePatterns: [...config.ignorePatterns, ...allExclusionPatterns]
12591
12737
  } : config;
12592
12738
  let files;
12739
+ let discoveredEntries;
12593
12740
  try {
12594
- files = await collectSourceFiles(configWithExclusions);
12741
+ const [collectedFiles, resolvedEntries] = await Promise.all([collectSourceFiles(configWithExclusions), resolveEntries(configWithExclusions).catch((entriesError) => {
12742
+ setupErrors.push(new WorkspaceError({
12743
+ code: "workspace-discovery-failed",
12744
+ message: "resolveEntries failed — defaulting to empty entry set",
12745
+ path: config.rootDir,
12746
+ detail: describeUnknownError(entriesError)
12747
+ }));
12748
+ return {
12749
+ productionEntries: [],
12750
+ testEntries: [],
12751
+ alwaysUsedFiles: []
12752
+ };
12753
+ })]);
12754
+ files = collectedFiles;
12755
+ discoveredEntries = resolvedEntries;
12595
12756
  } catch (collectError) {
12596
12757
  setupErrors.push(new WorkspaceError({
12597
12758
  code: "workspace-discovery-failed",
@@ -12602,22 +12763,6 @@ const analyze = async (config) => {
12602
12763
  }));
12603
12764
  return buildEmptyScanResult(setupErrors, performance.now() - pipelineStartTime);
12604
12765
  }
12605
- let discoveredEntries;
12606
- try {
12607
- discoveredEntries = await resolveEntries(configWithExclusions);
12608
- } catch (entriesError) {
12609
- setupErrors.push(new WorkspaceError({
12610
- code: "workspace-discovery-failed",
12611
- message: "resolveEntries failed — defaulting to empty entry set",
12612
- path: config.rootDir,
12613
- detail: describeUnknownError(entriesError)
12614
- }));
12615
- discoveredEntries = {
12616
- productionEntries: [],
12617
- testEntries: [],
12618
- alwaysUsedFiles: []
12619
- };
12620
- }
12621
12766
  const productionEntrySet = new Set(discoveredEntries.productionEntries);
12622
12767
  const testEntrySet = new Set(discoveredEntries.testEntries);
12623
12768
  const alwaysUsedFileSet = new Set(discoveredEntries.alwaysUsedFiles);
@@ -12644,9 +12789,11 @@ const analyze = async (config) => {
12644
12789
  }));
12645
12790
  return buildEmptyScanResult(setupErrors, performance.now() - pipelineStartTime);
12646
12791
  }
12792
+ const parsedModules = await parseFilesInParallel(files);
12647
12793
  const graphInputs = [];
12648
- for (const file of files) {
12649
- const parsedModule = parseSourceFile(file.path);
12794
+ for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
12795
+ const file = files[fileIndex];
12796
+ const parsedModule = parsedModules[fileIndex];
12650
12797
  const resolvedImportMap = /* @__PURE__ */ new Map();
12651
12798
  const safeResolveImport = (specifier) => {
12652
12799
  try {
package/dist/index.mjs CHANGED
@@ -4,6 +4,9 @@ import fg from "fast-glob";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { parseSync } from "oxc-parser";
6
6
  import ts from "typescript";
7
+ import { Worker } from "node:worker_threads";
8
+ import { fileURLToPath } from "node:url";
9
+ import os from "node:os";
7
10
  import { ResolverFactory } from "oxc-resolver";
8
11
  import { minimatch } from "minimatch";
9
12
 
@@ -6110,6 +6113,148 @@ const discoverToolingEntryPoints = (rootDir, workspacePackages) => {
6110
6113
  };
6111
6114
  };
6112
6115
 
6116
+ //#endregion
6117
+ //#region src/utils/resolve-available-concurrency.ts
6118
+ const resolveAvailableConcurrency = () => {
6119
+ const available = os.availableParallelism();
6120
+ if (!Number.isFinite(available) || available < 1) return 1;
6121
+ return Math.max(1, Math.min(Math.floor(available), 16));
6122
+ };
6123
+
6124
+ //#endregion
6125
+ //#region src/collect/parallel-parse.ts
6126
+ const deserializeErrors = (serializedErrors) => serializedErrors.map((errorJson) => new DeslopError({
6127
+ code: errorJson.code,
6128
+ module: errorJson.module,
6129
+ severity: errorJson.severity,
6130
+ message: errorJson.message,
6131
+ path: errorJson.path,
6132
+ detail: errorJson.detail
6133
+ }));
6134
+ const deserializeParsedSource = (serialized) => ({
6135
+ imports: serialized.imports,
6136
+ exports: serialized.exports,
6137
+ memberAccesses: serialized.memberAccesses,
6138
+ wholeObjectUses: serialized.wholeObjectUses,
6139
+ localIdentifierReferences: serialized.localIdentifierReferences,
6140
+ referencedFilenames: serialized.referencedFilenames,
6141
+ redundantTypePatterns: serialized.redundantTypePatterns,
6142
+ identityWrappers: serialized.identityWrappers,
6143
+ typeDefinitionHashes: serialized.typeDefinitionHashes,
6144
+ inlineTypeLiterals: serialized.inlineTypeLiterals,
6145
+ simplifiableFunctions: serialized.simplifiableFunctions,
6146
+ simplifiableExpressions: serialized.simplifiableExpressions,
6147
+ duplicateConstantCandidates: serialized.duplicateConstantCandidates,
6148
+ errors: deserializeErrors(serialized.errors)
6149
+ });
6150
+ const resolveWorkerPath = () => {
6151
+ const currentUrl = import.meta.url;
6152
+ if (currentUrl.endsWith(".ts")) return fileURLToPath(new URL("./parse-worker.ts", currentUrl));
6153
+ return fileURLToPath(new URL("./parse-worker.mjs", currentUrl));
6154
+ };
6155
+ const createWorker = (workerPath) => {
6156
+ return new Worker(workerPath, { ...workerPath.endsWith(".ts") ? { execArgv: ["--import", "tsx"] } : {} });
6157
+ };
6158
+ const waitForReady = (worker) => new Promise((resolve, reject) => {
6159
+ const onMessage = (message) => {
6160
+ if (message.type === "ready") {
6161
+ worker.off("message", onMessage);
6162
+ worker.off("error", onError);
6163
+ resolve();
6164
+ }
6165
+ };
6166
+ const onError = (error) => {
6167
+ worker.off("message", onMessage);
6168
+ worker.off("error", onError);
6169
+ reject(error);
6170
+ };
6171
+ worker.on("message", onMessage);
6172
+ worker.on("error", onError);
6173
+ });
6174
+ const parseFilesWithWorkerPool = async (files, workerCount) => {
6175
+ const workerPath = resolveWorkerPath();
6176
+ const results = new Array(files.length);
6177
+ const workers = [];
6178
+ try {
6179
+ for (let workerIndex = 0; workerIndex < workerCount; workerIndex++) workers.push(createWorker(workerPath));
6180
+ await Promise.all(workers.map(waitForReady));
6181
+ } catch {
6182
+ for (const worker of workers) worker.terminate();
6183
+ return files.map((file) => parseSourceFile(file.path));
6184
+ }
6185
+ let nextFileIndex = 0;
6186
+ let completedCount = 0;
6187
+ return new Promise((resolve, reject) => {
6188
+ const dispatchNext = (worker) => {
6189
+ if (nextFileIndex >= files.length) return;
6190
+ const fileIndex = nextFileIndex;
6191
+ nextFileIndex += 1;
6192
+ worker.postMessage({
6193
+ type: "parse",
6194
+ filePath: files[fileIndex].path,
6195
+ fileIndex
6196
+ });
6197
+ };
6198
+ const onWorkerMessage = (worker) => (message) => {
6199
+ if (message.type === "result") {
6200
+ results[message.fileIndex] = deserializeParsedSource(message.parsed);
6201
+ completedCount += 1;
6202
+ if (completedCount === files.length) {
6203
+ cleanup();
6204
+ resolve(results);
6205
+ } else dispatchNext(worker);
6206
+ } else if (message.type === "error") {
6207
+ results[message.fileIndex] = {
6208
+ imports: [],
6209
+ exports: [],
6210
+ memberAccesses: [],
6211
+ wholeObjectUses: [],
6212
+ localIdentifierReferences: [],
6213
+ referencedFilenames: [],
6214
+ redundantTypePatterns: [],
6215
+ identityWrappers: [],
6216
+ typeDefinitionHashes: [],
6217
+ inlineTypeLiterals: [],
6218
+ simplifiableFunctions: [],
6219
+ simplifiableExpressions: [],
6220
+ duplicateConstantCandidates: [],
6221
+ errors: [new ParseError({
6222
+ code: "parse-failed",
6223
+ message: `Worker parse failed: ${message.errorMessage}`,
6224
+ path: message.filePath
6225
+ })]
6226
+ };
6227
+ completedCount += 1;
6228
+ if (completedCount === files.length) {
6229
+ cleanup();
6230
+ resolve(results);
6231
+ } else dispatchNext(worker);
6232
+ }
6233
+ };
6234
+ const cleanup = () => {
6235
+ for (const worker of workers) worker.terminate();
6236
+ };
6237
+ for (const worker of workers) {
6238
+ worker.on("message", onWorkerMessage(worker));
6239
+ worker.on("error", (error) => {
6240
+ cleanup();
6241
+ reject(error);
6242
+ });
6243
+ }
6244
+ for (const worker of workers) dispatchNext(worker);
6245
+ });
6246
+ };
6247
+ const parseFilesInParallel = async (files) => {
6248
+ if (files.length <= 50) return files.map((file) => parseSourceFile(file.path));
6249
+ const concurrency = resolveAvailableConcurrency();
6250
+ if (concurrency <= 1) return files.map((file) => parseSourceFile(file.path));
6251
+ try {
6252
+ return await parseFilesWithWorkerPool(files, concurrency);
6253
+ } catch {
6254
+ return files.map((file) => parseSourceFile(file.path));
6255
+ }
6256
+ };
6257
+
6113
6258
  //#endregion
6114
6259
  //#region src/utils/is-platform-builtin-or-virtual.ts
6115
6260
  const BUILTIN_SUBPATH_NODE_MODULES = new Set([
@@ -12559,8 +12704,23 @@ const analyze = async (config) => {
12559
12704
  ignorePatterns: [...config.ignorePatterns, ...allExclusionPatterns]
12560
12705
  } : config;
12561
12706
  let files;
12707
+ let discoveredEntries;
12562
12708
  try {
12563
- files = await collectSourceFiles(configWithExclusions);
12709
+ const [collectedFiles, resolvedEntries] = await Promise.all([collectSourceFiles(configWithExclusions), resolveEntries(configWithExclusions).catch((entriesError) => {
12710
+ setupErrors.push(new WorkspaceError({
12711
+ code: "workspace-discovery-failed",
12712
+ message: "resolveEntries failed — defaulting to empty entry set",
12713
+ path: config.rootDir,
12714
+ detail: describeUnknownError(entriesError)
12715
+ }));
12716
+ return {
12717
+ productionEntries: [],
12718
+ testEntries: [],
12719
+ alwaysUsedFiles: []
12720
+ };
12721
+ })]);
12722
+ files = collectedFiles;
12723
+ discoveredEntries = resolvedEntries;
12564
12724
  } catch (collectError) {
12565
12725
  setupErrors.push(new WorkspaceError({
12566
12726
  code: "workspace-discovery-failed",
@@ -12571,22 +12731,6 @@ const analyze = async (config) => {
12571
12731
  }));
12572
12732
  return buildEmptyScanResult(setupErrors, performance.now() - pipelineStartTime);
12573
12733
  }
12574
- let discoveredEntries;
12575
- try {
12576
- discoveredEntries = await resolveEntries(configWithExclusions);
12577
- } catch (entriesError) {
12578
- setupErrors.push(new WorkspaceError({
12579
- code: "workspace-discovery-failed",
12580
- message: "resolveEntries failed — defaulting to empty entry set",
12581
- path: config.rootDir,
12582
- detail: describeUnknownError(entriesError)
12583
- }));
12584
- discoveredEntries = {
12585
- productionEntries: [],
12586
- testEntries: [],
12587
- alwaysUsedFiles: []
12588
- };
12589
- }
12590
12734
  const productionEntrySet = new Set(discoveredEntries.productionEntries);
12591
12735
  const testEntrySet = new Set(discoveredEntries.testEntries);
12592
12736
  const alwaysUsedFileSet = new Set(discoveredEntries.alwaysUsedFiles);
@@ -12613,9 +12757,11 @@ const analyze = async (config) => {
12613
12757
  }));
12614
12758
  return buildEmptyScanResult(setupErrors, performance.now() - pipelineStartTime);
12615
12759
  }
12760
+ const parsedModules = await parseFilesInParallel(files);
12616
12761
  const graphInputs = [];
12617
- for (const file of files) {
12618
- const parsedModule = parseSourceFile(file.path);
12762
+ for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
12763
+ const file = files[fileIndex];
12764
+ const parsedModule = parsedModules[fileIndex];
12619
12765
  const resolvedImportMap = /* @__PURE__ */ new Map();
12620
12766
  const safeResolveImport = (specifier) => {
12621
12767
  try {