@isentinel/jest-roblox 0.1.1 → 0.1.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.
package/dist/cli.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import { D as createOpenCloudBackend, I as isValidBackend, L as LuauScriptError, M as ROOT_ONLY_KEYS, N as VALID_BACKENDS, O as hashBuffer, S as loadConfig$1, T as createStudioBackend, _ as formatResult, a as formatAnnotations, b as formatBanner, c as execute, d as findFormatterOptions, g as formatMultiProjectResult, i as runTypecheck, l as formatExecuteOutput, m as formatCompactMultiProject, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, s as resolveGitHubActionsOptions, t as formatGameOutputNotice, u as loadCoverageManifest, x as rojoProjectSchema, y as formatTypecheckSummary } from "./game-output-C0_-YIAY.mjs";
1
+ import { D as createOpenCloudBackend, I as isValidBackend, L as LuauScriptError, M as ROOT_ONLY_KEYS, N as VALID_BACKENDS, O as hashBuffer, S as loadConfig$1, T as createStudioBackend, _ as formatResult, a as formatAnnotations, b as formatBanner, c as execute, d as findFormatterOptions, g as formatMultiProjectResult, i as runTypecheck, l as formatExecuteOutput, m as formatCompactMultiProject, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, s as resolveGitHubActionsOptions, t as formatGameOutputNotice, u as loadCoverageManifest, x as rojoProjectSchema, y as formatTypecheckSummary } from "./game-output-BMGxhjkE.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { type } from "arktype";
4
4
  import assert from "node:assert";
5
5
  import * as fs$1 from "node:fs";
6
- import fs from "node:fs";
6
+ import fs, { readFileSync } from "node:fs";
7
7
  import * as path$1 from "node:path";
8
- import path from "node:path";
8
+ import path, { dirname, join } from "node:path";
9
9
  import process from "node:process";
10
10
  import { parseArgs as parseArgs$1 } from "node:util";
11
11
  import { isAgent } from "std-env";
@@ -23,7 +23,7 @@ import istanbulCoverage from "istanbul-lib-coverage";
23
23
  import istanbulReport from "istanbul-lib-report";
24
24
  import istanbulReports from "istanbul-reports";
25
25
  //#region package.json
26
- var version = "0.1.1";
26
+ var version = "0.1.2";
27
27
  //#endregion
28
28
  //#region src/backends/auto.ts
29
29
  var StudioWithFallback = class {
@@ -116,10 +116,39 @@ function stripTsExtension(pattern) {
116
116
  }
117
117
  //#endregion
118
118
  //#region src/utils/rojo-tree.ts
119
+ function resolveNestedProjects(tree, rootDirectory) {
120
+ return resolveTree(tree, rootDirectory, /* @__PURE__ */ new Set());
121
+ }
119
122
  function collectPaths(node, result) {
120
123
  for (const [key, value] of Object.entries(node)) if (key === "$path" && typeof value === "string") result.push(value.replaceAll("\\", "/"));
121
124
  else if (typeof value === "object" && !Array.isArray(value) && !key.startsWith("$")) collectPaths(value, result);
122
125
  }
126
+ function resolveTree(node, rootDirectory, visited) {
127
+ const resolved = {};
128
+ for (const [key, value] of Object.entries(node)) {
129
+ if (key === "$path" && typeof value === "string" && value.endsWith(".project.json")) {
130
+ const projectPath = join(rootDirectory, value);
131
+ if (visited.has(projectPath)) throw new Error(`Circular project reference: ${value}`);
132
+ const chain = new Set(visited);
133
+ chain.add(projectPath);
134
+ let content;
135
+ try {
136
+ content = readFileSync(projectPath, "utf-8");
137
+ } catch (err) {
138
+ throw new Error(`Could not read nested Rojo project: ${value}`, { cause: err });
139
+ }
140
+ const innerTree = resolveTree(JSON.parse(content).tree, dirname(projectPath), chain);
141
+ for (const [innerKey, innerValue] of Object.entries(innerTree)) resolved[innerKey] = innerValue;
142
+ continue;
143
+ }
144
+ if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
145
+ resolved[key] = value;
146
+ continue;
147
+ }
148
+ resolved[key] = resolveTree(value, rootDirectory, visited);
149
+ }
150
+ return resolved;
151
+ }
123
152
  //#endregion
