@isentinel/jest-roblox 0.2.2 → 0.2.4
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/README.md +6 -1
- package/dist/cli.mjs +8 -10
- package/dist/{game-output-CwmtpYhn.mjs → game-output-BtWj32M8.mjs} +153 -5
- package/dist/index.d.mts +16 -1
- package/dist/index.mjs +1 -1
- package/dist/sea/jest-roblox +0 -0
- package/dist/sea-entry.cjs +1 -1
- package/package.json +6 -6
- package/plugin/JestRobloxRunner.rbxm +0 -0
- package/plugin/src/test-in-run-mode.server.luau +37 -11
package/README.md
CHANGED
|
@@ -227,6 +227,11 @@ Connects to Roblox Studio over WebSocket. Faster than Open Cloud (no upload
|
|
|
227
227
|
step), but Studio must be open with the plugin running. Studio doesn't expose which place is open, so
|
|
228
228
|
multiple concurrent projects aren't supported yet.
|
|
229
229
|
|
|
230
|
+
> [!NOTE]
|
|
231
|
+
> For `--coverage`, prefer `--backend open-cloud` since the coverage output is
|
|
232
|
+
> built to a separate output under `.jest-roblox-coverage/` that is likely not
|
|
233
|
+
> the studio place being served.
|
|
234
|
+
|
|
230
235
|
Install the plugin with [Drillbit](https://github.com/jacktabscode/drillbit):
|
|
231
236
|
|
|
232
237
|
#### Configuration file
|
|
@@ -235,7 +240,7 @@ Create a file named drillbit.toml in your project's directory.
|
|
|
235
240
|
|
|
236
241
|
```toml
|
|
237
242
|
[plugins.jest_roblox]
|
|
238
|
-
github = "https://github.com/christopher-buss/jest-roblox-cli/releases/download/v0.2.
|
|
243
|
+
github = "https://github.com/christopher-buss/jest-roblox-cli/releases/download/v0.2.4/JestRobloxRunner.rbxm"
|
|
239
244
|
```
|
|
240
245
|
|
|
241
246
|
Then run `drillbit` and it will download the plugin and install it in Studio for you.
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as collectPaths, D as formatTypecheckSummary, H as ROOT_ONLY_KEYS, I as createStudioBackend, J as LuauScriptError, K as isValidBackend, M as resolveNestedProjects, N as loadConfig$1, O as formatBanner, R as createOpenCloudBackend, S as formatAgentMultiProject, T as formatResult, U as VALID_BACKENDS, _ as resolveTsconfigDirectories, a as formatAnnotations, c as visitBlock, d as buildProjectJob, f as execute, g as processProjectResult, h as loadCoverageManifest, i as runTypecheck, j as findInTree, k as combineSourceMappers, m as formatExecuteOutput, n as parseGameOutput, o as formatJobSummary, p as executeBackend, q as hashBuffer, r as writeGameOutput, s as resolveGitHubActionsOptions, t as formatGameOutputNotice, v as rojoProjectSchema, w as formatMultiProjectResult, x as writeJsonFile, y as findFormatterOptions } from "./game-output-BtWj32M8.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { type } from "arktype";
|
|
4
4
|
import assert from "node:assert";
|
|
@@ -11,21 +11,19 @@ import { parseArgs as parseArgs$1 } from "node:util";
|
|
|
11
11
|
import { isAgent } from "std-env";
|
|
12
12
|
import color from "tinyrainbow";
|
|
13
13
|
import { WebSocketServer } from "ws";
|
|
14
|
-
import
|
|
14
|
+
import * as os from "node:os";
|
|
15
|
+
import { Buffer } from "node:buffer";
|
|
15
16
|
import { loadConfig } from "c12";
|
|
16
|
-
import { collectPaths, findInTree } from "@isentinel/rojo-utils";
|
|
17
17
|
import { getTsconfig } from "get-tsconfig";
|
|
18
18
|
import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
19
19
|
import * as cp from "node:child_process";
|
|
20
|
-
import * as os from "node:os";
|
|
21
20
|
import { RojoResolver } from "@roblox-ts/rojo-resolver";
|
|
22
|
-
import { Buffer } from "node:buffer";
|
|
23
21
|
import picomatch from "picomatch";
|
|
24
22
|
import istanbulCoverage from "istanbul-lib-coverage";
|
|
25
23
|
import istanbulReport from "istanbul-lib-report";
|
|
26
24
|
import istanbulReports from "istanbul-reports";
|
|
27
25
|
//#region package.json
|
|
28
|
-
var version = "0.2.
|
|
26
|
+
var version = "0.2.4";
|
|
29
27
|
//#endregion
|
|
30
28
|
//#region src/backends/auto.ts
|
|
31
29
|
var StudioWithFallback = class {
|
|
@@ -1473,7 +1471,7 @@ function instrumentRoot(options) {
|
|
|
1473
1471
|
functionCount: collectorResult.functions.length,
|
|
1474
1472
|
instrumentedLuauPath,
|
|
1475
1473
|
originalLuauPath,
|
|
1476
|
-
sourceHash: hashBuffer
|
|
1474
|
+
sourceHash: hashBuffer(sourceBuffer),
|
|
1477
1475
|
sourceMapPath,
|
|
1478
1476
|
statementCount: collectorResult.statements.length
|
|
1479
1477
|
};
|
|
@@ -1582,7 +1580,7 @@ const previousManifestSchema = type({
|
|
|
1582
1580
|
}).as();
|
|
1583
1581
|
function collectLuauRootsFromRojo(project, config) {
|
|
1584
1582
|
const paths = [];
|
|
1585
|
-
collectPaths
|
|
1583
|
+
collectPaths(project.tree, paths);
|
|
1586
1584
|
const ignorePatterns = config.coveragePathIgnorePatterns;
|
|
1587
1585
|
const isIgnored = picomatch(ignorePatterns, { contains: true });
|
|
1588
1586
|
return paths.filter((directoryPath) => {
|
|
@@ -1732,7 +1730,7 @@ function syncNonInstrumentedFiles(luauRoot, shadowDirectory, previousNonInstrume
|
|
|
1732
1730
|
for (const relativePath of discovered) {
|
|
1733
1731
|
const sourcePath = `${posixRoot}/${relativePath}`;
|
|
1734
1732
|
const shadowPath = `${shadowDirectory}/${relativePath}`;
|
|
1735
|
-
const currentHash = hashBuffer
|
|
1733
|
+
const currentHash = hashBuffer(fs$1.readFileSync(path$1.resolve(sourcePath)));
|
|
1736
1734
|
const previousRecord = previousNonInstrumented?.[sourcePath];
|
|
1737
1735
|
if (previousRecord?.sourceHash === currentHash) {
|
|
1738
1736
|
files[sourcePath] = previousRecord;
|
|
@@ -1762,7 +1760,7 @@ function computeSkipFiles(luauRoot, previousManifest) {
|
|
|
1762
1760
|
const relativePath = fileKey.slice(posixRoot.length + 1);
|
|
1763
1761
|
const sourcePath = path$1.resolve(record.originalLuauPath);
|
|
1764
1762
|
if (!fs$1.existsSync(sourcePath)) continue;
|
|
1765
|
-
if (hashBuffer
|
|
1763
|
+
if (hashBuffer(fs$1.readFileSync(sourcePath)) === record.sourceHash) skipFiles.add(relativePath);
|
|
1766
1764
|
}
|
|
1767
1765
|
return skipFiles;
|
|
1768
1766
|
}
|
|
@@ -4,15 +4,16 @@ import assert from "node:assert";
|
|
|
4
4
|
import * as fs$1 from "node:fs";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import * as path$1 from "node:path";
|
|
7
|
-
import path from "node:path";
|
|
7
|
+
import path, { dirname, join, relative } from "node:path";
|
|
8
8
|
import process from "node:process";
|
|
9
9
|
import color from "tinyrainbow";
|
|
10
10
|
import { WebSocketServer } from "ws";
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
11
|
+
import { homedir, tmpdir } from "node:os";
|
|
12
|
+
import * as crypto from "node:crypto";
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
|
+
import buffer from "node:buffer";
|
|
15
|
+
import { createDefineConfig, loadConfig } from "c12";
|
|
14
16
|
import { defuFn } from "defu";
|
|
15
|
-
import { collectPaths as collectPaths$1, resolveNestedProjects } from "@isentinel/rojo-utils";
|
|
16
17
|
import { getTsconfig } from "get-tsconfig";
|
|
17
18
|
import { TraceMap, originalPositionFor, sourceContentFor } from "@jridgewell/trace-mapping";
|
|
18
19
|
import hljs from "highlight.js/lib/core";
|
|
@@ -201,6 +202,82 @@ function extractSetupSeconds(parsed) {
|
|
|
201
202
|
return setup;
|
|
202
203
|
}
|
|
203
204
|
//#endregion
|
|
205
|
+
//#region packages/roblox-runner/dist/index.mjs
|
|
206
|
+
const CACHE_DIR_NAME = "jest-roblox";
|
|
207
|
+
function getCacheDirectory() {
|
|
208
|
+
const xdgCacheHome = process.env["XDG_CACHE_HOME"];
|
|
209
|
+
if (xdgCacheHome !== void 0 && xdgCacheHome !== "") return path$1.join(xdgCacheHome, CACHE_DIR_NAME);
|
|
210
|
+
if (process.platform === "win32") {
|
|
211
|
+
const localAppData = process.env["LOCALAPPDATA"];
|
|
212
|
+
if (localAppData !== void 0 && localAppData !== "") return path$1.join(localAppData, CACHE_DIR_NAME);
|
|
213
|
+
return path$1.join(tmpdir(), CACHE_DIR_NAME);
|
|
214
|
+
}
|
|
215
|
+
return path$1.join(homedir(), ".cache", CACHE_DIR_NAME);
|
|
216
|
+
}
|
|
217
|
+
function getCacheKey(universeId, placeId) {
|
|
218
|
+
return `${universeId}:${placeId}`;
|
|
219
|
+
}
|
|
220
|
+
function isUploaded(cache, key, fileHash) {
|
|
221
|
+
return cache[key]?.fileHash === fileHash;
|
|
222
|
+
}
|
|
223
|
+
function markUploaded(cache, key, fileHash) {
|
|
224
|
+
cache[key] = {
|
|
225
|
+
fileHash,
|
|
226
|
+
uploadedAt: Date.now()
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function readCache(cacheFilePath) {
|
|
230
|
+
try {
|
|
231
|
+
const data = fs$1.readFileSync(cacheFilePath, "utf-8");
|
|
232
|
+
return JSON.parse(data);
|
|
233
|
+
} catch {
|
|
234
|
+
return {};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function writeCache(cacheFilePath, cache) {
|
|
238
|
+
const cacheDirectory = path$1.dirname(cacheFilePath);
|
|
239
|
+
fs$1.mkdirSync(cacheDirectory, { recursive: true });
|
|
240
|
+
fs$1.writeFileSync(cacheFilePath, JSON.stringify(cache, null, 2));
|
|
241
|
+
}
|
|
242
|
+
function hashBuffer(data) {
|
|
243
|
+
return crypto.createHash("sha256").update(data).digest("hex");
|
|
244
|
+
}
|
|
245
|
+
function createFetchClient(defaultHeaders) {
|
|
246
|
+
return { async request(method, url, options) {
|
|
247
|
+
const headers = {
|
|
248
|
+
...defaultHeaders,
|
|
249
|
+
...options?.headers
|
|
250
|
+
};
|
|
251
|
+
const fetchOptions = {
|
|
252
|
+
headers,
|
|
253
|
+
method
|
|
254
|
+
};
|
|
255
|
+
if (options?.body !== void 0) if (options.body instanceof buffer.Buffer) fetchOptions.body = options.body;
|
|
256
|
+
else {
|
|
257
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
258
|
+
headers["Content-Type"] = "application/json";
|
|
259
|
+
}
|
|
260
|
+
const response = await fetch(url, fetchOptions);
|
|
261
|
+
return {
|
|
262
|
+
body: await (response.headers.get("content-type")?.includes("application/json") ?? false ? response.json() : response.text()),
|
|
263
|
+
headers: { "retry-after": response.headers.get("retry-after") ?? void 0 },
|
|
264
|
+
ok: response.ok,
|
|
265
|
+
status: response.status
|
|
266
|
+
};
|
|
267
|
+
} };
|
|
268
|
+
}
|
|
269
|
+
type({ path: "string" });
|
|
270
|
+
type({
|
|
271
|
+
"error?": { "message?": "string" },
|
|
272
|
+
"output?": { "results?": "string[]" },
|
|
273
|
+
"state": "'CANCELLED' | 'COMPLETE' | 'FAILED' | 'PROCESSING'"
|
|
274
|
+
});
|
|
275
|
+
type({
|
|
276
|
+
outputs: "string[]",
|
|
277
|
+
request_id: "string",
|
|
278
|
+
type: "'results'"
|
|
279
|
+
});
|
|
280
|
+
//#endregion
|
|
204
281
|
//#region src/config/schema.ts
|
|
205
282
|
const ROOT_ONLY_KEYS = new Set([
|
|
206
283
|
"backend",
|
|
@@ -866,6 +943,77 @@ function resolveFunctionValues(config) {
|
|
|
866
943
|
return resolved;
|
|
867
944
|
}
|
|
868
945
|
//#endregion
|
|
946
|
+
//#region packages/rojo-utils/dist/index.mjs
|
|
947
|
+
function resolveNestedProjects(tree, rootDirectory) {
|
|
948
|
+
return resolveTree(tree, rootDirectory, rootDirectory, /* @__PURE__ */ new Set());
|
|
949
|
+
}
|
|
950
|
+
function collectPaths(node, result) {
|
|
951
|
+
for (const [key, value] of Object.entries(node)) if (key === "$path" && typeof value === "string") result.push(value.replaceAll("\\", "/"));
|
|
952
|
+
else if (typeof value === "object" && !Array.isArray(value) && !key.startsWith("$")) collectPaths(value, result);
|
|
953
|
+
}
|
|
954
|
+
function inlineNestedProject(projectPath, currentDirectory, originalRoot, visited) {
|
|
955
|
+
const chain = new Set(visited);
|
|
956
|
+
chain.add(projectPath);
|
|
957
|
+
let content;
|
|
958
|
+
try {
|
|
959
|
+
content = readFileSync(projectPath, "utf-8");
|
|
960
|
+
} catch (err) {
|
|
961
|
+
const relativePath = relative(currentDirectory, projectPath);
|
|
962
|
+
throw new Error(`Could not read nested Rojo project: ${relativePath}`, { cause: err });
|
|
963
|
+
}
|
|
964
|
+
let project;
|
|
965
|
+
try {
|
|
966
|
+
project = JSON.parse(content);
|
|
967
|
+
} catch (err) {
|
|
968
|
+
const relativePath = relative(currentDirectory, projectPath);
|
|
969
|
+
throw new Error(`Failed to parse nested Rojo project: ${relativePath}`, { cause: err });
|
|
970
|
+
}
|
|
971
|
+
return resolveTree(project.tree, dirname(projectPath), originalRoot, chain);
|
|
972
|
+
}
|
|
973
|
+
function resolveRootRelativePath(currentDirectory, value, originalRoot) {
|
|
974
|
+
return relative(originalRoot, join(currentDirectory, value)).replaceAll("\\", "/");
|
|
975
|
+
}
|
|
976
|
+
function resolveTree(node, currentDirectory, originalRoot, visited) {
|
|
977
|
+
const resolved = {};
|
|
978
|
+
for (const [key, value] of Object.entries(node)) {
|
|
979
|
+
if (key === "$path" && typeof value === "string" && value.endsWith(".project.json")) {
|
|
980
|
+
const projectPath = join(currentDirectory, value);
|
|
981
|
+
if (visited.has(projectPath)) throw new Error(`Circular project reference: ${value}`);
|
|
982
|
+
const innerTree = inlineNestedProject(projectPath, currentDirectory, originalRoot, visited);
|
|
983
|
+
for (const [innerKey, innerValue] of Object.entries(innerTree)) resolved[innerKey] = innerValue;
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (key === "$path" && typeof value === "string") {
|
|
987
|
+
resolved[key] = resolveRootRelativePath(currentDirectory, value, originalRoot);
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
|
|
991
|
+
resolved[key] = value;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
resolved[key] = resolveTree(value, currentDirectory, originalRoot, visited);
|
|
995
|
+
}
|
|
996
|
+
return resolved;
|
|
997
|
+
}
|
|
998
|
+
function matchNodePath(childNode, targetPath, childDataModelPath) {
|
|
999
|
+
const nodePath = childNode.$path;
|
|
1000
|
+
if (typeof nodePath !== "string") return;
|
|
1001
|
+
const normalizedNodePath = nodePath.replace(/\/$/, "");
|
|
1002
|
+
if (normalizedNodePath === targetPath) return childDataModelPath;
|
|
1003
|
+
if (targetPath.startsWith(`${normalizedNodePath}/`)) return `${childDataModelPath}/${targetPath.slice(normalizedNodePath.length + 1)}`;
|
|
1004
|
+
}
|
|
1005
|
+
function findInTree(node, targetPath, currentDataModelPath) {
|
|
1006
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1007
|
+
if (key.startsWith("$") || typeof value !== "object") continue;
|
|
1008
|
+
const childNode = value;
|
|
1009
|
+
const childDataModelPath = currentDataModelPath === "" ? key : `${currentDataModelPath}/${key}`;
|
|
1010
|
+
const pathMatch = matchNodePath(childNode, targetPath, childDataModelPath);
|
|
1011
|
+
if (pathMatch !== void 0) return pathMatch;
|
|
1012
|
+
const found = findInTree(childNode, targetPath, childDataModelPath);
|
|
1013
|
+
if (found !== void 0) return found;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
//#endregion
|
|
869
1017
|
//#region src/utils/normalize-windows-path.ts
|
|
870
1018
|
const DRIVE_LETTER_START_REGEX = /^[A-Za-z]:\//;
|
|
871
1019
|
function normalizeWindowsPath(input = "") {
|
|
@@ -3456,4 +3604,4 @@ function writeGameOutput(filePath, entries) {
|
|
|
3456
3604
|
fs$1.writeFileSync(absolutePath, JSON.stringify(entries, null, 2));
|
|
3457
3605
|
}
|
|
3458
3606
|
//#endregion
|
|
3459
|
-
export {
|
|
3607
|
+
export { collectPaths as A, generateTestScript as B, formatFailure as C, formatTypecheckSummary as D, formatTestSummary as E, StudioBackend as F, defineProject as G, ROOT_ONLY_KEYS as H, createStudioBackend as I, LuauScriptError as J, isValidBackend as K, OpenCloudBackend as L, resolveNestedProjects as M, loadConfig$1 as N, formatBanner as O, resolveConfig as P, createOpenCloudBackend as R, formatAgentMultiProject as S, formatResult as T, VALID_BACKENDS as U, DEFAULT_CONFIG as V, defineConfig as W, parseJestOutput as X, extractJsonFromOutput as Y, resolveTsconfigDirectories as _, formatAnnotations as a, formatJson as b, visitBlock as c, buildProjectJob as d, execute as f, processProjectResult as g, loadCoverageManifest as h, runTypecheck as i, findInTree as j, combineSourceMappers as k, visitExpression as l, formatExecuteOutput as m, parseGameOutput as n, formatJobSummary as o, executeBackend as p, hashBuffer as q, writeGameOutput as r, resolveGitHubActionsOptions as s, formatGameOutputNotice as t, visitStatement as u, rojoProjectSchema as v, formatMultiProjectResult as w, writeJsonFile as x, findFormatterOptions as y, buildJestArgv as z };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { A as defineConfig, C as FormatterEntry, D as ROOT_ONLY_KEYS, E as ProjectTestConfig, M as Argv, O as ResolvedConfig, S as DisplayName, T as ProjectEntry, _ as ResolvedProjectConfig, a as formatExecuteOutput, b as ConfigInput, c as Backend, d as extractJsonFromOutput, f as parseJestOutput, g as TestStatus, h as TestFileResult, i as execute, j as defineProject, k as SnapshotFormatOptions, l as BackendOptions, m as TestCaseResult, n as ExecuteResult, o as TimingResult, p as JestResult, r as FormatOutputOptions, s as SourceMapper, t as ExecuteOptions, u as BackendResult, v as CliOptions, w as InlineProjectConfig, x as DEFAULT_CONFIG, y as Config } from "./executor-B2IDh6bH.mjs";
|
|
2
2
|
import { WebSocket, WebSocketServer } from "ws";
|
|
3
|
-
import { HttpClient } from "@isentinel/roblox-runner";
|
|
4
3
|
import buffer from "node:buffer";
|
|
5
4
|
|
|
5
|
+
//#region packages/roblox-runner/dist/index.d.mts
|
|
6
|
+
//#region src/http-client.d.ts
|
|
7
|
+
interface HttpResponse {
|
|
8
|
+
body: unknown;
|
|
9
|
+
headers?: Record<string, string | undefined>;
|
|
10
|
+
ok: boolean;
|
|
11
|
+
status: number;
|
|
12
|
+
}
|
|
13
|
+
interface RequestOptions {
|
|
14
|
+
body?: unknown;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
interface HttpClient {
|
|
18
|
+
request(method: string, url: string, options?: RequestOptions): Promise<HttpResponse>;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
6
21
|
//#region src/backends/open-cloud.d.ts
|
|
7
22
|
interface OpenCloudCredentials {
|
|
8
23
|
apiKey: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { B as
|
|
1
|
+
import { B as generateTestScript, C as formatFailure, E as formatTestSummary, F as StudioBackend, G as defineProject, H as ROOT_ONLY_KEYS, I as createStudioBackend, L as OpenCloudBackend, N as loadConfig, P as resolveConfig, R as createOpenCloudBackend, T as formatResult, V as DEFAULT_CONFIG, W as defineConfig, X as parseJestOutput, Y as extractJsonFromOutput, a as formatAnnotations, b as formatJson, c as visitBlock, f as execute, i as runTypecheck, l as visitExpression, m as formatExecuteOutput, n as parseGameOutput, o as formatJobSummary, r as writeGameOutput, t as formatGameOutputNotice, u as visitStatement, x as writeJsonFile, z as buildJestArgv } from "./game-output-BtWj32M8.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, visitBlock, visitExpression, visitStatement, writeGameOutput, writeJsonFile };
|
package/dist/sea/jest-roblox
CHANGED
|
Binary file
|
package/dist/sea-entry.cjs
CHANGED
|
@@ -7633,7 +7633,7 @@ function C$5({ force: e } = {}) {
|
|
|
7633
7633
|
var y$4 = C$5();
|
|
7634
7634
|
//#endregion
|
|
7635
7635
|
//#region package.json
|
|
7636
|
-
var version = "0.2.
|
|
7636
|
+
var version = "0.2.4";
|
|
7637
7637
|
//#endregion
|
|
7638
7638
|
//#region node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/constants.js
|
|
7639
7639
|
var require_constants$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isentinel/jest-roblox",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Jest-compatible CLI for running roblox-ts tests via Roblox Open Cloud",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jest",
|
|
@@ -54,10 +54,7 @@
|
|
|
54
54
|
"picomatch": "4.0.4",
|
|
55
55
|
"std-env": "4.0.0",
|
|
56
56
|
"tinyrainbow": "3.1.0",
|
|
57
|
-
"ws": "8.20.0"
|
|
58
|
-
"@isentinel/luau-ast": "0.1.0",
|
|
59
|
-
"@isentinel/rojo-utils": "0.1.0",
|
|
60
|
-
"@isentinel/roblox-runner": "0.1.0"
|
|
57
|
+
"ws": "8.20.0"
|
|
61
58
|
},
|
|
62
59
|
"devDependencies": {
|
|
63
60
|
"@isentinel/eslint-config": "5.0.0-beta.9",
|
|
@@ -88,7 +85,10 @@
|
|
|
88
85
|
"tsdown": "0.21.7",
|
|
89
86
|
"type-fest": "5.5.0",
|
|
90
87
|
"typescript": "5.9.3",
|
|
91
|
-
"vitest": "4.1.2"
|
|
88
|
+
"vitest": "4.1.2",
|
|
89
|
+
"@isentinel/luau-ast": "0.1.0",
|
|
90
|
+
"@isentinel/rojo-utils": "0.1.0",
|
|
91
|
+
"@isentinel/roblox-runner": "0.1.0"
|
|
92
92
|
},
|
|
93
93
|
"engines": {
|
|
94
94
|
"node": ">=24.10.0"
|
|
Binary file
|
|
@@ -7,6 +7,18 @@ if not RunService:IsRunning() then
|
|
|
7
7
|
return
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
local function endWithError(err: string): ()
|
|
11
|
+
local ok, endErr = pcall(function()
|
|
12
|
+
StudioTestService:EndTest({
|
|
13
|
+
jestOutput = HttpService:JSONEncode({ success = false, err = err }),
|
|
14
|
+
gameOutput = "[]",
|
|
15
|
+
})
|
|
16
|
+
end)
|
|
17
|
+
if not ok then
|
|
18
|
+
warn("[jest-roblox] EndTest failed: " .. tostring(endErr))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
10
22
|
local testArgs = StudioTestService:GetTestArgs()
|
|
11
23
|
if testArgs == nil then
|
|
12
24
|
for _ = 1, 50 do
|
|
@@ -18,29 +30,43 @@ if testArgs == nil then
|
|
|
18
30
|
end
|
|
19
31
|
end
|
|
20
32
|
|
|
21
|
-
if testArgs == nil
|
|
33
|
+
if testArgs == nil then
|
|
34
|
+
endWithError("StudioTestService:GetTestArgs() returned nil after 50 polls (5s)")
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if testArgs.config == nil or testArgs.config.configs == nil then
|
|
39
|
+
local keysOk, keys = pcall(HttpService.JSONEncode, HttpService, testArgs)
|
|
40
|
+
endWithError(
|
|
41
|
+
"testArgs.config.configs is nil. Keys: " .. (if keysOk then keys else "<unencodable>")
|
|
42
|
+
)
|
|
22
43
|
return
|
|
23
44
|
end
|
|
24
45
|
|
|
46
|
+
local configs = testArgs.config.configs
|
|
47
|
+
|
|
25
48
|
local loadStringEnabled = pcall(function()
|
|
26
49
|
loadstring("return true")
|
|
27
50
|
end)
|
|
28
51
|
|
|
29
52
|
if not loadStringEnabled then
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
endWithError("LoadString must be enabled in ServerScriptService to run tests")
|
|
54
|
+
return
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
local requireOk, Runner = pcall(require, script.Parent.shared.runner)
|
|
58
|
+
if not requireOk then
|
|
59
|
+
endWithError("Failed to require shared.runner: " .. tostring(Runner))
|
|
37
60
|
return
|
|
38
61
|
end
|
|
39
62
|
|
|
40
|
-
local
|
|
41
|
-
|
|
63
|
+
local runOk, entriesOrErr = pcall(Runner.runProjects, script, configs)
|
|
64
|
+
if not runOk then
|
|
65
|
+
endWithError("Runner.runProjects threw: " .. tostring(entriesOrErr))
|
|
66
|
+
return
|
|
67
|
+
end
|
|
42
68
|
|
|
43
69
|
StudioTestService:EndTest({
|
|
44
|
-
jestOutput = HttpService:JSONEncode({ entries =
|
|
70
|
+
jestOutput = HttpService:JSONEncode({ entries = entriesOrErr }),
|
|
45
71
|
gameOutput = "[]",
|
|
46
72
|
})
|