@isentinel/jest-roblox 0.3.0 → 0.3.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.
@@ -686,7 +686,7 @@ function C$4({ force: e } = {}) {
686
686
  var y$3 = C$4();
687
687
  //#endregion
688
688
  //#region package.json
689
- var version = "0.3.0";
689
+ var version = "0.3.1";
690
690
  //#endregion
691
691
  //#region src/config/errors.ts
692
692
  var ConfigError = class extends Error {
@@ -5332,6 +5332,11 @@ async function findWorkspaceDir(id = process.cwd(), options = {}) {
5332
5332
  throw new Error(`Cannot detect workspace root from ${id}`);
5333
5333
  }
5334
5334
  //#endregion
5335
+ //#region \0sea-stub:giget
5336
+ var require__sea_stub_giget = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5337
+ module.exports = {};
5338
+ }));
5339
+ //#endregion
5335
5340
  //#region node_modules/.pnpm/c12@4.0.0-beta.5_jiti@2.7.0_magicast@0.5.3/node_modules/c12/dist/_chunks/libs/ohash.mjs
5336
5341
  var ohash_exports = /* @__PURE__ */ __exportAll$1({
5337
5342
  n: () => dist_exports,
@@ -11261,7 +11266,7 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
11261
11266
  const customProviderKeys = Object.keys(sourceOptions.giget?.providers || {}).map((key) => `${key}:`);
11262
11267
  const gigetPrefixes = customProviderKeys.length > 0 ? [...new Set([...customProviderKeys, ...GIGET_PREFIXES])] : GIGET_PREFIXES;
11263
11268
  if (options.giget !== false && gigetPrefixes.some((prefix) => source.startsWith(prefix))) {
11264
- const { downloadTemplate } = await import("giget").catch((error) => {
11269
+ const { downloadTemplate } = await Promise.resolve().then(() => /* @__PURE__ */ __toESM(require__sea_stub_giget(), 1)).catch((error) => {
11265
11270
  throw new Error(`Extending config from \`${source}\` requires \`giget\` peer dependency to be installed.\n\nInstall it with: \`npx nypm i giget\``, { cause: error });
11266
11271
  });
11267
11272
  const { digest } = await Promise.resolve().then(() => (init_ohash(), ohash_exports)).then((n) => n.n);
@@ -24412,6 +24417,7 @@ function convertToLuau(filePath) {
24412
24417
  const RbxPathParent = Symbol("Parent");
24413
24418
  var RojoResolver = class RojoResolver {
24414
24419
  rbxPath = new Array();
24420
+ realpathCache = /* @__PURE__ */ new Map();
24415
24421
  walkedConfigFilesInternal = /* @__PURE__ */ new Set();
24416
24422
  walkedDirectoriesInternal = /* @__PURE__ */ new Set();
24417
24423
  filePathToRbxPathMap = /* @__PURE__ */ new Map();
@@ -24422,12 +24428,12 @@ var RojoResolver = class RojoResolver {
24422
24428
  static findRojoConfigFilePath(projectPath) {
24423
24429
  const warnings = new Array();
24424
24430
  const defaultPath = node_path.default.join(projectPath, ROJO_DEFAULT_NAME);
24425
- if ((0, node_fs.existsSync)(defaultPath)) return {
24431
+ if (node_fs.existsSync(defaultPath)) return {
24426
24432
  path: defaultPath,
24427
24433
  warnings
24428
24434
  };
24429
24435
  const candidates = new Array();
24430
- for (const fileName of (0, node_fs.readdirSync)(projectPath)) if (fileName !== ROJO_DEFAULT_NAME && (fileName === ROJO_OLD_NAME || ROJO_FILE_REGEX.test(fileName))) candidates.push(node_path.default.join(projectPath, fileName));
24436
+ for (const fileName of node_fs.readdirSync(projectPath)) if (fileName !== ROJO_DEFAULT_NAME && (fileName === ROJO_OLD_NAME || ROJO_FILE_REGEX.test(fileName))) candidates.push(node_path.default.join(projectPath, fileName));
24431
24437
  if (candidates.length > 1) warnings.push(`Multiple *.project.json files found, using ${candidates[0]}`);
24432
24438
  return {
24433
24439
  path: candidates[0],
@@ -24563,34 +24569,42 @@ var RojoResolver = class RojoResolver {
24563
24569
  get walkedDirectories() {
24564
24570
  return this.walkedDirectoriesInternal;
24565
24571
  }
24572
+ cachedRealpath(targetPath) {
24573
+ let resolved = this.realpathCache.get(targetPath);
24574
+ if (resolved === void 0) {
24575
+ resolved = node_fs.realpathSync(targetPath);
24576
+ this.realpathCache.set(targetPath, resolved);
24577
+ }
24578
+ return resolved;
24579
+ }
24566
24580
  getContainer(from, rbxPath) {
24567
24581
  if (this.isGame && rbxPath) {
24568
24582
  for (const container of from) if (arrayStartsWith(rbxPath, container)) return container;
24569
24583
  }
24570
24584
  }
24571
24585
  parseConfig(rojoConfigFilePath, doNotPush = false) {
24572
- if (!(0, node_fs.existsSync)(rojoConfigFilePath)) {
24586
+ if (!node_fs.existsSync(rojoConfigFilePath)) {
24573
24587
  this.warn(`RojoResolver: Path does not exist "${rojoConfigFilePath}"`);
24574
24588
  return;
24575
24589
  }
24576
- const realPath = (0, node_fs.realpathSync)(rojoConfigFilePath);
24590
+ const realPath = this.cachedRealpath(rojoConfigFilePath);
24577
24591
  this.walkedConfigFilesInternal.add(realPath);
24578
24592
  let configJson;
24579
24593
  try {
24580
- configJson = JSON.parse((0, node_fs.readFileSync)(realPath, "utf8"));
24594
+ configJson = JSON.parse(node_fs.readFileSync(realPath, "utf8"));
24581
24595
  } catch {}
24582
24596
  if (isValidRojoConfig(configJson)) this.parseTree(node_path.default.dirname(rojoConfigFilePath), configJson.name, configJson.tree, doNotPush);
24583
24597
  else this.warn("RojoResolver: Invalid configuration!");
24584
24598
  }
24585
24599
  parsePath(itemPath) {
24586
24600
  const luauPath = convertToLuau(itemPath);
24587
- const realPath = (0, node_fs.existsSync)(luauPath) ? (0, node_fs.realpathSync)(luauPath) : luauPath;
24601
+ const realPath = node_fs.existsSync(luauPath) ? this.cachedRealpath(luauPath) : luauPath;
24588
24602
  const extension = node_path.default.extname(luauPath);
24589
24603
  if (ROJO_MODULE_EXTS.has(extension)) this.filePathToRbxPathMap.set(luauPath, [...this.rbxPath]);
24590
24604
  else {
24591
- const isDirectory = (0, node_fs.existsSync)(realPath) && (0, node_fs.statSync)(realPath).isDirectory();
24605
+ const isDirectory = node_fs.existsSync(realPath) && node_fs.statSync(realPath).isDirectory();
24592
24606
  if (isDirectory) this.walkedDirectoriesInternal.add(realPath);
24593
- if (isDirectory && (0, node_fs.readdirSync)(realPath).includes(ROJO_DEFAULT_NAME)) this.parseConfig(node_path.default.join(luauPath, ROJO_DEFAULT_NAME), true);
24607
+ if (isDirectory && node_fs.readdirSync(realPath).includes(ROJO_DEFAULT_NAME)) this.parseConfig(node_path.default.join(luauPath, ROJO_DEFAULT_NAME), true);
24594
24608
  else {
24595
24609
  this.partitions.unshift({
24596
24610
  fsPath: luauPath,
@@ -24607,26 +24621,40 @@ var RojoResolver = class RojoResolver {
24607
24621
  for (const childName of Object.keys(tree).filter((value) => !value.startsWith("$"))) this.parseTree(basePath, childName, tree[childName]);
24608
24622
  if (!doNotPush) this.rbxPath.pop();
24609
24623
  }
24610
- searchChildren(directory, children) {
24611
- for (const child of children) {
24612
- const childPath = node_path.default.join(directory, child);
24613
- if ((0, node_fs.statSync)((0, node_fs.realpathSync)(childPath)).isFile() && child !== ROJO_DEFAULT_NAME && ROJO_FILE_REGEX.test(child)) this.parseConfig(childPath);
24614
- }
24615
- for (const child of children) {
24616
- const childPath = node_path.default.join(directory, child);
24617
- if ((0, node_fs.statSync)((0, node_fs.realpathSync)(childPath)).isDirectory()) this.searchDirectory(childPath, child);
24624
+ searchChildren(directory, directoryEntries) {
24625
+ const projectFiles = new Array();
24626
+ const subDirectories = new Array();
24627
+ for (const entry of directoryEntries) {
24628
+ const childPath = node_path.default.join(directory, entry.name);
24629
+ let isFile = entry.isFile();
24630
+ let isDirectory = entry.isDirectory();
24631
+ if (!isFile && !isDirectory) try {
24632
+ const stat = node_fs.statSync(this.cachedRealpath(childPath));
24633
+ isFile = stat.isFile();
24634
+ isDirectory = stat.isDirectory();
24635
+ } catch (err) {
24636
+ this.warn(`RojoResolver: Failed to resolve "${childPath}" (${err.message})`);
24637
+ continue;
24638
+ }
24639
+ if (isFile && ROJO_FILE_REGEX.test(entry.name)) projectFiles.push(childPath);
24640
+ else if (isDirectory) subDirectories.push({
24641
+ name: entry.name,
24642
+ path: childPath
24643
+ });
24618
24644
  }
24645
+ for (const childPath of projectFiles) this.parseConfig(childPath);
24646
+ for (const { name, path: childPath } of subDirectories) this.searchDirectory(childPath, name);
24619
24647
  }
24620
24648
  searchDirectory(directory, item) {
24621
- const realPath = (0, node_fs.realpathSync)(directory);
24649
+ const realPath = this.cachedRealpath(directory);
24622
24650
  this.walkedDirectoriesInternal.add(realPath);
24623
- const children = (0, node_fs.readdirSync)(realPath);
24624
- if (children.includes(ROJO_DEFAULT_NAME)) {
24651
+ const directoryEntries = node_fs.readdirSync(directory, { withFileTypes: true });
24652
+ if (directoryEntries.some((entry) => entry.name === ROJO_DEFAULT_NAME)) {
24625
24653
  this.parseConfig(node_path.default.join(directory, ROJO_DEFAULT_NAME));
24626
24654
  return;
24627
24655
  }
24628
24656
  if (item !== void 0) this.rbxPath.push(item);
24629
- this.searchChildren(directory, children);
24657
+ this.searchChildren(directory, directoryEntries);
24630
24658
  if (item !== void 0) this.rbxPath.pop();
24631
24659
  }
24632
24660
  warn(str) {
@@ -30020,7 +30048,7 @@ function createSnapshotPathResolver(config) {
30020
30048
  const result = `${basePath}/${normalized.slice(prefix.length + 1)}`;
30021
30049
  const mapping = findMapping(result, tsconfigMappings);
30022
30050
  if (mapping !== void 0) return {
30023
- filePath: replacePrefix(result, mapping.outDir, mapping.rootDir).replace(/^\.\//, ""),
30051
+ filePath: replacePrefix(result, mapping.outDir, mapping.rootDir),
30024
30052
  mapping
30025
30053
  };
30026
30054
  return { filePath: result };
@@ -30040,6 +30068,96 @@ function buildMappings(tree, prefix) {
30040
30068
  return mappings;
30041
30069
  }
30042
30070
  //#endregion
30071
+ //#region src/timing/orchestration-collector.ts
30072
+ /**
30073
+ * A buffered span-tree profiler for a single, sequential host run. Nesting is
30074
+ * tracked with one shared stack, so spans must open and close in LIFO order:
30075
+ * profile a phase, and any spans it opens nest under it. It is NOT safe to run
30076
+ * two `profile` / `profileAsync` calls concurrently on the same collector (e.g.
30077
+ * `Promise.all`) — interleaved opens/closes would corrupt the stack. Create one
30078
+ * collector per run; `flushTimingReport` empties it so a second flush is a
30079
+ * no-op.
30080
+ */
30081
+ function createTimingCollector(options = {}) {
30082
+ const clock = options.clock ?? { now: () => node_perf_hooks.performance.now() };
30083
+ const sink = options.sink ?? ((line) => void node_process.default.stderr.write(`${line}\n`));
30084
+ const enabled = options.enabled ?? node_process.default.env["TIMING"] !== void 0;
30085
+ const roots = /* @__PURE__ */ new Map();
30086
+ const stack = [];
30087
+ function open(name) {
30088
+ const top = stack.at(-1);
30089
+ const node = childOf(top === void 0 ? roots : top.children, name);
30090
+ stack.push(node);
30091
+ const start = clock.now();
30092
+ return () => {
30093
+ node.elapsedMs += clock.now() - start;
30094
+ stack.pop();
30095
+ };
30096
+ }
30097
+ function profile(name, func) {
30098
+ if (!enabled) return func();
30099
+ const close = open(name);
30100
+ try {
30101
+ return func();
30102
+ } finally {
30103
+ close();
30104
+ }
30105
+ }
30106
+ async function profileAsync(name, func) {
30107
+ if (!enabled) return func();
30108
+ const close = open(name);
30109
+ try {
30110
+ return await func();
30111
+ } finally {
30112
+ close();
30113
+ }
30114
+ }
30115
+ function record(name, elapsedMs) {
30116
+ if (!enabled) return;
30117
+ const top = stack.at(-1);
30118
+ const node = childOf(top === void 0 ? roots : top.children, name);
30119
+ node.elapsedMs += elapsedMs;
30120
+ }
30121
+ function emit(node, depth) {
30122
+ sink(`[TIMING] ${" ".repeat(depth)}${node.name}: ${String(Math.round(node.elapsedMs))}ms`);
30123
+ for (const child of node.children.values()) emit(child, depth + 1);
30124
+ }
30125
+ function flushTimingReport() {
30126
+ if (!enabled || roots.size === 0) return;
30127
+ let total = 0;
30128
+ for (const node of roots.values()) {
30129
+ emit(node, 0);
30130
+ total += Math.round(node.elapsedMs);
30131
+ }
30132
+ sink(`[TIMING] TOTAL (host): ${String(total)}ms`);
30133
+ roots.clear();
30134
+ }
30135
+ return {
30136
+ flushTimingReport,
30137
+ profile,
30138
+ profileAsync,
30139
+ record
30140
+ };
30141
+ }
30142
+ /**
30143
+ * Shared disabled collector for callers that thread a profiler through their
30144
+ * signatures but are invoked outside a profiled workspace run (single-mode
30145
+ * coverage, the `instrument` subcommand, tests). Every method is a no-op.
30146
+ */
30147
+ const NOOP_TIMING_COLLECTOR = createTimingCollector({ enabled: false });
30148
+ function childOf(parent, name) {
30149
+ let node = parent.get(name);
30150
+ if (node === void 0) {
30151
+ node = {
30152
+ name,
30153
+ children: /* @__PURE__ */ new Map(),
30154
+ elapsedMs: 0
30155
+ };
30156
+ parent.set(name, node);
30157
+ }
30158
+ return node;
30159
+ }
30160
+ //#endregion
30043
30161
  //#region src/types/rojo.ts
30044
30162
  const rojoProjectSchema = type({
30045
30163
  "name": "string",
@@ -30189,38 +30307,48 @@ function formatExecuteOutput(options) {
30189
30307
  * config.
30190
30308
  */
30191
30309
  async function runProjects(options) {
30192
- const jobs = options.projects.map((project) => buildProjectJob(project));
30193
- const { rawResults, timing: backendTiming } = await options.backend.runTests({
30194
- jobs,
30195
- parallel: options.parallel,
30196
- scriptOverride: options.scriptOverride,
30197
- streaming: options.streaming,
30198
- workStealing: options.workStealing
30310
+ const timing = options.timing ?? NOOP_TIMING_COLLECTOR;
30311
+ const jobs = timing.profile("buildJobs", () => {
30312
+ return options.projects.map((project) => buildProjectJob(project, timing));
30313
+ });
30314
+ const { rawResults, timing: backendTiming } = await timing.profileAsync("backend.runTests", async () => {
30315
+ const result = await options.backend.runTests({
30316
+ jobs,
30317
+ parallel: options.parallel,
30318
+ scriptOverride: options.scriptOverride,
30319
+ streaming: options.streaming,
30320
+ workStealing: options.workStealing
30321
+ });
30322
+ recordBackendTimingSpans(timing, result.timing);
30323
+ return result;
30199
30324
  });
30200
30325
  if (rawResults.length !== jobs.length) throw new Error(`Backend returned ${rawResults.length.toString()} results for ${jobs.length.toString()} jobs — rawResults must be parallel to jobs`);
30201
30326
  return {
30202
30327
  backendTiming,
30203
- results: rawResults.map((raw, index) => {
30204
- const job = jobs[index];
30205
- try {
30206
- return processProjectResult(buildProjectResult(raw.entry, job, raw.fallbackGameOutput), {
30207
- backendTiming,
30208
- config: job.config,
30209
- deferFormatting: options.deferFormatting,
30210
- startTime: options.startTime,
30211
- version: options.version
30212
- });
30213
- } catch (err) {
30214
- if (!(err instanceof LuauScriptError)) throw err;
30215
- return buildExecutionErrorResult({
30216
- backendTiming,
30217
- config: job.config,
30218
- deferFormatting: options.deferFormatting,
30219
- error: err,
30220
- startTime: options.startTime,
30221
- version: options.version
30222
- });
30223
- }
30328
+ results: timing.profile("processResults", () => {
30329
+ return rawResults.map((raw, index) => {
30330
+ const job = jobs[index];
30331
+ try {
30332
+ return processProjectResult(buildProjectResult(raw.entry, job, raw.fallbackGameOutput), {
30333
+ backendTiming,
30334
+ config: job.config,
30335
+ deferFormatting: options.deferFormatting,
30336
+ startTime: options.startTime,
30337
+ timing,
30338
+ version: options.version
30339
+ });
30340
+ } catch (err) {
30341
+ if (!(err instanceof LuauScriptError)) throw err;
30342
+ return buildExecutionErrorResult({
30343
+ backendTiming,
30344
+ config: job.config,
30345
+ deferFormatting: options.deferFormatting,
30346
+ error: err,
30347
+ startTime: options.startTime,
30348
+ version: options.version
30349
+ });
30350
+ }
30351
+ });
30224
30352
  })
30225
30353
  };
30226
30354
  }
@@ -30261,6 +30389,10 @@ function parseTsconfigMappings(options) {
30261
30389
  rootDir: normalizeDirectoryPath(options.rootDir ?? "src")
30262
30390
  }];
30263
30391
  }
30392
+ function recordBackendTimingSpans(timing, backendTiming) {
30393
+ if (backendTiming.uploadMs !== void 0) timing.record("uploadMs", backendTiming.uploadMs);
30394
+ timing.record("executionMs", backendTiming.executionMs);
30395
+ }
30264
30396
  const EXIT_CODE_MESSAGE$1 = /^Exited with code: \d+$/;
30265
30397
  /**
30266
30398
  * Compose the human-readable failure message for an exec-error file
@@ -30461,7 +30593,7 @@ function writeSnapshots(snapshotWrites, config, tsconfigMappings) {
30461
30593
  node_fs.writeFileSync(absolutePath, content);
30462
30594
  const { filePath, mapping } = resolved;
30463
30595
  if (mapping !== void 0) {
30464
- const outPath = mapping.outDir + filePath.slice(mapping.rootDir.length);
30596
+ const outPath = replacePrefix(filePath, mapping.rootDir, mapping.outDir);
30465
30597
  const absoluteOutPath = node_path.resolve(config.rootDir, outPath);
30466
30598
  node_fs.mkdirSync(node_path.dirname(absoluteOutPath), { recursive: true });
30467
30599
  node_fs.writeFileSync(absoluteOutPath, content);
@@ -30490,19 +30622,23 @@ function writeSnapshots(snapshotWrites, config, tsconfigMappings) {
30490
30622
  * formatter output. Called once per job.
30491
30623
  */
30492
30624
  function processProjectResult(entry, options) {
30493
- const { backendTiming, config, deferFormatting, startTime, version } = options;
30625
+ const { backendTiming, config, deferFormatting, startTime, timing, version } = options;
30494
30626
  const { coverageData, gameOutput, luauTiming, result, setupMs, snapshotWrites } = entry;
30495
- const tsconfigMappings = resolveAllTsconfigMappings(config.rootDir);
30496
- const writeCounts = snapshotWrites !== void 0 ? writeSnapshots(snapshotWrites, config, tsconfigMappings) : {
30627
+ const tsconfigMappings = timing.profile("resolveTsconfigMappings", () => {
30628
+ return resolveAllTsconfigMappings(config.rootDir);
30629
+ });
30630
+ const writeCounts = snapshotWrites !== void 0 ? timing.profile("writeSnapshots", () => {
30631
+ return writeSnapshots(snapshotWrites, config, tsconfigMappings);
30632
+ }) : {
30497
30633
  attempted: 0,
30498
30634
  failed: 0,
30499
30635
  written: 0
30500
30636
  };
30501
30637
  const testsMs = calculateTestsMs(result.testResults);
30502
- const sourceMapper = config.sourceMap ? buildSourceMapper(config, tsconfigMappings) : void 0;
30638
+ const sourceMapper = config.sourceMap ? timing.profile("buildSourceMapper", () => buildSourceMapper(config, tsconfigMappings)) : void 0;
30503
30639
  resolveTestFilePaths(result, sourceMapper);
30504
30640
  const totalMs = Date.now() - startTime;
30505
- const timing = {
30641
+ const resultTiming = {
30506
30642
  executionMs: backendTiming.executionMs,
30507
30643
  setupMs,
30508
30644
  startTime,
@@ -30515,7 +30651,7 @@ function processProjectResult(entry, options) {
30515
30651
  result,
30516
30652
  snapshotWriteFailures: writeCounts.failed,
30517
30653
  sourceMapper,
30518
- timing,
30654
+ timing: resultTiming,
30519
30655
  version
30520
30656
  }) : "";
30521
30657
  if (luauTiming !== void 0) printLuauTiming(luauTiming);
@@ -30527,7 +30663,7 @@ function processProjectResult(entry, options) {
30527
30663
  result,
30528
30664
  snapshotWriteFailures: writeCounts.failed > 0 ? writeCounts.failed : void 0,
30529
30665
  sourceMapper,
30530
- timing
30666
+ timing: resultTiming
30531
30667
  };
30532
30668
  }
30533
30669
  /**
@@ -30535,8 +30671,10 @@ function processProjectResult(entry, options) {
30535
30671
  * carries its own config so the Luau runner never re-resolves or shares format
30536
30672
  * state across projects (fixes the spike's snapshot-diff regression — C1).
30537
30673
  */
30538
- function buildProjectJob(parameters) {
30539
- const tsconfigMappings = resolveAllTsconfigMappings(parameters.config.rootDir);
30674
+ function buildProjectJob(parameters, timing) {
30675
+ const tsconfigMappings = timing.profile("resolveTsconfigMappings", () => {
30676
+ return resolveAllTsconfigMappings(parameters.config.rootDir);
30677
+ });
30540
30678
  const luauProject = isLuauProject(parameters.testFiles, tsconfigMappings);
30541
30679
  return {
30542
30680
  config: applySnapshotFormatDefaults(parameters.config, luauProject),
@@ -38915,89 +39053,6 @@ function buildWithRojo(projectPath, outputPath) {
38915
39053
  }
38916
39054
  }
38917
39055
  //#endregion
38918
- //#region src/timing/orchestration-collector.ts
38919
- /**
38920
- * A buffered span-tree profiler for a single, sequential host run. Nesting is
38921
- * tracked with one shared stack, so spans must open and close in LIFO order:
38922
- * profile a phase, and any spans it opens nest under it. It is NOT safe to run
38923
- * two `profile` / `profileAsync` calls concurrently on the same collector (e.g.
38924
- * `Promise.all`) — interleaved opens/closes would corrupt the stack. Create one
38925
- * collector per run; `flushTimingReport` empties it so a second flush is a
38926
- * no-op.
38927
- */
38928
- function createTimingCollector(options = {}) {
38929
- const clock = options.clock ?? { now: () => node_perf_hooks.performance.now() };
38930
- const sink = options.sink ?? ((line) => void node_process.default.stderr.write(`${line}\n`));
38931
- const enabled = options.enabled ?? node_process.default.env["TIMING"] !== void 0;
38932
- const roots = /* @__PURE__ */ new Map();
38933
- const stack = [];
38934
- function open(name) {
38935
- const top = stack.at(-1);
38936
- const node = childOf(top === void 0 ? roots : top.children, name);
38937
- stack.push(node);
38938
- const start = clock.now();
38939
- return () => {
38940
- node.elapsedMs += clock.now() - start;
38941
- stack.pop();
38942
- };
38943
- }
38944
- function profile(name, func) {
38945
- if (!enabled) return func();
38946
- const close = open(name);
38947
- try {
38948
- return func();
38949
- } finally {
38950
- close();
38951
- }
38952
- }
38953
- async function profileAsync(name, func) {
38954
- if (!enabled) return func();
38955
- const close = open(name);
38956
- try {
38957
- return await func();
38958
- } finally {
38959
- close();
38960
- }
38961
- }
38962
- function emit(node, depth) {
38963
- sink(`[TIMING] ${" ".repeat(depth)}${node.name}: ${String(Math.round(node.elapsedMs))}ms`);
38964
- for (const child of node.children.values()) emit(child, depth + 1);
38965
- }
38966
- function flushTimingReport() {
38967
- if (!enabled || roots.size === 0) return;
38968
- let total = 0;
38969
- for (const node of roots.values()) {
38970
- emit(node, 0);
38971
- total += Math.round(node.elapsedMs);
38972
- }
38973
- sink(`[TIMING] TOTAL (host): ${String(total)}ms`);
38974
- roots.clear();
38975
- }
38976
- return {
38977
- flushTimingReport,
38978
- profile,
38979
- profileAsync
38980
- };
38981
- }
38982
- /**
38983
- * Shared disabled collector for callers that thread a profiler through their
38984
- * signatures but are invoked outside a profiled workspace run (single-mode
38985
- * coverage, the `instrument` subcommand, tests). Every method is a no-op.
38986
- */
38987
- const NOOP_TIMING_COLLECTOR = createTimingCollector({ enabled: false });
38988
- function childOf(parent, name) {
38989
- let node = parent.get(name);
38990
- if (node === void 0) {
38991
- node = {
38992
- name,
38993
- children: /* @__PURE__ */ new Map(),
38994
- elapsedMs: 0
38995
- };
38996
- parent.set(name, node);
38997
- }
38998
- return node;
38999
- }
39000
- //#endregion
39001
39056
  //#region src/utils/hash.ts
39002
39057
  function hashBuffer(data) {
39003
39058
  return (0, node_crypto.createHash)("sha256").update(data).digest("hex");
@@ -40472,15 +40527,26 @@ function classifyTestFiles(files, config) {
40472
40527
  typeTestFiles
40473
40528
  };
40474
40529
  }
40530
+ function resolveAllSetupFilePaths(configs) {
40531
+ const resolvers = /* @__PURE__ */ new Map();
40532
+ for (const config of configs) {
40533
+ if (config.setupFiles === void 0 && config.setupFilesAfterEnv === void 0) continue;
40534
+ const rojoConfigPath = node_path.resolve(config.rootDir, config.rojoProject ?? DEFAULT_ROJO_PROJECT$1);
40535
+ const key = JSON.stringify([config.rootDir, rojoConfigPath]);
40536
+ let resolve = resolvers.get(key);
40537
+ if (resolve === void 0) {
40538
+ resolve = createSetupResolver({
40539
+ configDirectory: config.rootDir,
40540
+ rojoConfigPath
40541
+ });
40542
+ resolvers.set(key, resolve);
40543
+ }
40544
+ if (config.setupFiles !== void 0) config.setupFiles = config.setupFiles.map(resolve);
40545
+ if (config.setupFilesAfterEnv !== void 0) config.setupFilesAfterEnv = config.setupFilesAfterEnv.map(resolve);
40546
+ }
40547
+ }
40475
40548
  function resolveSetupFilePaths(config) {
40476
- if (config.setupFiles === void 0 && config.setupFilesAfterEnv === void 0) return;
40477
- const rojoConfigPath = node_path.resolve(config.rootDir, config.rojoProject ?? DEFAULT_ROJO_PROJECT$1);
40478
- const resolve = createSetupResolver({
40479
- configDirectory: config.rootDir,
40480
- rojoConfigPath
40481
- });
40482
- if (config.setupFiles !== void 0) config.setupFiles = config.setupFiles.map(resolve);
40483
- if (config.setupFilesAfterEnv !== void 0) config.setupFilesAfterEnv = config.setupFilesAfterEnv.map(resolve);
40549
+ resolveAllSetupFilePaths([config]);
40484
40550
  }
40485
40551
  //#endregion
40486
40552
  //#region src/run/multi.ts
@@ -40488,30 +40554,52 @@ const DEFAULT_ROJO_PROJECT = "default.project.json";
40488
40554
  const VERSION$3 = version;
40489
40555
  async function runMultiProject(options) {
40490
40556
  const { cli, config: rootConfig, rawProjects } = options;
40491
- const allProjects = await resolveAllProjects(rawProjects, rootConfig, loadRojoTree(rootConfig), rootConfig.rootDir);
40492
- for (const project of allProjects) resolveSetupFilePaths(project.config);
40493
- const { filesByProject, projects } = selectProjects(allProjects, cli.project, cli.files, rootConfig.rootDir);
40557
+ const timing = options.timing ?? NOOP_TIMING_COLLECTOR;
40558
+ const rojoTree = timing.profile("loadRojoTree", () => loadRojoTree(rootConfig));
40559
+ const allProjects = await timing.profileAsync("resolveAllProjects", async () => {
40560
+ return resolveAllProjects(rawProjects, rootConfig, rojoTree, rootConfig.rootDir);
40561
+ });
40562
+ timing.profile("resolveSetupFilePaths", () => {
40563
+ resolveAllSetupFilePaths(allProjects.map((project) => project.config));
40564
+ });
40565
+ const { filesByProject, projects } = timing.profile("selectProjects", () => {
40566
+ return selectProjects(allProjects, cli.project, cli.files, rootConfig.rootDir);
40567
+ });
40494
40568
  const cacheRoot = node_path.resolve(rootConfig.rootDir, ".jest-roblox", "cache");
40495
- const cleaned = cleanLeftoverStubs(projects, rootConfig.rootDir);
40569
+ const cleaned = timing.profile("cleanLeftoverStubs", () => {
40570
+ return cleanLeftoverStubs(projects, rootConfig.rootDir);
40571
+ });
40496
40572
  if (cleaned.length > 0) node_process.default.stderr.write(`jest-roblox: cleaned ${String(cleaned.length)} leftover stub(s):\n${cleaned.map((stubPath) => ` ${stubPath}\n`).join("")}`);
40497
- generateProjectStubs(projects, rootConfig.rootDir, cacheRoot);
40498
- const { effectiveConfig, preCoverageMs } = prepareMultiProjectCoverage(rootConfig, projects, cacheRoot);
40499
- const backend = await resolveBackend(cli, effectiveConfig);
40573
+ timing.profile("generateProjectStubs", () => {
40574
+ generateProjectStubs(projects, rootConfig.rootDir, cacheRoot);
40575
+ });
40576
+ const { effectiveConfig, preCoverageMs } = timing.profile("prepareCoverage", () => {
40577
+ return prepareMultiProjectCoverage(rootConfig, projects, cacheRoot);
40578
+ });
40579
+ const backend = await timing.profileAsync("resolveBackend", async () => {
40580
+ return resolveBackend(cli, effectiveConfig);
40581
+ });
40500
40582
  const parallel = effectiveParallelForBackend(effectiveConfig.parallel, backend);
40501
- if (!rootConfig.collectCoverage && backend.kind === "open-cloud") buildOpenCloudPlace(rootConfig, projects, cacheRoot);
40502
- const { allTypeTestFiles, pendingJobs } = collectPendingJobs({
40503
- cliFiles: cli.files,
40504
- effectivePlaceFile: effectiveConfig.placeFile,
40505
- filesByProject,
40506
- projects,
40507
- rootConfig
40583
+ if (!rootConfig.collectCoverage && backend.kind === "open-cloud") timing.profile("buildOpenCloudPlace", () => {
40584
+ buildOpenCloudPlace(rootConfig, projects, cacheRoot);
40508
40585
  });
40509
- const projectResults = await runJobs(backend, pendingJobs, parallel);
40586
+ const { allTypeTestFiles, pendingJobs } = timing.profile("collectPendingJobs", () => {
40587
+ return collectPendingJobs({
40588
+ cliFiles: cli.files,
40589
+ effectivePlaceFile: effectiveConfig.placeFile,
40590
+ filesByProject,
40591
+ projects,
40592
+ rootConfig
40593
+ });
40594
+ });
40595
+ const projectResults = await runJobs(backend, pendingJobs, parallel, timing);
40510
40596
  const uniqueTypeTestFiles = [...new Set(allTypeTestFiles)];
40511
- const typecheckResult = uniqueTypeTestFiles.length > 0 ? runTypecheck({
40512
- files: uniqueTypeTestFiles,
40513
- rootDir: rootConfig.rootDir,
40514
- tsconfig: rootConfig.typecheckTsconfig
40597
+ const typecheckResult = uniqueTypeTestFiles.length > 0 ? timing.profile("runTypecheck", () => {
40598
+ return runTypecheck({
40599
+ files: uniqueTypeTestFiles,
40600
+ rootDir: rootConfig.rootDir,
40601
+ tsconfig: rootConfig.typecheckTsconfig
40602
+ });
40515
40603
  }) : void 0;
40516
40604
  if (projectResults.length === 0 && typecheckResult === void 0) {
40517
40605
  if (rootConfig.passWithNoTests) return {
@@ -40599,28 +40687,31 @@ function collectPendingJobs(arguments_) {
40599
40687
  pendingJobs
40600
40688
  };
40601
40689
  }
40602
- async function runJobs(backend, pendingJobs, parallel) {
40690
+ async function runJobs(backend, pendingJobs, parallel, timing) {
40603
40691
  if (pendingJobs.length === 0) {
40604
40692
  await backend.close?.();
40605
40693
  return [];
40606
40694
  }
40607
40695
  let runResult;
40608
40696
  try {
40609
- runResult = await runProjects({
40610
- backend,
40611
- deferFormatting: true,
40612
- parallel,
40613
- projects: pendingJobs.map((pending) => {
40614
- return {
40615
- config: pending.config,
40616
- displayColor: pending.displayColor,
40617
- displayName: pending.displayName,
40618
- runtimeInjectionPaths: pending.runtimeInjectionPaths,
40619
- testFiles: pending.runtimeFiles
40620
- };
40621
- }),
40622
- startTime: Date.now(),
40623
- version: VERSION$3
40697
+ runResult = await timing.profileAsync("runProjects", async () => {
40698
+ return runProjects({
40699
+ backend,
40700
+ deferFormatting: true,
40701
+ parallel,
40702
+ projects: pendingJobs.map((pending) => {
40703
+ return {
40704
+ config: pending.config,
40705
+ displayColor: pending.displayColor,
40706
+ displayName: pending.displayName,
40707
+ runtimeInjectionPaths: pending.runtimeInjectionPaths,
40708
+ testFiles: pending.runtimeFiles
40709
+ };
40710
+ }),
40711
+ startTime: Date.now(),
40712
+ timing,
40713
+ version: VERSION$3
40714
+ });
40624
40715
  });
40625
40716
  } finally {
40626
40717
  await backend.close?.();
@@ -40697,9 +40788,14 @@ function selectProjects(allProjects, projectNames, cliFiles, rootDirectory) {
40697
40788
  const VERSION$2 = version;
40698
40789
  async function runSingleProject(options) {
40699
40790
  const { cli } = options;
40700
- const config = narrowConfigByFiles(options.config, cli.files ?? []);
40701
- resolveSetupFilePaths(config);
40702
- const discovery = discoverTestFiles(config, cli.files);
40791
+ const timing = options.timing ?? NOOP_TIMING_COLLECTOR;
40792
+ const config = timing.profile("narrowConfigByFiles", () => {
40793
+ return narrowConfigByFiles(options.config, cli.files ?? []);
40794
+ });
40795
+ timing.profile("resolveSetupFilePaths", () => {
40796
+ resolveSetupFilePaths(config);
40797
+ });
40798
+ const discovery = timing.profile("discoverTestFiles", () => discoverTestFiles(config, cli.files));
40703
40799
  if (discovery.files.length === 0) {
40704
40800
  if (config.passWithNoTests) return {
40705
40801
  mode: "single",
@@ -40712,7 +40808,9 @@ async function runSingleProject(options) {
40712
40808
  validationExitCode: 2
40713
40809
  };
40714
40810
  }
40715
- const { runtimeFiles, typeTestFiles } = classifyTestFiles(discovery.files, config);
40811
+ const { runtimeFiles, typeTestFiles } = timing.profile("classifyTestFiles", () => {
40812
+ return classifyTestFiles(discovery.files, config);
40813
+ });
40716
40814
  if (typeTestFiles.length === 0 && runtimeFiles.length === 0) {
40717
40815
  if (config.passWithNoTests) return {
40718
40816
  mode: "single",
@@ -40729,19 +40827,27 @@ async function runSingleProject(options) {
40729
40827
  let effectiveConfig = config;
40730
40828
  if (config.collectCoverage && !config.typecheckOnly && runtimeFiles.length > 0) {
40731
40829
  const preCoverageStart = Date.now();
40732
- const { placeFile } = prepareCoverage(config);
40830
+ const { placeFile } = timing.profile("prepareCoverage", () => prepareCoverage(config));
40733
40831
  preCoverageMs = Date.now() - preCoverageStart;
40734
40832
  effectiveConfig = {
40735
40833
  ...config,
40736
40834
  placeFile
40737
40835
  };
40738
40836
  }
40739
- const typecheckResult = typeTestFiles.length > 0 ? runTypecheck({
40740
- files: typeTestFiles,
40741
- rootDir: effectiveConfig.rootDir,
40742
- tsconfig: effectiveConfig.typecheckTsconfig
40837
+ const typecheckResult = typeTestFiles.length > 0 ? timing.profile("runTypecheck", () => {
40838
+ return runTypecheck({
40839
+ files: typeTestFiles,
40840
+ rootDir: effectiveConfig.rootDir,
40841
+ tsconfig: effectiveConfig.typecheckTsconfig
40842
+ });
40843
+ }) : void 0;
40844
+ const runtimeResult = runtimeFiles.length > 0 ? await executeRuntimeTests({
40845
+ cli,
40846
+ config: effectiveConfig,
40847
+ testFiles: runtimeFiles,
40848
+ timing,
40849
+ totalFiles: discovery.totalFiles
40743
40850
  }) : void 0;
40744
- const runtimeResult = runtimeFiles.length > 0 ? await executeRuntimeTests(options, effectiveConfig, runtimeFiles, discovery.totalFiles) : void 0;
40745
40851
  return {
40746
40852
  mode: "single",
40747
40853
  preCoverageMs,
@@ -40749,19 +40855,25 @@ async function runSingleProject(options) {
40749
40855
  typecheckResult
40750
40856
  };
40751
40857
  }
40752
- async function executeRuntimeTests(options, config, testFiles, totalFiles) {
40858
+ async function executeRuntimeTests(options) {
40859
+ const { cli, config, testFiles, timing, totalFiles } = options;
40753
40860
  if (!config.silent && !usesAgentFormatter(config.formatters, config.verbose) && !hasFormatter(config.formatters, "json") && testFiles.length !== totalFiles) node_process.default.stderr.write(`Running ${String(testFiles.length)} of ${String(totalFiles)} test files\n`);
40754
- const backend = await resolveBackend(options.cli, config);
40861
+ const backend = await timing.profileAsync("resolveBackend", async () => {
40862
+ return resolveBackend(cli, config);
40863
+ });
40755
40864
  try {
40756
- const { results } = await runProjects({
40757
- backend,
40758
- deferFormatting: true,
40759
- projects: [{
40760
- config,
40761
- testFiles
40762
- }],
40763
- startTime: Date.now(),
40764
- version: VERSION$2
40865
+ const { results } = await timing.profileAsync("runProjects", async () => {
40866
+ return runProjects({
40867
+ backend,
40868
+ deferFormatting: true,
40869
+ projects: [{
40870
+ config,
40871
+ testFiles
40872
+ }],
40873
+ startTime: Date.now(),
40874
+ timing,
40875
+ version: VERSION$2
40876
+ });
40765
40877
  });
40766
40878
  return results[0];
40767
40879
  } finally {
@@ -41497,12 +41609,7 @@ const SYNTHESIZED_PLACE_FILE = "synthesized.rbxl";
41497
41609
  const WORKSPACE_CACHE_DIRECTORY = node_path.join(".jest-roblox", "workspace");
41498
41610
  const ROJO_PROJECT_DEFAULT = "test.project.json";
41499
41611
  async function runWorkspace(options) {
41500
- const timing = createTimingCollector();
41501
- try {
41502
- return await runWorkspaceProfiled(options, timing);
41503
- } finally {
41504
- timing.flushTimingReport();
41505
- }
41612
+ return runWorkspaceProfiled(options, options.timing ?? NOOP_TIMING_COLLECTOR);
41506
41613
  }
41507
41614
  function buildCoverageMap(entries) {
41508
41615
  const map = /* @__PURE__ */ new Map();
@@ -41616,6 +41723,7 @@ async function runWorkspaceProfiled(options, timing) {
41616
41723
  };
41617
41724
  }),
41618
41725
  startTime,
41726
+ timing,
41619
41727
  version,
41620
41728
  ...dispatchSpec
41621
41729
  });
@@ -42215,7 +42323,7 @@ const EMPTY_RESULT = {
42215
42323
  preCoverageMs: 0,
42216
42324
  projectResults: []
42217
42325
  };
42218
- async function runWorkspaceMode(cli, workspace) {
42326
+ async function runWorkspaceMode(cli, workspace, timing) {
42219
42327
  const basicValidation = validateBasicWorkspaceFlags(cli);
42220
42328
  if (!basicValidation.ok) return {
42221
42329
  ...EMPTY_RESULT,
@@ -42288,6 +42396,7 @@ async function runWorkspaceMode(cli, workspace) {
42288
42396
  ...onStreamingResult !== void 0 ? { onStreamingResult } : {},
42289
42397
  packageInfos,
42290
42398
  runOptions,
42399
+ timing,
42291
42400
  version: VERSION$1,
42292
42401
  workspaceRoot,
42293
42402
  workStealingCredentials
@@ -42390,18 +42499,25 @@ function isWorkspaceInvocation(cli) {
42390
42499
  return cli.workspace === true || cli.packages !== void 0 || cli.affectedSince !== void 0;
42391
42500
  }
42392
42501
  async function runJestRoblox(cli, config) {
42393
- if (isWorkspaceInvocation(cli)) return runWorkspaceMode(cli, config.workspace);
42394
- const merged = mergeCliWithConfig(cli, config);
42395
- const rawProjects = merged.projects;
42396
- if (rawProjects !== void 0 && rawProjects.length > 0) return runMultiProject({
42397
- cli,
42398
- config: merged,
42399
- rawProjects
42400
- });
42401
- return runSingleProject({
42402
- cli,
42403
- config: merged
42404
- });
42502
+ const timing = createTimingCollector();
42503
+ try {
42504
+ if (isWorkspaceInvocation(cli)) return await runWorkspaceMode(cli, config.workspace, timing);
42505
+ const merged = mergeCliWithConfig(cli, config);
42506
+ const rawProjects = merged.projects;
42507
+ if (rawProjects !== void 0 && rawProjects.length > 0) return await runMultiProject({
42508
+ cli,
42509
+ config: merged,
42510
+ rawProjects,
42511
+ timing
42512
+ });
42513
+ return await runSingleProject({
42514
+ cli,
42515
+ config: merged,
42516
+ timing
42517
+ });
42518
+ } finally {
42519
+ timing.flushTimingReport();
42520
+ }
42405
42521
  }
42406
42522
  //#endregion
42407
42523
  //#region src/utils/error-chain.ts