124
153
  //#region src/config/projects.ts
125
154
  function extractStaticRoot(pattern) {
@@ -1653,8 +1682,12 @@ function writeManifest(manifestPath, allFiles, luauRoots, placeFile) {
1653
1682
  function buildRojoProject(rojoProjectPath, roots, placeFile) {
1654
1683
  const rojoProjectRaw = rojoProjectSchema(JSON.parse(fs$1.readFileSync(rojoProjectPath, "utf-8")));
1655
1684
  if (rojoProjectRaw instanceof type.errors) throw new Error(`Malformed Rojo project JSON: ${rojoProjectRaw.toString()}`);
1656
- const rewritten = rewriteRojoProject(rojoProjectRaw, {
1657
- projectRelocation: path$1.relative(COVERAGE_DIR, path$1.dirname(rojoProjectPath)).replaceAll("\\", "/"),
1685
+ const projectRelocation = path$1.relative(COVERAGE_DIR, path$1.dirname(rojoProjectPath)).replaceAll("\\", "/");
1686
+ const rewritten = rewriteRojoProject({
1687
+ ...rojoProjectRaw,
1688
+ tree: resolveNestedProjects(rojoProjectRaw.tree, path$1.dirname(rojoProjectPath))
1689
+ }, {
1690
+ projectRelocation,
1658
1691
  roots
1659
1692
  });
1660
1693
  const rewrittenProjectPath = path$1.join(COVERAGE_DIR, path$1.basename(rojoProjectPath));
@@ -1798,7 +1831,7 @@ function globSync(pattern, options = {}) {
1798
1831
  return walkDirectory(cwd, cwd).filter((file) => matchesGlobPattern(file, pattern));
1799
1832
  }
1800
1833
  function matchesGlobPattern(filePath, pattern) {
1801
- const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLESTAR\}\}/g, ".*");
1834
+ const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*\//g, "{{DOUBLESTAR_SLASH}}").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLESTAR_SLASH\}\}/g, "(.+/)?");
1802
1835
  return new RegExp(`^${regexPattern}$`).test(filePath);
1803
1836
  }
1804
1837
  function walkDirectory(directoryPath, baseDirectory) {
@@ -2095,7 +2128,11 @@ function printFinalStatus(passed) {
2095
2128
  process.stdout.write(`${badge}\n`);
2096
2129
  }
2097
2130
  function processCoverage(config, coverageData) {
2098
- if (!config.collectCoverage || coverageData === void 0) return true;
2131
+ if (!config.collectCoverage) return true;
2132
+ if (coverageData === void 0) {
2133
+ if (!config.silent) process.stderr.write("Warning: coverage data was empty — the Rojo project may point at uninstrumented source\n");
2134
+ return true;
2135
+ }
2099
2136
  const manifest = loadCoverageManifest(config.rootDir);
2100
2137
  if (manifest === void 0) {
2101
2138
  if (!config.silent) process.stderr.write("Warning: Coverage manifest not found, skipping TS mapping\n");
@@ -2277,7 +2314,7 @@ function loadRojoTree(config) {
2277
2314
  const content = fs$1.readFileSync(rojoPath, "utf8");
2278
2315
  const validated = rojoProjectSchema(JSON.parse(content));
2279
2316
  if (validated instanceof type.errors) throw new Error(`Invalid Rojo project: ${validated.summary}`);
2280
- return validated.tree;
2317
+ return resolveNestedProjects(validated.tree, path$1.dirname(rojoPath));
2281
2318
  }
2282
2319
  const STUB_SKIP_KEYS = new Set([
2283
2320
  "outDir",
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
2
2
  import { type } from "arktype";
3
3
  import assert from "node:assert";
4
4
  import * as fs$1 from "node:fs";
5
- import { existsSync } from "node:fs";
5
+ import { existsSync, readFileSync } from "node:fs";
6
6
  import * as path$1 from "node:path";
7
7
  import path from "node:path";
8
8
  import process from "node:process";
@@ -780,6 +780,7 @@ async function loadConfig$1(configPath, cwd = process.cwd()) {
780
780
  cwd,
781
781
  dotenv: false,
782
782
  globalRc: false,
783
+ import: isSea() ? seaImport : void 0,
783
784
  merger,
784
785
  omit$Keys: true,
785
786
  packageJson: false,
@@ -799,6 +800,16 @@ async function loadConfig$1(configPath, cwd = process.cwd()) {
799
800
  config.rootDir ??= cwd;
800
801
  return resolveConfig(config);
801
802
  }
803
+ function isSea() {
804
+ return process.env["JEST_ROBLOX_SEA"] === "true";
805
+ }
806
+ async function seaImport(id) {
807
+ if (id.endsWith(".json")) {
808
+ const content = readFileSync(id, "utf-8");
809
+ return JSON.parse(content);
810
+ }
811
+ return import(id);
812
+ }
802
813
  function merger(...sources) {
803
814
  return defuFn(...sources.filter(Boolean));
804
815
  }
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { A as generateTestScript, C as resolveConfig, D as createOpenCloudBackend, E as OpenCloudBackend, F as defineProject, M as ROOT_ONLY_KEYS, P as defineConfig, R as extractJsonFromOutput, S as loadConfig, T as createStudioBackend, _ as formatResult, a as formatAnnotations, c as execute, f as formatJson, h as formatFailure, i as runTypecheck, j as DEFAULT_CONFIG, k as buildJestArgv, l as formatExecuteOutput, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, t as formatGameOutputNotice, v as formatTestSummary, w as StudioBackend, z as parseJestOutput } from "./game-output-C0_-YIAY.mjs";
1
+ import { A as generateTestScript, C as resolveConfig, D as createOpenCloudBackend, E as OpenCloudBackend, F as defineProject, M as ROOT_ONLY_KEYS, P as defineConfig, R as extractJsonFromOutput, S as loadConfig, T as createStudioBackend, _ as formatResult, a as formatAnnotations, c as execute, f as formatJson, h as formatFailure, i as runTypecheck, j as DEFAULT_CONFIG, k as buildJestArgv, l as formatExecuteOutput, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, t as formatGameOutputNotice, v as formatTestSummary, w as StudioBackend, z as parseJestOutput } from "./game-output-BMGxhjkE.mjs";
2
2
  export { DEFAULT_CONFIG, OpenCloudBackend, ROOT_ONLY_KEYS, StudioBackend, buildJestArgv, createOpenCloudBackend, createStudioBackend, defineConfig, defineProject, execute, extractJsonFromOutput, formatAnnotations, formatExecuteOutput, formatFailure, formatGameOutputNotice, formatJobSummary, formatJson, formatResult, formatTestSummary, generateTestScript, loadConfig, parseGameOutput, parseJestOutput, resolveConfig, runTypecheck, writeGameOutput, writeJsonFile };
Binary file
@@ -7640,7 +7640,7 @@ function f$9() {
7640
7640
  var C$5 = f$9();
7641
7641
  //#endregion
7642
7642
  //#region package.json
7643
- var version = "0.1.1";
7643
+ var version = "0.1.2";
7644
7644
  //#endregion
7645
7645
  //#region node_modules/.pnpm/ws@8.18.0/node_modules/ws/lib/stream.js
7646
7646
  var require_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -11358,7 +11358,7 @@ function normalizeString(path, allowAboveRoot) {
11358
11358
  }
11359
11359
  return res;
11360
11360
  }
11361
- var _DRIVE_LETTER_START_RE, _UNC_REGEX, _IS_ABSOLUTE_RE, _DRIVE_LETTER_RE, _ROOT_FOLDER_RE, _EXTNAME_RE, normalize, join, resolve$2, isAbsolute$1, extname$2, relative, dirname, basename;
11361
+ var _DRIVE_LETTER_START_RE, _UNC_REGEX, _IS_ABSOLUTE_RE, _DRIVE_LETTER_RE, _ROOT_FOLDER_RE, _EXTNAME_RE, normalize, join$2, resolve$2, isAbsolute$1, extname$2, relative, dirname$1, basename;
11362
11362
  var init_pathe_M_eThtNZ = __esmMin((() => {
11363
11363
  _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
11364
11364
  _UNC_REGEX = /^[/\\]{2}/;
@@ -11385,7 +11385,7 @@ var init_pathe_M_eThtNZ = __esmMin((() => {
11385
11385
  }
11386
11386
  return isPathAbsolute && !isAbsolute$1(path) ? `/${path}` : path;
11387
11387
  };
11388
- join = function(...segments) {
11388
+ join$2 = function(...segments) {
11389
11389
  let path = "";
11390
11390
  for (const seg of segments) {
11391
11391
  if (!seg) continue;
@@ -11432,7 +11432,7 @@ var init_pathe_M_eThtNZ = __esmMin((() => {
11432
11432
  }
11433
11433
  return [..._from.map(() => ".."), ..._to].join("/");
11434
11434
  };
11435
- dirname = function(p) {
11435
+ dirname$1 = function(p) {
11436
11436
  const segments = normalizeWindowsPath$1(p).replace(/\/$/, "").split("/").slice(0, -1);
11437
11437
  if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) segments[0] += "/";
11438
11438
  return segments.join("/") || (isAbsolute$1(p) ? "/" : ".");
@@ -13115,11 +13115,11 @@ async function findFile(filename, _options = {}) {
13115
13115
  let root = segments.findIndex((r) => r.match(options.rootPattern));
13116
13116
  if (root === -1) root = 0;
13117
13117
  if (options.reverse) for (let index = root + 1; index <= segments.length; index++) for (const filename2 of filenames) {
13118
- const filePath = join(...segments.slice(0, index), filename2);
13118
+ const filePath = join$2(...segments.slice(0, index), filename2);
13119
13119
  if (await options.test(filePath)) return filePath;
13120
13120
  }
13121
13121
  else for (let index = segments.length; index > root; index--) for (const filename2 of filenames) {
13122
- const filePath = join(...segments.slice(0, index), filename2);
13122
+ const filePath = join$2(...segments.slice(0, index), filename2);
13123
13123
  if (await options.test(filePath)) return filePath;
13124
13124
  }
13125
13125
  throw new Error(`Cannot find matching ${filename} in ${options.startingFrom} or parent directories`);
@@ -13179,10 +13179,10 @@ async function resolvePackageJSON(id = process.cwd(), options = {}) {
13179
13179
  });
13180
13180
  }
13181
13181
  const workspaceTests = {
13182
- workspaceFile: (opts) => findFile(workspaceFiles, opts).then((r) => dirname(r)),
13182
+ workspaceFile: (opts) => findFile(workspaceFiles, opts).then((r) => dirname$1(r)),
13183
13183
  gitConfig: (opts) => findFile(".git/config", opts).then((r) => resolve$2(r, "../..")),
13184
- lockFile: (opts) => findFile(lockFiles, opts).then((r) => dirname(r)),
13185
- packageJson: (opts) => findFile(packageFiles, opts).then((r) => dirname(r))
13184
+ lockFile: (opts) => findFile(lockFiles, opts).then((r) => dirname$1(r)),
13185
+ packageJson: (opts) => findFile(packageFiles, opts).then((r) => dirname$1(r))
13186
13186
  };
13187
13187
  async function findWorkspaceDir(id = process.cwd(), options = {}) {
13188
13188
  const startingFrom = _resolvePath(id, options);
@@ -17263,7 +17263,7 @@ function parsePackageManagerField(packageManager) {
17263
17263
  async function detectPackageManager(cwd, options = {}) {
17264
17264
  const detected = await findup(resolve$2(cwd || "."), async (path) => {
17265
17265
  if (!options.ignorePackageJSON) {
17266
- const packageJSONPath = join(path, "package.json");
17266
+ const packageJSONPath = join$2(path, "package.json");
17267
17267
  if ((0, node_fs.existsSync)(packageJSONPath)) {
17268
17268
  const packageJSON = JSON.parse(await (0, node_fs_promises.readFile)(packageJSONPath, "utf8"));
17269
17269
  if (packageJSON?.packageManager) {
@@ -17284,7 +17284,7 @@ async function detectPackageManager(cwd, options = {}) {
17284
17284
  }
17285
17285
  }
17286
17286
  }
17287
- if ((0, node_fs.existsSync)(join(path, "deno.json"))) return packageManagers.find((pm) => pm.name === "deno");
17287
+ if ((0, node_fs.existsSync)(join$2(path, "deno.json"))) return packageManagers.find((pm) => pm.name === "deno");
17288
17288
  }
17289
17289
  if (!options.ignoreLockFile) {
17290
17290
  for (const packageManager of packageManagers) if ([packageManager.lockFile, packageManager.files].flat().filter(Boolean).some((file) => (0, node_fs.existsSync)(resolve$2(path, file)))) return { ...packageManager };
@@ -35752,7 +35752,7 @@ async function downloadTemplate(input, options = {}) {
35752
35752
  const tarPath = resolve$2(resolve$2(cacheDirectory(), providerName, template.name), (template.version || template.name) + ".tar.gz");
35753
35753
  if (options.preferOffline && (0, node_fs.existsSync)(tarPath)) options.offline = true;
35754
35754
  if (!options.offline) {
35755
- await (0, node_fs_promises.mkdir)(dirname(tarPath), { recursive: true });
35755
+ await (0, node_fs_promises.mkdir)(dirname$1(tarPath), { recursive: true });
35756
35756
  const s2 = Date.now();
35757
35757
  await download(template.tar, tarPath, { headers: {
35758
35758
  Authorization: options.auth ? `Bearer ${options.auth}` : void 0,
@@ -40894,9 +40894,9 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
40894
40894
  const cloneName = source.replace(/\W+/g, "_").split("_").splice(0, 3).join("_") + "_" + digest(source).slice(0, 10).replace(/[-_]/g, "");
40895
40895
  let cloneDir;
40896
40896
  const localNodeModules = resolve$2(options.cwd, "node_modules");
40897
- const parentDir = dirname(options.cwd);
40898
- if (basename(parentDir) === ".c12") cloneDir = join(parentDir, cloneName);
40899
- else if ((0, node_fs.existsSync)(localNodeModules)) cloneDir = join(localNodeModules, ".c12", cloneName);
40897
+ const parentDir = dirname$1(options.cwd);
40898
+ if (basename(parentDir) === ".c12") cloneDir = join$2(parentDir, cloneName);
40899
+ else if ((0, node_fs.existsSync)(localNodeModules)) cloneDir = join$2(localNodeModules, ".c12", cloneName);
40900
40900
  else cloneDir = process.env.XDG_CACHE_HOME ? resolve$2(process.env.XDG_CACHE_HOME, "c12", cloneName) : resolve$2((0, node_os.homedir)(), ".cache/c12", cloneName);
40901
40901
  if ((0, node_fs.existsSync)(cloneDir) && !sourceOptions.install) await (0, node_fs_promises.rm)(cloneDir, { recursive: true });
40902
40902
  source = (await downloadTemplate(source, {
@@ -40911,7 +40911,7 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
40911
40911
  if (NPM_PACKAGE_RE.test(source)) source = tryResolve(source, options) || source;
40912
40912
  const ext = extname$2(source);
40913
40913
  const isDir = _isDirectory(resolve$2(options.cwd, source)) ?? (!ext || ext === basename(source));
40914
- const cwd = resolve$2(options.cwd, isDir ? source : dirname(source));
40914
+ const cwd = resolve$2(options.cwd, isDir ? source : dirname$1(source));
40915
40915
  if (isDir) source = options.configFile;
40916
40916
  const res = {
40917
40917
  config: void 0,
@@ -40935,7 +40935,7 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
40935
40935
  const { createJiti } = await Promise.resolve().then(() => (init_jiti(), jiti_exports)).catch(() => {
40936
40936
  throw new Error(`Failed to load config file \`${res.configFile}\`: ${error?.message}. Hint install \`jiti\` for compatibility.`, { cause: error });
40937
40937
  });
40938
- const jiti = createJiti(join(options.cwd || ".", options.configFile || "/"), {
40938
+ const jiti = createJiti(join$2(options.cwd || ".", options.configFile || "/"), {
40939
40939
  interopDefault: true,
40940
40940
  moduleCache: false,
40941
40941
  extensions: [...SUPPORTED_EXTENSIONS]
@@ -40963,7 +40963,7 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
40963
40963
  function tryResolve(id, options) {
40964
40964
  const res = resolveModulePath(id, {
40965
40965
  try: true,
40966
- from: (0, node_url.pathToFileURL)(join(options.cwd || ".", options.configFile || "/")),
40966
+ from: (0, node_url.pathToFileURL)(join$2(options.cwd || ".", options.configFile || "/")),
40967
40967
  suffixes: ["", "/index"],
40968
40968
  extensions: SUPPORTED_EXTENSIONS,
40969
40969
  cache: false
@@ -41654,6 +41654,7 @@ async function loadConfig(configPath, cwd = node_process.default.cwd()) {
41654
41654
  cwd,
41655
41655
  dotenv: false,
41656
41656
  globalRc: false,
41657
+ import: isSea() ? seaImport : void 0,
41657
41658
  merger,
41658
41659
  omit$Keys: true,
41659
41660
  packageJson: false,
@@ -41673,6 +41674,16 @@ async function loadConfig(configPath, cwd = node_process.default.cwd()) {
41673
41674
  config.rootDir ??= cwd;
41674
41675
  return resolveConfig(config);
41675
41676
  }
41677
+ function isSea() {
41678
+ return node_process.default.env["JEST_ROBLOX_SEA"] === "true";
41679
+ }
41680
+ async function seaImport(id) {
41681
+ if (id.endsWith(".json")) {
41682
+ const content = (0, node_fs.readFileSync)(id, "utf-8");
41683
+ return JSON.parse(content);
41684
+ }
41685
+ return import(id);
41686
+ }
41676
41687
  function merger(...sources) {
41677
41688
  return defuFn(...sources.filter(Boolean));
41678
41689
  }
@@ -41691,10 +41702,39 @@ function stripTsExtension(pattern) {
41691
41702
  }
41692
41703
  //#endregion
41693
41704
  //#region src/utils/rojo-tree.ts
41705
+ function resolveNestedProjects(tree, rootDirectory) {
41706
+ return resolveTree(tree, rootDirectory, /* @__PURE__ */ new Set());
41707
+ }
41694
41708
  function collectPaths(node, result) {
41695
41709
  for (const [key, value] of Object.entries(node)) if (key === "$path" && typeof value === "string") result.push(value.replaceAll("\\", "/"));
41696
41710
  else if (typeof value === "object" && !Array.isArray(value) && !key.startsWith("$")) collectPaths(value, result);
41697
41711
  }
41712
+ function resolveTree(node, rootDirectory, visited) {
41713
+ const resolved = {};
41714
+ for (const [key, value] of Object.entries(node)) {
41715
+ if (key === "$path" && typeof value === "string" && value.endsWith(".project.json")) {
41716
+ const projectPath = (0, node_path.join)(rootDirectory, value);
41717
+ if (visited.has(projectPath)) throw new Error(`Circular project reference: ${value}`);
41718
+ const chain = new Set(visited);
41719
+ chain.add(projectPath);
41720
+ let content;
41721
+ try {
41722
+ content = (0, node_fs.readFileSync)(projectPath, "utf-8");
41723
+ } catch (err) {
41724
+ throw new Error(`Could not read nested Rojo project: ${value}`, { cause: err });
41725
+ }
41726
+ const innerTree = resolveTree(JSON.parse(content).tree, (0, node_path.dirname)(projectPath), chain);
41727
+ for (const [innerKey, innerValue] of Object.entries(innerTree)) resolved[innerKey] = innerValue;
41728
+ continue;
41729
+ }
41730
+ if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
41731
+ resolved[key] = value;
41732
+ continue;
41733
+ }
41734
+ resolved[key] = resolveTree(value, rootDirectory, visited);
41735
+ }
41736
+ return resolved;
41737
+ }
41698
41738
  //#endregion
41699
41739
  //#region src/config/projects.ts
41700
41740
  function extractStaticRoot(pattern) {
@@ -49643,8 +49683,8 @@ var import_RojoResolver = (/* @__PURE__ */ __commonJSMin(((exports) => {
49643
49683
  this.value = value;
49644
49684
  }
49645
49685
  };
49646
- const SCHEMA_PATH = path_1.default.join(PACKAGE_ROOT, "rojo-schema.json");
49647
- const validateRojo = new Lazy(() => ajv.compile(JSON.parse(fs_extra_1.default.readFileSync(SCHEMA_PATH).toString())));
49686
+ path_1.default.join(PACKAGE_ROOT, "rojo-schema.json");
49687
+ const validateRojo = new Lazy(() => ajv.compile(JSON.parse("{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"required\": [\"name\", \"tree\"],\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"servePort\": {\n \"type\": \"integer\"\n },\n \"tree\": {\n \"$id\": \"tree\",\n \"type\": \"object\",\n \"properties\": {\n \"$className\": {\n \"type\": \"string\"\n },\n \"$ignoreUnknownInstances\": {\n \"type\": \"boolean\"\n },\n \"$path\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"optional\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n },\n \"$properties\": {\n \"type\": \"object\"\n }\n },\n \"patternProperties\": {\n \"^[^\\\\$].*$\": { \"$ref\": \"tree\" }\n }\n }\n }\n}\n")));
49648
49688
  function isValidRojoConfig(value) {
49649
49689
  return validateRojo.get()(value) === true;
49650
49690
  }
@@ -54130,8 +54170,12 @@ function writeManifest(manifestPath, allFiles, luauRoots, placeFile) {
54130
54170
  function buildRojoProject(rojoProjectPath, roots, placeFile) {
54131
54171
  const rojoProjectRaw = rojoProjectSchema(JSON.parse(node_fs.readFileSync(rojoProjectPath, "utf-8")));
54132
54172
  if (rojoProjectRaw instanceof type.errors) throw new Error(`Malformed Rojo project JSON: ${rojoProjectRaw.toString()}`);
54133
- const rewritten = rewriteRojoProject(rojoProjectRaw, {
54134
- projectRelocation: node_path.relative(COVERAGE_DIR, node_path.dirname(rojoProjectPath)).replaceAll("\\", "/"),
54173
+ const projectRelocation = node_path.relative(COVERAGE_DIR, node_path.dirname(rojoProjectPath)).replaceAll("\\", "/");
54174
+ const rewritten = rewriteRojoProject({
54175
+ ...rojoProjectRaw,
54176
+ tree: resolveNestedProjects(rojoProjectRaw.tree, node_path.dirname(rojoProjectPath))
54177
+ }, {
54178
+ projectRelocation,
54135
54179
  roots
54136
54180
  });
54137
54181
  const rewrittenProjectPath = node_path.join(COVERAGE_DIR, node_path.basename(rojoProjectPath));
@@ -60807,7 +60851,7 @@ function globSync(pattern, options = {}) {
60807
60851
  return walkDirectory(cwd, cwd).filter((file) => matchesGlobPattern(file, pattern));
60808
60852
  }
60809
60853
  function matchesGlobPattern(filePath, pattern) {
60810
- const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLESTAR\}\}/g, ".*");
60854
+ const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*\//g, "{{DOUBLESTAR_SLASH}}").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLESTAR_SLASH\}\}/g, "(.+/)?");
60811
60855
  return new RegExp(`^${regexPattern}$`).test(filePath);
60812
60856
  }
60813
60857
  function walkDirectory(directoryPath, baseDirectory) {
@@ -61104,7 +61148,11 @@ function printFinalStatus(passed) {
61104
61148
  node_process.default.stdout.write(`${badge}\n`);
61105
61149
  }
61106
61150
  function processCoverage(config, coverageData) {
61107
- if (!config.collectCoverage || coverageData === void 0) return true;
61151
+ if (!config.collectCoverage) return true;
61152
+ if (coverageData === void 0) {
61153
+ if (!config.silent) node_process.default.stderr.write("Warning: coverage data was empty — the Rojo project may point at uninstrumented source\n");
61154
+ return true;
61155
+ }
61108
61156
  const manifest = loadCoverageManifest(config.rootDir);
61109
61157
  if (manifest === void 0) {
61110
61158
  if (!config.silent) node_process.default.stderr.write("Warning: Coverage manifest not found, skipping TS mapping\n");
@@ -61286,7 +61334,7 @@ function loadRojoTree(config) {
61286
61334
  const content = node_fs.readFileSync(rojoPath, "utf8");
61287
61335
  const validated = rojoProjectSchema(JSON.parse(content));
61288
61336
  if (validated instanceof type.errors) throw new Error(`Invalid Rojo project: ${validated.summary}`);
61289
- return validated.tree;
61337
+ return resolveNestedProjects(validated.tree, node_path.dirname(rojoPath));
61290
61338
  }
61291
61339
  const STUB_SKIP_KEYS = new Set([
61292
61340
  "outDir",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isentinel/jest-roblox",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Jest-compatible CLI for running roblox-ts tests via Roblox Open Cloud",
5
5
  "keywords": [
6
6
  "jest",