@isentinel/jest-roblox 0.0.6 → 0.0.8
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.d.mts
CHANGED
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as createStudioBackend, O as LuauScriptError, T as createOpenCloudBackend, a as formatAnnotations, c as execute, d as writeJsonFile, g as loadConfig, h as rojoProjectSchema, i as runTypecheck, l as loadCoverageManifest, n as parseGameOutput, o as formatJobSummary, r as writeGameOutput, s as resolveGitHubActionsOptions, t as formatGameOutputNotice, x as isValidBackend, y as VALID_BACKENDS } from "./game-output-M8du29nj.mjs";
|
|
2
2
|
import assert from "node:assert";
|
|
3
|
+
import * as fs from "node:fs";
|
|
3
4
|
import * as path$1 from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
5
6
|
import { parseArgs as parseArgs$1 } from "node:util";
|
|
6
7
|
import color from "tinyrainbow";
|
|
7
8
|
import { WebSocketServer } from "ws";
|
|
8
9
|
import { type } from "arktype";
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
10
|
import * as os from "node:os";
|
|
11
11
|
import { Buffer } from "node:buffer";
|
|
12
12
|
import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
@@ -18,7 +18,7 @@ import istanbulReport from "istanbul-lib-report";
|
|
|
18
18
|
import istanbulReports from "istanbul-reports";
|
|
19
19
|
|
|
20
20
|
//#region package.json
|
|
21
|
-
var version = "0.0.
|
|
21
|
+
var version = "0.0.8";
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region src/backends/auto.ts
|
|
@@ -41,7 +41,7 @@ var StudioWithFallback = class {
|
|
|
41
41
|
};
|
|
42
42
|
function isStudioBusyError(error) {
|
|
43
43
|
if (error instanceof LuauScriptError) return /previous call to start play session/i.test(error.message);
|
|
44
|
-
return error.code === "EADDRINUSE";
|
|
44
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
45
45
|
}
|
|
46
46
|
async function probeStudioPlugin(port, timeoutMs, createServer = (wsPort) => {
|
|
47
47
|
return new WebSocketServer({ port: wsPort });
|
|
@@ -139,15 +139,15 @@ function mapCoverageToTypeScript(coverageData, manifest) {
|
|
|
139
139
|
return buildResult(pendingStatements, pendingFunctions, pendingBranches);
|
|
140
140
|
}
|
|
141
141
|
function loadFileResources(record) {
|
|
142
|
-
let
|
|
142
|
+
let coverageMapRaw;
|
|
143
143
|
let sourceMapRaw;
|
|
144
144
|
try {
|
|
145
|
-
|
|
145
|
+
coverageMapRaw = fs.readFileSync(record.coverageMapPath, "utf-8");
|
|
146
146
|
sourceMapRaw = fs.readFileSync(record.sourceMapPath, "utf-8");
|
|
147
147
|
} catch {
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
|
-
const parsed = coverageMapSchema(JSON.parse(
|
|
150
|
+
const parsed = coverageMapSchema(JSON.parse(coverageMapRaw));
|
|
151
151
|
if (parsed instanceof type.errors) return;
|
|
152
152
|
return {
|
|
153
153
|
coverageMap: parsed,
|
|
@@ -278,10 +278,10 @@ function mapFileFunctions(resources, fileCoverage, pendingFunctions, resolvedTsP
|
|
|
278
278
|
function mapBranchArmLocations(traceMap, rawLocations) {
|
|
279
279
|
const mappedLocations = [];
|
|
280
280
|
let tsPath;
|
|
281
|
-
for (const
|
|
282
|
-
const
|
|
283
|
-
if (
|
|
284
|
-
const mapped = mapStatement(traceMap,
|
|
281
|
+
for (const rawLocation of rawLocations) {
|
|
282
|
+
const location = spanSchema(rawLocation);
|
|
283
|
+
if (location instanceof type.errors) return;
|
|
284
|
+
const mapped = mapStatement(traceMap, location);
|
|
285
285
|
if (mapped === void 0) return;
|
|
286
286
|
if (tsPath === void 0) tsPath = mapped.start.source;
|
|
287
287
|
else if (tsPath !== mapped.start.source) return;
|
|
@@ -315,14 +315,14 @@ function mapFileBranches(resources, fileCoverage, pendingBranches) {
|
|
|
315
315
|
fileBranches = [];
|
|
316
316
|
pendingBranches.set(result.tsPath, fileBranches);
|
|
317
317
|
}
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
assert(
|
|
318
|
+
const firstLocation = result.locations[0];
|
|
319
|
+
const lastLocation = result.locations[result.locations.length - 1];
|
|
320
|
+
assert(firstLocation !== void 0 && lastLocation !== void 0, "Branch locations must not be empty after successful mapping");
|
|
321
321
|
fileBranches.push({
|
|
322
322
|
armHitCounts: entry.locations.map((_, index) => armHitCounts[index] ?? 0),
|
|
323
323
|
loc: {
|
|
324
|
-
end:
|
|
325
|
-
start:
|
|
324
|
+
end: lastLocation.end,
|
|
325
|
+
start: firstLocation.start
|
|
326
326
|
},
|
|
327
327
|
locations: result.locations,
|
|
328
328
|
type: entry.type
|
|
@@ -877,17 +877,17 @@ function extractFunctionName(node) {
|
|
|
877
877
|
}
|
|
878
878
|
|
|
879
879
|
//#endregion
|
|
880
|
-
//#region src/coverage/
|
|
880
|
+
//#region src/coverage/coverage-map-builder.ts
|
|
881
881
|
function buildCoverageMap$1(result) {
|
|
882
882
|
const statementMap = {};
|
|
883
|
-
for (const
|
|
883
|
+
for (const statement of result.statements) statementMap[String(statement.index)] = {
|
|
884
884
|
end: {
|
|
885
|
-
column:
|
|
886
|
-
line:
|
|
885
|
+
column: statement.location.endcolumn,
|
|
886
|
+
line: statement.location.endline
|
|
887
887
|
},
|
|
888
888
|
start: {
|
|
889
|
-
column:
|
|
890
|
-
line:
|
|
889
|
+
column: statement.location.begincolumn,
|
|
890
|
+
line: statement.location.beginline
|
|
891
891
|
}
|
|
892
892
|
};
|
|
893
893
|
const functionMap = {};
|
|
@@ -980,14 +980,13 @@ function collectProbes(result) {
|
|
|
980
980
|
}
|
|
981
981
|
/** Mutates `mutableLines` in place, inserting probe text at each probe's position. */
|
|
982
982
|
function applyProbes(mutableLines, probes) {
|
|
983
|
-
for (const
|
|
984
|
-
const lineIndex =
|
|
983
|
+
for (const { column, line: probeLine, text } of probes) {
|
|
984
|
+
const lineIndex = probeLine - 1;
|
|
985
985
|
const line = mutableLines[lineIndex];
|
|
986
|
-
assert(line !== void 0, `Invalid probe line number: ${
|
|
987
|
-
const
|
|
988
|
-
const
|
|
989
|
-
|
|
990
|
-
mutableLines[lineIndex] = before + probe.text + after;
|
|
986
|
+
assert(line !== void 0, `Invalid probe line number: ${probeLine}`);
|
|
987
|
+
const before = line.slice(0, column - 1);
|
|
988
|
+
const after = line.slice(column - 1);
|
|
989
|
+
mutableLines[lineIndex] = before + text + after;
|
|
991
990
|
}
|
|
992
991
|
}
|
|
993
992
|
function extractModeDirective(lines) {
|
|
@@ -1100,9 +1099,9 @@ function instrumentRoot(options) {
|
|
|
1100
1099
|
const source = fs.readFileSync(path$1.resolve(originalLuauPath), "utf-8");
|
|
1101
1100
|
const collectorResult = collectCoverage(ast);
|
|
1102
1101
|
const instrumentedSource = insertProbes(source, collectorResult, fileKey);
|
|
1103
|
-
const
|
|
1102
|
+
const coverageMap = buildCoverageMap$1(collectorResult);
|
|
1104
1103
|
fs.writeFileSync(path$1.join(shadowDir, relativePath), instrumentedSource);
|
|
1105
|
-
fs.writeFileSync(coverageMapOutputPath, JSON.stringify(
|
|
1104
|
+
fs.writeFileSync(coverageMapOutputPath, JSON.stringify(coverageMap, void 0, " "));
|
|
1106
1105
|
files[fileKey] = {
|
|
1107
1106
|
key: fileKey,
|
|
1108
1107
|
branchCount: collectorResult.branches.length,
|
|
@@ -1232,7 +1231,9 @@ function prepareCoverage(config) {
|
|
|
1232
1231
|
};
|
|
1233
1232
|
fs.mkdirSync(path$1.dirname(manifestPath), { recursive: true });
|
|
1234
1233
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, void 0, " "));
|
|
1235
|
-
const
|
|
1234
|
+
const rojoProjectRaw = rojoProjectSchema(JSON.parse(fs.readFileSync(rojoProjectPath, "utf-8")));
|
|
1235
|
+
if (rojoProjectRaw instanceof type.errors) throw new Error(`Malformed Rojo project JSON: ${rojoProjectRaw.toString()}`);
|
|
1236
|
+
const rewritten = rewriteRojoProject(rojoProjectRaw, {
|
|
1236
1237
|
projectRelocation: path$1.relative(COVERAGE_DIR, path$1.dirname(rojoProjectPath)).replaceAll("\\", "/"),
|
|
1237
1238
|
roots
|
|
1238
1239
|
});
|
|
@@ -1433,6 +1434,7 @@ Options:
|
|
|
1433
1434
|
--coverage Enable coverage collection
|
|
1434
1435
|
--coverageDirectory <path> Directory for coverage output (default: coverage)
|
|
1435
1436
|
--coverageReporters <r...> Coverage reporters (default: text, lcov)
|
|
1437
|
+
--formatters <name...> Output formatters (default: default; auto: github-actions)
|
|
1436
1438
|
--no-cache Force re-upload place file (skip cache)
|
|
1437
1439
|
--pollInterval <ms> Open Cloud poll interval in ms (default: 500)
|
|
1438
1440
|
--projects <path...> DataModel paths to search for tests
|
|
@@ -1475,6 +1477,10 @@ function parseArgs(args) {
|
|
|
1475
1477
|
multiple: true,
|
|
1476
1478
|
type: "string"
|
|
1477
1479
|
},
|
|
1480
|
+
"formatters": {
|
|
1481
|
+
multiple: true,
|
|
1482
|
+
type: "string"
|
|
1483
|
+
},
|
|
1478
1484
|
"gameOutput": { type: "string" },
|
|
1479
1485
|
"help": {
|
|
1480
1486
|
default: false,
|
|
@@ -1539,6 +1545,7 @@ function parseArgs(args) {
|
|
|
1539
1545
|
coverageDirectory: values.coverageDirectory,
|
|
1540
1546
|
coverageReporters: values.coverageReporters,
|
|
1541
1547
|
files: positionals.length > 0 ? positionals : void 0,
|
|
1548
|
+
formatters: values.formatters,
|
|
1542
1549
|
gameOutput: values.gameOutput,
|
|
1543
1550
|
help: values.help,
|
|
1544
1551
|
json: values.json,
|
|
@@ -1630,6 +1637,30 @@ function processCoverage(config, coverageData) {
|
|
|
1630
1637
|
}
|
|
1631
1638
|
return true;
|
|
1632
1639
|
}
|
|
1640
|
+
function findFormatterOptions(formatters, name) {
|
|
1641
|
+
for (const entry of formatters) {
|
|
1642
|
+
if (entry === name) return {};
|
|
1643
|
+
if (Array.isArray(entry) && entry[0] === name) return entry[1];
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
function runGitHubActionsFormatter(config, result, sourceMapper) {
|
|
1647
|
+
const userOptions = findFormatterOptions(config.formatters, "github-actions");
|
|
1648
|
+
if (userOptions === void 0) return;
|
|
1649
|
+
const typedOptions = userOptions;
|
|
1650
|
+
const options = resolveGitHubActionsOptions(typedOptions, sourceMapper);
|
|
1651
|
+
if (typedOptions.displayAnnotations !== false) {
|
|
1652
|
+
const annotations = formatAnnotations(result, options);
|
|
1653
|
+
if (annotations !== "") process.stderr.write(`${annotations}\n`);
|
|
1654
|
+
}
|
|
1655
|
+
const { jobSummary } = typedOptions;
|
|
1656
|
+
if (jobSummary?.enabled !== false) {
|
|
1657
|
+
const outputPath = jobSummary?.outputPath ?? process.env["GITHUB_STEP_SUMMARY"];
|
|
1658
|
+
if (outputPath !== void 0) {
|
|
1659
|
+
const summary = formatJobSummary(result, options);
|
|
1660
|
+
fs.appendFileSync(outputPath, summary);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1633
1664
|
async function outputResults(config, typecheckResult, runtimeResult) {
|
|
1634
1665
|
const mergedResult = mergeResults(typecheckResult, runtimeResult?.result);
|
|
1635
1666
|
if (runtimeResult !== void 0 && runtimeResult.output !== "") console.log(runtimeResult.output);
|
|
@@ -1637,6 +1668,7 @@ async function outputResults(config, typecheckResult, runtimeResult) {
|
|
|
1637
1668
|
if (typecheckResult !== void 0 && !config.silent) printTypecheckSummary(typecheckResult);
|
|
1638
1669
|
if (config.outputFile !== void 0) await writeJsonFile(mergedResult, config.outputFile);
|
|
1639
1670
|
if (runtimeResult !== void 0) writeGameOutputIfConfigured(config, runtimeResult.gameOutput, { hintsShown: !mergedResult.success });
|
|
1671
|
+
runGitHubActionsFormatter(config, mergedResult, runtimeResult?.sourceMapper);
|
|
1640
1672
|
const passed = mergedResult.success && coveragePassed;
|
|
1641
1673
|
if (!config.silent && config.collectCoverage) printFinalStatus(passed);
|
|
1642
1674
|
return passed ? 0 : 1;
|
|
@@ -1725,6 +1757,11 @@ function validateBackend(value) {
|
|
|
1725
1757
|
function getLuauErrorHint(message) {
|
|
1726
1758
|
for (const [pattern, hint] of LUAU_ERROR_HINTS) if (pattern.test(message)) return hint;
|
|
1727
1759
|
}
|
|
1760
|
+
function resolveFormatters(cli, config) {
|
|
1761
|
+
const explicit = cli.formatters ?? config.formatters;
|
|
1762
|
+
if (explicit !== void 0) return explicit;
|
|
1763
|
+
return process.env["GITHUB_ACTIONS"] === "true" ? ["default", "github-actions"] : ["default"];
|
|
1764
|
+
}
|
|
1728
1765
|
function mergeCliWithConfig(cli, config) {
|
|
1729
1766
|
return {
|
|
1730
1767
|
...config,
|
|
@@ -1736,6 +1773,7 @@ function mergeCliWithConfig(cli, config) {
|
|
|
1736
1773
|
compactMaxFailures: cli.compactMaxFailures ?? config.compactMaxFailures,
|
|
1737
1774
|
coverageDirectory: cli.coverageDirectory ?? config.coverageDirectory,
|
|
1738
1775
|
coverageReporters: cli.coverageReporters ?? config.coverageReporters,
|
|
1776
|
+
formatters: resolveFormatters(cli, config),
|
|
1739
1777
|
gameOutput: cli.gameOutput ?? config.gameOutput,
|
|
1740
1778
|
json: cli.json ?? config.json,
|
|
1741
1779
|
outputFile: cli.outputFile ?? config.outputFile,
|
|
@@ -1763,6 +1801,7 @@ function mergeResults(typecheck, runtime) {
|
|
|
1763
1801
|
numFailedTests: typecheck.numFailedTests + runtime.numFailedTests,
|
|
1764
1802
|
numPassedTests: typecheck.numPassedTests + runtime.numPassedTests,
|
|
1765
1803
|
numPendingTests: typecheck.numPendingTests + runtime.numPendingTests,
|
|
1804
|
+
numTodoTests: (typecheck.numTodoTests ?? 0) + (runtime.numTodoTests ?? 0),
|
|
1766
1805
|
numTotalTests: typecheck.numTotalTests + runtime.numTotalTests,
|
|
1767
1806
|
startTime: Math.min(typecheck.startTime, runtime.startTime),
|
|
1768
1807
|
success: typecheck.success && runtime.success,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
2
4
|
import * as path$1 from "node:path";
|
|
3
5
|
import path from "node:path";
|
|
4
6
|
import process from "node:process";
|
|
5
7
|
import color from "tinyrainbow";
|
|
6
8
|
import { WebSocketServer } from "ws";
|
|
7
9
|
import { type } from "arktype";
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import * as crypto from "node:crypto";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
@@ -82,15 +82,15 @@ function parseJestOutput(output) {
|
|
|
82
82
|
};
|
|
83
83
|
} catch {}
|
|
84
84
|
}
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
return { result: validateJestResult(JSON.parse(
|
|
85
|
+
const jsonString = extractJsonFromOutput(output);
|
|
86
|
+
if (jsonString === void 0) throw new Error(`No valid Jest result JSON found in output, output was:\n${output}`);
|
|
87
|
+
return { result: validateJestResult(JSON.parse(jsonString)) };
|
|
88
88
|
}
|
|
89
89
|
function countBraces(line) {
|
|
90
90
|
let count = 0;
|
|
91
|
-
for (const
|
|
92
|
-
if (
|
|
93
|
-
if (
|
|
91
|
+
for (const character of line) {
|
|
92
|
+
if (character === "{") count++;
|
|
93
|
+
if (character === "}") count--;
|
|
94
94
|
}
|
|
95
95
|
return count;
|
|
96
96
|
}
|
|
@@ -311,6 +311,12 @@ function createFetchClient(defaultHeaders) {
|
|
|
311
311
|
const OPEN_CLOUD_BASE_URL = "https://apis.roblox.com";
|
|
312
312
|
const RATE_LIMIT_DEFAULT_WAIT_MS = 5e3;
|
|
313
313
|
const MAX_RATE_LIMIT_RETRIES = 5;
|
|
314
|
+
const taskResponse = type({ path: "string" });
|
|
315
|
+
const taskStatusResponse = type({
|
|
316
|
+
"error?": { "message?": "string" },
|
|
317
|
+
"output?": { "results?": "string[]" },
|
|
318
|
+
"state": "'CANCELLED' | 'COMPLETE' | 'FAILED' | 'PROCESSING'"
|
|
319
|
+
});
|
|
314
320
|
var OpenCloudBackend = class {
|
|
315
321
|
credentials;
|
|
316
322
|
http;
|
|
@@ -319,7 +325,7 @@ var OpenCloudBackend = class {
|
|
|
319
325
|
constructor(credentials, options) {
|
|
320
326
|
this.credentials = credentials;
|
|
321
327
|
this.http = options?.http ?? createFetchClient({ "x-api-key": credentials.apiKey });
|
|
322
|
-
this.readFile = options?.readFile ?? ((
|
|
328
|
+
this.readFile = options?.readFile ?? ((filePath) => fs.readFileSync(filePath));
|
|
323
329
|
this.sleepFn = options?.sleep ?? (async (ms) => {
|
|
324
330
|
return new Promise((resolve) => {
|
|
325
331
|
setTimeout(resolve, ms);
|
|
@@ -334,14 +340,15 @@ var OpenCloudBackend = class {
|
|
|
334
340
|
const placeData = this.readFile(placeFilePath);
|
|
335
341
|
const fileHash = hashBuffer(placeData);
|
|
336
342
|
const cacheKey = getCacheKey(this.credentials.universeId, this.credentials.placeId);
|
|
337
|
-
let uploadCached = false;
|
|
338
343
|
const cache = readCache(cacheFilePath);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
const uploadCached = await this.uploadOrReuseCached({
|
|
345
|
+
cache,
|
|
346
|
+
cacheEnabled: options.config.cache,
|
|
347
|
+
cacheFilePath,
|
|
348
|
+
cacheKey,
|
|
349
|
+
fileHash,
|
|
350
|
+
placeData
|
|
351
|
+
});
|
|
345
352
|
const uploadMs = Date.now() - uploadStart;
|
|
346
353
|
const executionStart = Date.now();
|
|
347
354
|
const taskPath = await this.createExecutionTask(options);
|
|
@@ -369,7 +376,7 @@ var OpenCloudBackend = class {
|
|
|
369
376
|
timeout: `${Math.floor(options.config.timeout / 1e3)}s`
|
|
370
377
|
} });
|
|
371
378
|
if (!response.ok) throw new Error(`Failed to create execution task: ${response.status}`);
|
|
372
|
-
return response.body.path;
|
|
379
|
+
return taskResponse.assert(response.body).path;
|
|
373
380
|
}
|
|
374
381
|
async pollForCompletion(taskPath, timeoutMs, pollIntervalMs) {
|
|
375
382
|
const url = `${OPEN_CLOUD_BASE_URL}/cloud/v2/${taskPath}`;
|
|
@@ -385,7 +392,7 @@ var OpenCloudBackend = class {
|
|
|
385
392
|
continue;
|
|
386
393
|
}
|
|
387
394
|
if (!response.ok) throw new Error(`Failed to poll task: ${response.status}`);
|
|
388
|
-
const body = response.body;
|
|
395
|
+
const body = taskStatusResponse.assert(response.body);
|
|
389
396
|
switch (body.state) {
|
|
390
397
|
case "COMPLETE": {
|
|
391
398
|
const value = body.output?.results?.[0];
|
|
@@ -404,6 +411,13 @@ var OpenCloudBackend = class {
|
|
|
404
411
|
}
|
|
405
412
|
throw new Error("Execution timed out");
|
|
406
413
|
}
|
|
414
|
+
async uploadOrReuseCached({ cache, cacheEnabled, cacheFilePath, cacheKey, fileHash, placeData }) {
|
|
415
|
+
if (cacheEnabled && isUploaded(cache, cacheKey, fileHash)) return true;
|
|
416
|
+
await this.uploadPlaceData(placeData);
|
|
417
|
+
markUploaded(cache, cacheKey, fileHash);
|
|
418
|
+
writeCache(cacheFilePath, cache);
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
407
421
|
async uploadPlaceData(placeData) {
|
|
408
422
|
const url = `${OPEN_CLOUD_BASE_URL}/universes/v1/${this.credentials.universeId}/places/${this.credentials.placeId}/versions?versionType=Saved`;
|
|
409
423
|
const response = await this.http.request("POST", url, {
|
|
@@ -415,10 +429,10 @@ var OpenCloudBackend = class {
|
|
|
415
429
|
};
|
|
416
430
|
function createOpenCloudBackend() {
|
|
417
431
|
const apiKey = process.env["ROBLOX_OPEN_CLOUD_API_KEY"];
|
|
418
|
-
const universeId = process.env["ROBLOX_UNIVERSE_ID"];
|
|
419
|
-
const placeId = process.env["ROBLOX_PLACE_ID"];
|
|
420
432
|
if (apiKey === void 0) throw new Error("ROBLOX_OPEN_CLOUD_API_KEY environment variable is required");
|
|
433
|
+
const universeId = process.env["ROBLOX_UNIVERSE_ID"];
|
|
421
434
|
if (universeId === void 0) throw new Error("ROBLOX_UNIVERSE_ID environment variable is required");
|
|
435
|
+
const placeId = process.env["ROBLOX_PLACE_ID"];
|
|
422
436
|
if (placeId === void 0) throw new Error("ROBLOX_PLACE_ID environment variable is required");
|
|
423
437
|
return new OpenCloudBackend({
|
|
424
438
|
apiKey,
|
|
@@ -436,6 +450,7 @@ function parseRetryAfter(headers) {
|
|
|
436
450
|
|
|
437
451
|
//#endregion
|
|
438
452
|
//#region src/backends/studio.ts
|
|
453
|
+
const DEFAULT_STUDIO_TIMEOUT = 3e5;
|
|
439
454
|
const pluginMessageSchema = type({
|
|
440
455
|
"gameOutput?": "string",
|
|
441
456
|
"jestOutput": "string",
|
|
@@ -449,7 +464,7 @@ var StudioBackend = class {
|
|
|
449
464
|
preConnected;
|
|
450
465
|
constructor(options) {
|
|
451
466
|
this.port = options.port;
|
|
452
|
-
this.timeout = options.timeout ??
|
|
467
|
+
this.timeout = options.timeout ?? DEFAULT_STUDIO_TIMEOUT;
|
|
453
468
|
this.createServer = options.createServer ?? ((port) => new WebSocketServer({ port }));
|
|
454
469
|
this.preConnected = options.preConnected;
|
|
455
470
|
}
|
|
@@ -573,7 +588,11 @@ const DEFAULT_CONFIG = {
|
|
|
573
588
|
"**/*.test.lua",
|
|
574
589
|
"**/*.test.luau"
|
|
575
590
|
],
|
|
576
|
-
testPathIgnorePatterns: [
|
|
591
|
+
testPathIgnorePatterns: [
|
|
592
|
+
"/node_modules/",
|
|
593
|
+
"/dist/",
|
|
594
|
+
"/out/"
|
|
595
|
+
],
|
|
577
596
|
timeout: 3e5,
|
|
578
597
|
typecheck: false,
|
|
579
598
|
typecheckOnly: false,
|
|
@@ -630,6 +649,14 @@ async function loadConfig$1(configPath, cwd = process.cwd()) {
|
|
|
630
649
|
return resolveConfig(result.config);
|
|
631
650
|
}
|
|
632
651
|
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/types/rojo.ts
|
|
654
|
+
const rojoProjectSchema = type({
|
|
655
|
+
"name": "string",
|
|
656
|
+
"servePort?": "number.integer",
|
|
657
|
+
"tree": "object"
|
|
658
|
+
}).as();
|
|
659
|
+
|
|
633
660
|
//#endregion
|
|
634
661
|
//#region src/utils/normalize-windows-path.ts
|
|
635
662
|
const DRIVE_LETTER_START_REGEX = /^[A-Za-z]:\//;
|
|
@@ -666,7 +693,7 @@ function createPathResolver(rojoProject, config) {
|
|
|
666
693
|
if (key.startsWith("$") || typeof value !== "object") continue;
|
|
667
694
|
const dataModelPath = prefix ? `${prefix}.${key}` : key;
|
|
668
695
|
const node = value;
|
|
669
|
-
if (typeof node
|
|
696
|
+
if (typeof node.$path === "string") mappings.set(dataModelPath, node.$path);
|
|
670
697
|
walkTree(node, dataModelPath);
|
|
671
698
|
}
|
|
672
699
|
}
|
|
@@ -876,7 +903,7 @@ function hasExecError(file) {
|
|
|
876
903
|
|
|
877
904
|
//#endregion
|
|
878
905
|
//#region src/utils/banner.ts
|
|
879
|
-
const SEPARATOR = "⎯";
|
|
906
|
+
const SEPARATOR$1 = "⎯";
|
|
880
907
|
const levelStyles = {
|
|
881
908
|
error: {
|
|
882
909
|
badge: (text) => color.bgRed(color.white(color.bold(text))),
|
|
@@ -895,7 +922,7 @@ function formatBannerBar({ level, termWidth, title }) {
|
|
|
895
922
|
const remaining = width - badgeText.length;
|
|
896
923
|
const leftWidth = Math.max(1, Math.floor(remaining / 2));
|
|
897
924
|
const rightWidth = Math.max(1, remaining - leftWidth);
|
|
898
|
-
return `${styles.separator(SEPARATOR.repeat(leftWidth))}${badge}${styles.separator(SEPARATOR.repeat(rightWidth))}`;
|
|
925
|
+
return `${styles.separator(SEPARATOR$1.repeat(leftWidth))}${badge}${styles.separator(SEPARATOR$1.repeat(rightWidth))}`;
|
|
899
926
|
}
|
|
900
927
|
function formatBanner({ body, level, termWidth, title }) {
|
|
901
928
|
const width = termWidth ?? getDefaultWidth();
|
|
@@ -905,7 +932,7 @@ function formatBanner({ body, level, termWidth, title }) {
|
|
|
905
932
|
termWidth: width,
|
|
906
933
|
title
|
|
907
934
|
});
|
|
908
|
-
const closing = styles.separator(SEPARATOR.repeat(width));
|
|
935
|
+
const closing = styles.separator(SEPARATOR$1.repeat(width));
|
|
909
936
|
return `\n${header}\n${body.length > 0 ? `\n${body.join("\n")}\n` : ""}\n${closing}\n\n`;
|
|
910
937
|
}
|
|
911
938
|
function getDefaultWidth() {
|
|
@@ -1106,27 +1133,27 @@ function cleanExecErrorMessage(raw) {
|
|
|
1106
1133
|
}
|
|
1107
1134
|
function formatSourceSnippet(snippet, filePath, options) {
|
|
1108
1135
|
const useColor = options?.useColor ?? true;
|
|
1109
|
-
const
|
|
1136
|
+
const styles = options?.styles ?? createStyles(useColor);
|
|
1110
1137
|
const language = options?.language;
|
|
1111
1138
|
const lines = [];
|
|
1112
1139
|
const indent = " ";
|
|
1113
1140
|
const location = snippet.column !== void 0 ? `${filePath}:${snippet.failureLine}:${snippet.column}` : `${filePath}:${snippet.failureLine}`;
|
|
1114
|
-
const langSuffix = language !== void 0 ?
|
|
1115
|
-
lines.push(
|
|
1141
|
+
const langSuffix = language !== void 0 ? styles.dim(` (${language})`) : "";
|
|
1142
|
+
lines.push(styles.location(` ❯ ${location}`) + langSuffix);
|
|
1116
1143
|
const maxLineNumber = Math.max(...snippet.lines.map((line) => line.num));
|
|
1117
1144
|
const padding = String(maxLineNumber).length;
|
|
1118
1145
|
for (const line of snippet.lines) {
|
|
1119
1146
|
const prefix = `${String(line.num).padStart(padding)}|`;
|
|
1120
1147
|
const highlighted = highlightSyntax(filePath, expandTabs(line.content), useColor);
|
|
1121
1148
|
if (line.num === snippet.failureLine) {
|
|
1122
|
-
lines.push(`${indent}${
|
|
1149
|
+
lines.push(`${indent}${styles.lineNumber(prefix)} ${highlighted}`);
|
|
1123
1150
|
if (snippet.column !== void 0) {
|
|
1124
1151
|
const beforeColumn = expandTabs(line.content.slice(0, snippet.column - 1));
|
|
1125
1152
|
const caretGutter = `${" ".repeat(padding)}|`;
|
|
1126
|
-
const gutterPrefix =
|
|
1127
|
-
lines.push(`${indent}${gutterPrefix} ${" ".repeat(beforeColumn.length)}${
|
|
1153
|
+
const gutterPrefix = styles.lineNumber(caretGutter);
|
|
1154
|
+
lines.push(`${indent}${gutterPrefix} ${" ".repeat(beforeColumn.length)}${styles.status.fail("^")}`);
|
|
1128
1155
|
}
|
|
1129
|
-
} else lines.push(`${indent}${
|
|
1156
|
+
} else lines.push(`${indent}${styles.lineNumber(prefix)} ${highlighted}`);
|
|
1130
1157
|
}
|
|
1131
1158
|
return lines.join("\n");
|
|
1132
1159
|
}
|
|
@@ -1305,27 +1332,27 @@ function highlightSyntax(filePath, code, useColor) {
|
|
|
1305
1332
|
if (!useColor) return code;
|
|
1306
1333
|
return highlightCode(filePath, code);
|
|
1307
1334
|
}
|
|
1308
|
-
function formatDiffBlock(parsed,
|
|
1335
|
+
function formatDiffBlock(parsed, styles) {
|
|
1309
1336
|
if (parsed.snapshotDiff !== void 0) {
|
|
1310
1337
|
const lines = [""];
|
|
1311
|
-
for (const diffLine of parsed.snapshotDiff.split("\n")) if (diffLine.startsWith("- ")) lines.push(
|
|
1312
|
-
else if (diffLine.startsWith("+ ")) lines.push(
|
|
1313
|
-
else lines.push(
|
|
1338
|
+
for (const diffLine of parsed.snapshotDiff.split("\n")) if (diffLine.startsWith("- ")) lines.push(styles.diff.expected(diffLine));
|
|
1339
|
+
else if (diffLine.startsWith("+ ")) lines.push(styles.diff.received(diffLine));
|
|
1340
|
+
else lines.push(styles.dim(diffLine));
|
|
1314
1341
|
return lines;
|
|
1315
1342
|
}
|
|
1316
1343
|
if (parsed.expected !== void 0 && parsed.received !== void 0) return [
|
|
1317
1344
|
"",
|
|
1318
|
-
|
|
1319
|
-
|
|
1345
|
+
styles.diff.expected("- Expected"),
|
|
1346
|
+
styles.diff.received("+ Received"),
|
|
1320
1347
|
"",
|
|
1321
|
-
|
|
1322
|
-
|
|
1348
|
+
styles.diff.expected(`- ${parsed.expected}`),
|
|
1349
|
+
styles.diff.received(`+ ${parsed.received}`)
|
|
1323
1350
|
];
|
|
1324
1351
|
return [];
|
|
1325
1352
|
}
|
|
1326
|
-
function formatErrorLine(parsed,
|
|
1327
|
-
if (useColor && parsed.message.startsWith("Error:")) return
|
|
1328
|
-
return
|
|
1353
|
+
function formatErrorLine(parsed, styles, useColor) {
|
|
1354
|
+
if (useColor && parsed.message.startsWith("Error:")) return styles.status.fail(color.bold("Error:") + parsed.message.slice(6));
|
|
1355
|
+
return styles.status.fail(parsed.message);
|
|
1329
1356
|
}
|
|
1330
1357
|
function formatFallbackSnippet(message, styles, useColor) {
|
|
1331
1358
|
const location = parseSourceLocation(message);
|
|
@@ -1342,7 +1369,7 @@ function formatFallbackSnippet(message, styles, useColor) {
|
|
|
1342
1369
|
useColor
|
|
1343
1370
|
})];
|
|
1344
1371
|
}
|
|
1345
|
-
function formatMappedLocationSnippets(loc, showLuau,
|
|
1372
|
+
function formatMappedLocationSnippets(loc, showLuau, styles, useColor) {
|
|
1346
1373
|
const snippets = [];
|
|
1347
1374
|
if (loc.tsPath !== void 0 && loc.tsLine !== void 0) {
|
|
1348
1375
|
const tsSnippet = getSourceSnippet({
|
|
@@ -1356,7 +1383,7 @@ function formatMappedLocationSnippets(loc, showLuau, st, useColor) {
|
|
|
1356
1383
|
const label = showLuau ? "TypeScript" : void 0;
|
|
1357
1384
|
snippets.push("", formatSourceSnippet(tsSnippet, loc.tsPath, {
|
|
1358
1385
|
language: label,
|
|
1359
|
-
styles
|
|
1386
|
+
styles,
|
|
1360
1387
|
useColor
|
|
1361
1388
|
}));
|
|
1362
1389
|
}
|
|
@@ -1368,7 +1395,7 @@ function formatMappedLocationSnippets(loc, showLuau, st, useColor) {
|
|
|
1368
1395
|
});
|
|
1369
1396
|
if (luauSnippet !== void 0) snippets.push("", formatSourceSnippet(luauSnippet, loc.luauPath, {
|
|
1370
1397
|
language: "Luau",
|
|
1371
|
-
styles
|
|
1398
|
+
styles,
|
|
1372
1399
|
useColor
|
|
1373
1400
|
}));
|
|
1374
1401
|
}
|
|
@@ -1379,7 +1406,7 @@ function formatMappedLocationSnippets(loc, showLuau, st, useColor) {
|
|
|
1379
1406
|
line: loc.luauLine
|
|
1380
1407
|
});
|
|
1381
1408
|
if (luauSnippet !== void 0) snippets.push("", formatSourceSnippet(luauSnippet, loc.luauPath, {
|
|
1382
|
-
styles
|
|
1409
|
+
styles,
|
|
1383
1410
|
useColor
|
|
1384
1411
|
}));
|
|
1385
1412
|
}
|
|
@@ -1406,22 +1433,24 @@ function formatSnapshotCallSnippet(filePath, styles, useColor) {
|
|
|
1406
1433
|
})];
|
|
1407
1434
|
}
|
|
1408
1435
|
function resolveSourceSnippets(options) {
|
|
1409
|
-
const { filePath, hasSnapshotDiff, mappedLocations, message, showLuau, sourceMapper, styles
|
|
1410
|
-
if (mappedLocations.length > 0) return mappedLocations.flatMap((loc) =>
|
|
1411
|
-
|
|
1436
|
+
const { filePath, hasSnapshotDiff, mappedLocations, message, showLuau, sourceMapper, styles, useColor } = options;
|
|
1437
|
+
if (mappedLocations.length > 0) return mappedLocations.flatMap((loc) => {
|
|
1438
|
+
return formatMappedLocationSnippets(loc, showLuau, styles, useColor);
|
|
1439
|
+
});
|
|
1440
|
+
const fallback = formatFallbackSnippet(message, styles, useColor);
|
|
1412
1441
|
if (fallback.length > 0) return fallback;
|
|
1413
|
-
if (hasSnapshotDiff && filePath !== void 0) return formatSnapshotCallSnippet(resolveDisplayPath(filePath, sourceMapper),
|
|
1442
|
+
if (hasSnapshotDiff && filePath !== void 0) return formatSnapshotCallSnippet(resolveDisplayPath(filePath, sourceMapper), styles, useColor);
|
|
1414
1443
|
return [];
|
|
1415
1444
|
}
|
|
1416
1445
|
function formatFailureMessage(originalMessage, options) {
|
|
1417
|
-
const { filePath, showLuau, sourceMapper, styles
|
|
1446
|
+
const { filePath, showLuau, sourceMapper, styles, useColor } = options;
|
|
1418
1447
|
let mappedLocations = [];
|
|
1419
1448
|
let message = originalMessage;
|
|
1420
1449
|
if (sourceMapper !== void 0) ({locations: mappedLocations, message} = sourceMapper.mapFailureWithLocations(originalMessage));
|
|
1421
1450
|
const parsed = parseErrorMessage(originalMessage);
|
|
1422
1451
|
return [
|
|
1423
|
-
formatErrorLine(parsed,
|
|
1424
|
-
...formatDiffBlock(parsed,
|
|
1452
|
+
formatErrorLine(parsed, styles, useColor),
|
|
1453
|
+
...formatDiffBlock(parsed, styles),
|
|
1425
1454
|
...resolveSourceSnippets({
|
|
1426
1455
|
filePath,
|
|
1427
1456
|
hasSnapshotDiff: parsed.snapshotDiff !== void 0,
|
|
@@ -1429,19 +1458,19 @@ function formatFailureMessage(originalMessage, options) {
|
|
|
1429
1458
|
message,
|
|
1430
1459
|
showLuau,
|
|
1431
1460
|
sourceMapper,
|
|
1432
|
-
styles
|
|
1461
|
+
styles,
|
|
1433
1462
|
useColor
|
|
1434
1463
|
})
|
|
1435
1464
|
];
|
|
1436
1465
|
}
|
|
1437
|
-
function formatSnapshotLine(snapshot,
|
|
1466
|
+
function formatSnapshotLine(snapshot, styles) {
|
|
1438
1467
|
if (snapshot === void 0 || snapshot.unmatched === 0) return;
|
|
1439
|
-
return `${
|
|
1468
|
+
return `${styles.dim(" Snapshots")} ${styles.summary.failed(`${snapshot.unmatched} failed`)}`;
|
|
1440
1469
|
}
|
|
1441
1470
|
function formatFileFailures(file, options, styles, failureCtx) {
|
|
1442
1471
|
const lines = [];
|
|
1443
1472
|
const displayPath = resolveDisplayPath(file.testFilePath, options.sourceMapper);
|
|
1444
|
-
for (const
|
|
1473
|
+
for (const testCase of file.testResults) if (testCase.status === "failed") {
|
|
1445
1474
|
const index = failureCtx.currentIndex;
|
|
1446
1475
|
failureCtx.currentIndex++;
|
|
1447
1476
|
lines.push(formatFailure({
|
|
@@ -1450,7 +1479,7 @@ function formatFileFailures(file, options, styles, failureCtx) {
|
|
|
1450
1479
|
showLuau: options.showLuau,
|
|
1451
1480
|
sourceMapper: options.sourceMapper,
|
|
1452
1481
|
styles,
|
|
1453
|
-
test:
|
|
1482
|
+
test: testCase,
|
|
1454
1483
|
totalFailures: failureCtx.totalFailures,
|
|
1455
1484
|
useColor: options.color
|
|
1456
1485
|
}));
|
|
@@ -1480,24 +1509,24 @@ function formatLogHints(options, styles) {
|
|
|
1480
1509
|
if (options.gameOutput !== void 0) lines.push(styles.dim(` View ${options.gameOutput} for Roblox game logs`));
|
|
1481
1510
|
return lines.join("\n");
|
|
1482
1511
|
}
|
|
1483
|
-
function formatTestInGroup(
|
|
1484
|
-
const duration =
|
|
1485
|
-
if (
|
|
1486
|
-
const failedText = ` × ${
|
|
1512
|
+
function formatTestInGroup(testCase, styles) {
|
|
1513
|
+
const duration = testCase.duration !== void 0 ? styles.lineNumber(` ${testCase.duration}ms`) : "";
|
|
1514
|
+
if (testCase.status === "passed") return `${styles.status.pass(" ✓")}${styles.status.fail(` ${testCase.title}`)}${duration}`;
|
|
1515
|
+
const failedText = ` × ${testCase.title}`;
|
|
1487
1516
|
return `${styles.status.fail(failedText)}${duration}`;
|
|
1488
1517
|
}
|
|
1489
1518
|
function formatDescribeGroup(describeName, tests, styles) {
|
|
1490
1519
|
const lines = [];
|
|
1491
|
-
const groupHasFailure = tests.some((
|
|
1520
|
+
const groupHasFailure = tests.some((testCase) => testCase.status === "failed");
|
|
1492
1521
|
const groupTestCount = tests.length;
|
|
1493
|
-
const groupDuration = tests.reduce((sum,
|
|
1522
|
+
const groupDuration = tests.reduce((sum, testCase) => sum + (testCase.duration ?? 0), 0);
|
|
1494
1523
|
const groupDurationStr = styles.lineNumber(` ${groupDuration}ms`);
|
|
1495
1524
|
if (groupHasFailure) {
|
|
1496
|
-
const failedCount = tests.filter((
|
|
1525
|
+
const failedCount = tests.filter((testCase) => testCase.status === "failed").length;
|
|
1497
1526
|
const groupMeta = styles.dim(`(${groupTestCount} tests | `) + styles.summary.failed(`${failedCount} failed`) + styles.dim(")");
|
|
1498
1527
|
const header = styles.status.fail(` ❯ ${describeName}`);
|
|
1499
1528
|
lines.push(`${header} ${groupMeta}${groupDurationStr}`);
|
|
1500
|
-
for (const
|
|
1529
|
+
for (const testCase of tests) lines.push(formatTestInGroup(testCase, styles));
|
|
1501
1530
|
} else {
|
|
1502
1531
|
const groupMeta = styles.dim(`(${groupTestCount} tests)`);
|
|
1503
1532
|
const marker = styles.status.pass(" ✓");
|
|
@@ -1556,7 +1585,7 @@ function formatPassedFileSummary(file, ctx) {
|
|
|
1556
1585
|
const meta = ctx.styles.dim(`(${ctx.testCount} tests${duration})`);
|
|
1557
1586
|
lines.push(` ${symbol} ${ctx.formattedPath} ${meta}`);
|
|
1558
1587
|
if (ctx.verbose) {
|
|
1559
|
-
for (const
|
|
1588
|
+
for (const testCase of file.testResults) if (testCase.status === "passed") lines.push(formatPass(testCase, ctx.styles));
|
|
1560
1589
|
}
|
|
1561
1590
|
return lines;
|
|
1562
1591
|
}
|
|
@@ -1596,7 +1625,7 @@ function formatCompact(result, options) {
|
|
|
1596
1625
|
lines.push(...failureLines);
|
|
1597
1626
|
}
|
|
1598
1627
|
for (const file of execErrors) {
|
|
1599
|
-
const relativePath = makeRelative(resolveDisplayPath(file.testFilePath, options.sourceMapper), options.rootDir);
|
|
1628
|
+
const relativePath = makeRelative$1(resolveDisplayPath(file.testFilePath, options.sourceMapper), options.rootDir);
|
|
1600
1629
|
assert(file.failureMessage !== void 0, "exec error files have failureMessage");
|
|
1601
1630
|
const errorMessage = cleanExecErrorMessage(file.failureMessage);
|
|
1602
1631
|
lines.push(`[FAIL] ${relativePath} - suite failed to run`, errorMessage);
|
|
@@ -1615,7 +1644,7 @@ function formatCompactLogHints(options) {
|
|
|
1615
1644
|
if (options.gameOutput !== void 0) lines.push(`View ${options.gameOutput} for Roblox game logs`);
|
|
1616
1645
|
return lines.join("\n");
|
|
1617
1646
|
}
|
|
1618
|
-
function makeRelative(filePath, rootDirectory) {
|
|
1647
|
+
function makeRelative$1(filePath, rootDirectory) {
|
|
1619
1648
|
if (filePath.startsWith(rootDirectory)) return path.relative(rootDirectory, filePath);
|
|
1620
1649
|
return filePath;
|
|
1621
1650
|
}
|
|
@@ -1683,7 +1712,7 @@ function formatCompactFailure(test, filePath, options) {
|
|
|
1683
1712
|
if (options.sourceMapper !== void 0) ({locations: mappedLocations, message} = options.sourceMapper.mapFailureWithLocations(originalMessage));
|
|
1684
1713
|
const parsed = parseErrorMessage(originalMessage);
|
|
1685
1714
|
const location = findFailureLocation(mappedLocations, message);
|
|
1686
|
-
const relativePath = makeRelative(location?.path ?? filePath, options.rootDir);
|
|
1715
|
+
const relativePath = makeRelative$1(location?.path ?? filePath, options.rootDir);
|
|
1687
1716
|
const lineInfo = location?.line !== void 0 ? `:${location.line}` : "";
|
|
1688
1717
|
lines.push(`[FAIL] ${relativePath}${lineInfo} - ${test.title}`);
|
|
1689
1718
|
if (parsed.snapshotDiff !== void 0) lines.push(parsed.snapshotDiff);
|
|
@@ -1739,7 +1768,7 @@ function buildMappings(tree, prefix) {
|
|
|
1739
1768
|
if (key.startsWith("$") || typeof value !== "object") continue;
|
|
1740
1769
|
const dataModelPath = prefix ? `${prefix}/${key}` : key;
|
|
1741
1770
|
const node = value;
|
|
1742
|
-
if (typeof node
|
|
1771
|
+
if (typeof node.$path === "string") mappings.push([dataModelPath, node.$path]);
|
|
1743
1772
|
mappings.push(...buildMappings(node, dataModelPath));
|
|
1744
1773
|
}
|
|
1745
1774
|
mappings.sort((a, b) => b[0].length - a[0].length);
|
|
@@ -1748,7 +1777,6 @@ function buildMappings(tree, prefix) {
|
|
|
1748
1777
|
|
|
1749
1778
|
//#endregion
|
|
1750
1779
|
//#region src/executor.ts
|
|
1751
|
-
const rojoProjectSchema = type({ tree: "object" });
|
|
1752
1780
|
function isLuauProject(testFiles, tsconfigDirectories) {
|
|
1753
1781
|
if (tsconfigDirectories.outDir !== void 0) return false;
|
|
1754
1782
|
if (testFiles.some((file) => /\.tsx?$/.test(file))) return false;
|
|
@@ -1820,7 +1848,8 @@ async function execute(options) {
|
|
|
1820
1848
|
exitCode: result.success ? 0 : 1,
|
|
1821
1849
|
gameOutput,
|
|
1822
1850
|
output,
|
|
1823
|
-
result
|
|
1851
|
+
result,
|
|
1852
|
+
sourceMapper
|
|
1824
1853
|
};
|
|
1825
1854
|
}
|
|
1826
1855
|
function normalizeDirectoryPath(directory) {
|
|
@@ -1878,10 +1907,13 @@ const instrumentedFileRecordSchema = type({
|
|
|
1878
1907
|
const coverageManifestSchema = type({
|
|
1879
1908
|
files: type("Record<string, unknown>").pipe((files) => {
|
|
1880
1909
|
const validated = {};
|
|
1910
|
+
const skipped = [];
|
|
1881
1911
|
for (const [key, value] of Object.entries(files)) {
|
|
1882
1912
|
const parsed = instrumentedFileRecordSchema(value);
|
|
1883
|
-
if (
|
|
1913
|
+
if (parsed instanceof type.errors) skipped.push(key);
|
|
1914
|
+
else validated[key] = parsed;
|
|
1884
1915
|
}
|
|
1916
|
+
if (skipped.length > 0) process.stderr.write(`Warning: ${skipped.length} file record(s) in coverage manifest failed validation and were skipped: ${skipped.join(", ")}\n`);
|
|
1885
1917
|
return validated;
|
|
1886
1918
|
}),
|
|
1887
1919
|
generatedAt: "string",
|
|
@@ -1951,6 +1983,154 @@ function writeSnapshots(snapshotWrites, config, tsconfigDirectories) {
|
|
|
1951
1983
|
}
|
|
1952
1984
|
}
|
|
1953
1985
|
|
|
1986
|
+
//#endregion
|
|
1987
|
+
//#region src/formatters/github-actions.ts
|
|
1988
|
+
const SEPARATOR = " · ";
|
|
1989
|
+
function escapeData(value) {
|
|
1990
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
1991
|
+
}
|
|
1992
|
+
function escapeProperty(value) {
|
|
1993
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
1994
|
+
}
|
|
1995
|
+
function formatAnnotation(annotation) {
|
|
1996
|
+
const properties = [`file=${escapeProperty(annotation.file)}`];
|
|
1997
|
+
if (annotation.line !== void 0) properties.push(`line=${String(annotation.line)}`);
|
|
1998
|
+
if (annotation.col !== void 0) properties.push(`col=${String(annotation.col)}`);
|
|
1999
|
+
if (annotation.title !== void 0) properties.push(`title=${escapeProperty(annotation.title)}`);
|
|
2000
|
+
return `::error ${properties.join(",")}::${escapeData(annotation.message)}`;
|
|
2001
|
+
}
|
|
2002
|
+
function collectAnnotations(result, options) {
|
|
2003
|
+
const annotations = [];
|
|
2004
|
+
for (const file of result.testResults) {
|
|
2005
|
+
if (hasExecError(file)) {
|
|
2006
|
+
collectExecErrorAnnotation(annotations, file, options);
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
collectTestFailureAnnotations(annotations, file, options);
|
|
2010
|
+
}
|
|
2011
|
+
return annotations;
|
|
2012
|
+
}
|
|
2013
|
+
function formatAnnotations(result, options) {
|
|
2014
|
+
const annotations = collectAnnotations(result, options);
|
|
2015
|
+
if (annotations.length === 0) return "";
|
|
2016
|
+
return annotations.map(formatAnnotation).join("\n");
|
|
2017
|
+
}
|
|
2018
|
+
function formatJobSummary(result, options) {
|
|
2019
|
+
const fileLink = createFileLink(options);
|
|
2020
|
+
const lines = ["## Test Results\n", renderStats(result)];
|
|
2021
|
+
const failures = [];
|
|
2022
|
+
for (const file of result.testResults) {
|
|
2023
|
+
if (hasExecError(file)) {
|
|
2024
|
+
failures.push({
|
|
2025
|
+
file: makeRelative(file.testFilePath, options.workspace),
|
|
2026
|
+
title: "Test suite failed to run"
|
|
2027
|
+
});
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
for (const test of file.testResults) {
|
|
2031
|
+
if (test.status !== "failed") continue;
|
|
2032
|
+
failures.push({
|
|
2033
|
+
file: makeRelative(file.testFilePath, options.workspace),
|
|
2034
|
+
title: test.fullName
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
if (failures.length > 0) {
|
|
2039
|
+
lines.push("### Failures\n");
|
|
2040
|
+
for (const failure of failures) {
|
|
2041
|
+
const link = fileLink(failure.file);
|
|
2042
|
+
const fileRef = link !== void 0 ? `[${failure.file}](${link})` : failure.file;
|
|
2043
|
+
lines.push(`- **${failure.title}** in ${fileRef}`);
|
|
2044
|
+
}
|
|
2045
|
+
lines.push("");
|
|
2046
|
+
}
|
|
2047
|
+
return lines.join("\n");
|
|
2048
|
+
}
|
|
2049
|
+
function resolveGitHubActionsOptions(userOptions, sourceMapper, environment = process.env) {
|
|
2050
|
+
return {
|
|
2051
|
+
repository: userOptions.jobSummary?.fileLinks?.repository ?? environment["GITHUB_REPOSITORY"],
|
|
2052
|
+
serverUrl: environment["GITHUB_SERVER_URL"],
|
|
2053
|
+
sha: userOptions.jobSummary?.fileLinks?.commitHash ?? environment["GITHUB_SHA"],
|
|
2054
|
+
sourceMapper,
|
|
2055
|
+
workspace: userOptions.jobSummary?.fileLinks?.workspacePath ?? environment["GITHUB_WORKSPACE"]
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
function makeRelative(filePath, workspace) {
|
|
2059
|
+
if (workspace === void 0) return filePath;
|
|
2060
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
2061
|
+
const normalizedWorkspace = workspace.replace(/\\/g, "/").replace(/\/$/, "");
|
|
2062
|
+
if (normalized.startsWith(`${normalizedWorkspace}/`)) return normalized.slice(normalizedWorkspace.length + 1);
|
|
2063
|
+
return filePath;
|
|
2064
|
+
}
|
|
2065
|
+
function collectExecErrorAnnotation(annotations, file, options) {
|
|
2066
|
+
annotations.push({
|
|
2067
|
+
file: makeRelative(file.testFilePath, options.workspace),
|
|
2068
|
+
message: file.failureMessage,
|
|
2069
|
+
title: "Test suite failed to run"
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
function collectTestFailureAnnotations(annotations, file, options) {
|
|
2073
|
+
for (const test of file.testResults) {
|
|
2074
|
+
if (test.status !== "failed") continue;
|
|
2075
|
+
const firstFailure = test.failureMessages[0] ?? "";
|
|
2076
|
+
let annotationFile = file.testFilePath;
|
|
2077
|
+
let line;
|
|
2078
|
+
let column;
|
|
2079
|
+
if (options.sourceMapper !== void 0 && firstFailure !== "") {
|
|
2080
|
+
const location = options.sourceMapper.mapFailureWithLocations(firstFailure).locations[0];
|
|
2081
|
+
if (location?.tsPath !== void 0) {
|
|
2082
|
+
annotationFile = location.tsPath;
|
|
2083
|
+
line = location.tsLine;
|
|
2084
|
+
column = location.tsColumn;
|
|
2085
|
+
} else if (location !== void 0) {
|
|
2086
|
+
annotationFile = location.luauPath;
|
|
2087
|
+
line = location.luauLine;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
annotations.push({
|
|
2091
|
+
col: column,
|
|
2092
|
+
file: makeRelative(annotationFile, options.workspace),
|
|
2093
|
+
line,
|
|
2094
|
+
message: firstFailure,
|
|
2095
|
+
title: test.fullName
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
function noun(count, singular, plural) {
|
|
2100
|
+
return count === 1 ? singular : plural;
|
|
2101
|
+
}
|
|
2102
|
+
function renderStats(result) {
|
|
2103
|
+
const failedFiles = result.testResults.filter((file) => file.numFailingTests > 0 || hasExecError(file)).length;
|
|
2104
|
+
const passedFiles = result.testResults.filter((file) => file.numFailingTests === 0 && !hasExecError(file)).length;
|
|
2105
|
+
const totalFiles = failedFiles + passedFiles;
|
|
2106
|
+
const fileInfo = [];
|
|
2107
|
+
if (failedFiles > 0) fileInfo.push(`❌ **${String(failedFiles)} ${noun(failedFiles, "failure", "failures")}**`);
|
|
2108
|
+
if (passedFiles > 0) fileInfo.push(`✅ **${String(passedFiles)} ${noun(passedFiles, "pass", "passes")}**`);
|
|
2109
|
+
fileInfo.push(`${String(totalFiles)} total`);
|
|
2110
|
+
const testInfo = [];
|
|
2111
|
+
if (result.numFailedTests > 0) testInfo.push(`❌ **${String(result.numFailedTests)} ${noun(result.numFailedTests, "failure", "failures")}**`);
|
|
2112
|
+
if (result.numPassedTests > 0) testInfo.push(`✅ **${String(result.numPassedTests)} ${noun(result.numPassedTests, "pass", "passes")}**`);
|
|
2113
|
+
const primaryTotal = result.numFailedTests + result.numPassedTests;
|
|
2114
|
+
testInfo.push(`${String(primaryTotal)} total`);
|
|
2115
|
+
let output = "### Summary\n\n";
|
|
2116
|
+
output += `- **Test Files**: ${fileInfo.join(SEPARATOR)}\n`;
|
|
2117
|
+
output += `- **Test Results**: ${testInfo.join(SEPARATOR)}\n`;
|
|
2118
|
+
const otherInfo = [];
|
|
2119
|
+
if (result.numPendingTests > 0) otherInfo.push(`${String(result.numPendingTests)} ${noun(result.numPendingTests, "skip", "skips")}`);
|
|
2120
|
+
if (result.numTodoTests !== void 0 && result.numTodoTests > 0) otherInfo.push(`${String(result.numTodoTests)} ${noun(result.numTodoTests, "todo", "todos")}`);
|
|
2121
|
+
if (otherInfo.length > 0) {
|
|
2122
|
+
const otherTotal = result.numPendingTests + (result.numTodoTests ?? 0);
|
|
2123
|
+
otherInfo.push(`${String(otherTotal)} total`);
|
|
2124
|
+
output += `- **Other**: ${otherInfo.join(SEPARATOR)}\n`;
|
|
2125
|
+
}
|
|
2126
|
+
return output;
|
|
2127
|
+
}
|
|
2128
|
+
function createFileLink(options) {
|
|
2129
|
+
const { repository, serverUrl, sha } = options;
|
|
2130
|
+
if (serverUrl === void 0 || repository === void 0 || sha === void 0) return (_filePath) => {};
|
|
2131
|
+
return (filePath) => `${serverUrl}/${repository}/blob/${sha}/${filePath}`;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
1954
2134
|
//#endregion
|
|
1955
2135
|
//#region src/typecheck/collect.ts
|
|
1956
2136
|
const TEST_FUNCTIONS = new Set(["it", "test"]);
|
|
@@ -2021,28 +2201,28 @@ function extractDefinition(node, source) {
|
|
|
2021
2201
|
|
|
2022
2202
|
//#endregion
|
|
2023
2203
|
//#region src/typecheck/parse.ts
|
|
2024
|
-
const
|
|
2204
|
+
const errorCodeRegExp = /error TS(?<errorCode>\d+)/;
|
|
2025
2205
|
function parseTscErrorLine(line) {
|
|
2026
2206
|
const parenIndex = line.lastIndexOf("(", line.indexOf("): error TS"));
|
|
2027
2207
|
if (parenIndex === -1) return ["", null];
|
|
2028
2208
|
const filePath = line.slice(0, parenIndex);
|
|
2029
2209
|
const rest = line.slice(parenIndex);
|
|
2030
2210
|
const closeParenIndex = rest.indexOf(")");
|
|
2031
|
-
const [
|
|
2032
|
-
if (
|
|
2211
|
+
const [lineString, columnString] = rest.slice(1, closeParenIndex).split(",");
|
|
2212
|
+
if (lineString === void 0 || lineString === "" || columnString === void 0 || columnString === "") return [filePath, null];
|
|
2033
2213
|
const afterParen = rest.slice(closeParenIndex + 1);
|
|
2034
|
-
const
|
|
2035
|
-
if (
|
|
2036
|
-
const
|
|
2037
|
-
const marker = `error TS${String(
|
|
2214
|
+
const errorCodeString = errorCodeRegExp.exec(afterParen)?.groups?.["errorCode"];
|
|
2215
|
+
if (errorCodeString === void 0) return [filePath, null];
|
|
2216
|
+
const errorCode = Number(errorCodeString);
|
|
2217
|
+
const marker = `error TS${String(errorCode)}: `;
|
|
2038
2218
|
const markerIndex = afterParen.indexOf(marker);
|
|
2039
|
-
const
|
|
2219
|
+
const errorMessage = afterParen.slice(markerIndex + marker.length).trim();
|
|
2040
2220
|
return [filePath, {
|
|
2041
|
-
column: Number(
|
|
2042
|
-
|
|
2043
|
-
|
|
2221
|
+
column: Number(columnString),
|
|
2222
|
+
errorCode,
|
|
2223
|
+
errorMessage,
|
|
2044
2224
|
filePath,
|
|
2045
|
-
line: Number(
|
|
2225
|
+
line: Number(lineString)
|
|
2046
2226
|
}];
|
|
2047
2227
|
}
|
|
2048
2228
|
function parseTscOutput(stdout) {
|
|
@@ -2128,7 +2308,7 @@ function buildFileResult(filePath, fileInfo, errors) {
|
|
|
2128
2308
|
for (const error of errors) {
|
|
2129
2309
|
const charIndex = indexMap.get(`${String(error.line)}:${String(error.column)}`);
|
|
2130
2310
|
const definition = charIndex !== void 0 ? sortedDefinitions.find((td) => td.start <= charIndex && td.end >= charIndex) : void 0;
|
|
2131
|
-
const message = `TS${String(error.
|
|
2311
|
+
const message = `TS${String(error.errorCode)}: ${error.errorMessage}`;
|
|
2132
2312
|
if (definition) {
|
|
2133
2313
|
const existing = errorsByTest.get(definition.name) ?? [];
|
|
2134
2314
|
existing.push(message);
|
|
@@ -2152,7 +2332,7 @@ function buildFileResult(filePath, fileInfo, errors) {
|
|
|
2152
2332
|
status: "failed",
|
|
2153
2333
|
title: "<file-level type error>"
|
|
2154
2334
|
});
|
|
2155
|
-
const numberFailing = testCases.filter((
|
|
2335
|
+
const numberFailing = testCases.filter((testCase) => testCase.status === "failed").length;
|
|
2156
2336
|
return {
|
|
2157
2337
|
numFailingTests: numberFailing,
|
|
2158
2338
|
numPassingTests: testCases.length - numberFailing,
|
|
@@ -2209,4 +2389,4 @@ function writeGameOutput(filePath, entries) {
|
|
|
2209
2389
|
}
|
|
2210
2390
|
|
|
2211
2391
|
//#endregion
|
|
2212
|
-
export {
|
|
2392
|
+
export { parseJestOutput as A, createStudioBackend as C, generateTestScript as D, buildJestArgv as E, LuauScriptError as O, StudioBackend as S, createOpenCloudBackend as T, resolveConfig as _, formatAnnotations as a, defineConfig as b, execute as c, writeJsonFile as d, formatFailure as f, loadConfig$1 as g, rojoProjectSchema as h, runTypecheck as i, extractJsonFromOutput as k, loadCoverageManifest as l, formatTestSummary as m, parseGameOutput as n, formatJobSummary as o, formatResult as p, writeGameOutput as r, resolveGitHubActionsOptions as s, formatGameOutputNotice as t, formatJson as u, DEFAULT_CONFIG as v, OpenCloudBackend as w, isValidBackend as x, VALID_BACKENDS as y };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as ResolvedConfig, c as Argv, i as FormatterEntry, n as Config, o as SnapshotFormatOptions, r as DEFAULT_CONFIG, s as defineConfig, t as CliOptions } from "./schema-DcDQmTyn.mjs";
|
|
2
2
|
import { WebSocket, WebSocketServer } from "ws";
|
|
3
|
+
import "arktype";
|
|
3
4
|
import buffer from "node:buffer";
|
|
4
5
|
|
|
5
6
|
//#region src/coverage/types.d.ts
|
|
@@ -17,12 +18,18 @@ interface RawFileCoverage {
|
|
|
17
18
|
type RawCoverageData = Record<string, RawFileCoverage>;
|
|
18
19
|
//#endregion
|
|
19
20
|
//#region src/types/jest-result.d.ts
|
|
20
|
-
type TestStatus = "failed" | "passed" | "pending" | "skipped";
|
|
21
|
+
type TestStatus = "disabled" | "failed" | "passed" | "pending" | "skipped" | "todo";
|
|
21
22
|
interface TestCaseResult {
|
|
22
23
|
ancestorTitles: Array<string>;
|
|
23
24
|
duration?: number;
|
|
24
25
|
failureMessages: Array<string>;
|
|
25
26
|
fullName: string;
|
|
27
|
+
location?: {
|
|
28
|
+
column: number;
|
|
29
|
+
line: number;
|
|
30
|
+
};
|
|
31
|
+
numPassingAsserts?: number;
|
|
32
|
+
retryReasons?: Array<string>;
|
|
26
33
|
status: TestStatus;
|
|
27
34
|
title: string;
|
|
28
35
|
}
|
|
@@ -45,6 +52,7 @@ interface JestResult {
|
|
|
45
52
|
numFailedTests: number;
|
|
46
53
|
numPassedTests: number;
|
|
47
54
|
numPendingTests: number;
|
|
55
|
+
numTodoTests?: number;
|
|
48
56
|
numTotalTests: number;
|
|
49
57
|
snapshot?: SnapshotSummary;
|
|
50
58
|
startTime: number;
|
|
@@ -121,6 +129,7 @@ declare class OpenCloudBackend implements Backend {
|
|
|
121
129
|
runTests(options: BackendOptions): Promise<BackendResult>;
|
|
122
130
|
private createExecutionTask;
|
|
123
131
|
private pollForCompletion;
|
|
132
|
+
private uploadOrReuseCached;
|
|
124
133
|
private uploadPlaceData;
|
|
125
134
|
}
|
|
126
135
|
declare function createOpenCloudBackend(): OpenCloudBackend;
|
|
@@ -157,22 +166,6 @@ declare function createStudioBackend(options: StudioOptions): StudioBackend;
|
|
|
157
166
|
declare function resolveConfig(config: Config): ResolvedConfig;
|
|
158
167
|
declare function loadConfig(configPath?: string, cwd?: string): Promise<ResolvedConfig>;
|
|
159
168
|
//#endregion
|
|
160
|
-
//#region src/executor.d.ts
|
|
161
|
-
interface ExecuteOptions {
|
|
162
|
-
backend: Backend;
|
|
163
|
-
config: ResolvedConfig;
|
|
164
|
-
testFiles: Array<string>;
|
|
165
|
-
version: string;
|
|
166
|
-
}
|
|
167
|
-
interface ExecuteResult {
|
|
168
|
-
coverageData?: RawCoverageData;
|
|
169
|
-
exitCode: number;
|
|
170
|
-
gameOutput?: string;
|
|
171
|
-
output: string;
|
|
172
|
-
result: JestResult;
|
|
173
|
-
}
|
|
174
|
-
declare function execute(options: ExecuteOptions): Promise<ExecuteResult>;
|
|
175
|
-
//#endregion
|
|
176
169
|
//#region src/source-mapper/index.d.ts
|
|
177
170
|
interface MappedLocation {
|
|
178
171
|
luauLine: number;
|
|
@@ -192,6 +185,23 @@ interface SourceMapper {
|
|
|
192
185
|
resolveTestFilePath(testFilePath: string): string | undefined;
|
|
193
186
|
}
|
|
194
187
|
//#endregion
|
|
188
|
+
//#region src/executor.d.ts
|
|
189
|
+
interface ExecuteOptions {
|
|
190
|
+
backend: Backend;
|
|
191
|
+
config: ResolvedConfig;
|
|
192
|
+
testFiles: Array<string>;
|
|
193
|
+
version: string;
|
|
194
|
+
}
|
|
195
|
+
interface ExecuteResult {
|
|
196
|
+
coverageData?: RawCoverageData;
|
|
197
|
+
exitCode: number;
|
|
198
|
+
gameOutput?: string;
|
|
199
|
+
output: string;
|
|
200
|
+
result: JestResult;
|
|
201
|
+
sourceMapper?: SourceMapper;
|
|
202
|
+
}
|
|
203
|
+
declare function execute(options: ExecuteOptions): Promise<ExecuteResult>;
|
|
204
|
+
//#endregion
|
|
195
205
|
//#region src/types/timing.d.ts
|
|
196
206
|
interface TimingResult {
|
|
197
207
|
executionMs: number;
|
|
@@ -263,6 +273,76 @@ declare function formatFailure({
|
|
|
263
273
|
declare function formatTestSummary(result: JestResult, timing: TimingResult, styles?: Styles): string;
|
|
264
274
|
declare function formatResult(result: JestResult, timing: TimingResult, options: FormatOptions): string;
|
|
265
275
|
//#endregion
|
|
276
|
+
//#region src/formatters/github-actions.d.ts
|
|
277
|
+
interface GitHubActionsOptions {
|
|
278
|
+
repository?: string;
|
|
279
|
+
serverUrl?: string;
|
|
280
|
+
sha?: string;
|
|
281
|
+
sourceMapper?: SourceMapper;
|
|
282
|
+
workspace?: string;
|
|
283
|
+
}
|
|
284
|
+
interface GitHubActionsFormatterOptions {
|
|
285
|
+
/**
|
|
286
|
+
* Whether to emit `::error` workflow commands for test failures.
|
|
287
|
+
*
|
|
288
|
+
* @default true
|
|
289
|
+
*/
|
|
290
|
+
displayAnnotations?: boolean;
|
|
291
|
+
/**
|
|
292
|
+
* Configuration for the GitHub Actions Job Summary.
|
|
293
|
+
*
|
|
294
|
+
* When enabled, a markdown summary of test results is written to the path
|
|
295
|
+
* specified by `outputPath`.
|
|
296
|
+
*/
|
|
297
|
+
jobSummary?: Partial<JobSummaryOptions>;
|
|
298
|
+
}
|
|
299
|
+
interface JobSummaryOptions {
|
|
300
|
+
/**
|
|
301
|
+
* Whether to generate the summary.
|
|
302
|
+
*
|
|
303
|
+
* @default true
|
|
304
|
+
*/
|
|
305
|
+
enabled: boolean;
|
|
306
|
+
/**
|
|
307
|
+
* Configuration for generating permalink URLs to source files in the
|
|
308
|
+
* GitHub repository.
|
|
309
|
+
*
|
|
310
|
+
* When all three values are available (either from this config or the
|
|
311
|
+
* defaults picked from environment variables), test names in the summary
|
|
312
|
+
* will link to the relevant source lines.
|
|
313
|
+
*/
|
|
314
|
+
fileLinks: {
|
|
315
|
+
/**
|
|
316
|
+
* The commit SHA to use in permalink URLs.
|
|
317
|
+
*
|
|
318
|
+
* @default process.env.GITHUB_SHA
|
|
319
|
+
*/
|
|
320
|
+
commitHash?: string;
|
|
321
|
+
/**
|
|
322
|
+
* The GitHub repository in `owner/repo` format.
|
|
323
|
+
*
|
|
324
|
+
* @default process.env.GITHUB_REPOSITORY
|
|
325
|
+
*/
|
|
326
|
+
repository?: string;
|
|
327
|
+
/**
|
|
328
|
+
* The absolute path to the root of the repository on disk.
|
|
329
|
+
*
|
|
330
|
+
* Used to compute relative file paths for the permalink URLs.
|
|
331
|
+
*
|
|
332
|
+
* @default process.env.GITHUB_WORKSPACE
|
|
333
|
+
*/
|
|
334
|
+
workspacePath?: string;
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* File path to write the summary to.
|
|
338
|
+
*
|
|
339
|
+
* @default process.env.GITHUB_STEP_SUMMARY
|
|
340
|
+
*/
|
|
341
|
+
outputPath: string | undefined;
|
|
342
|
+
}
|
|
343
|
+
declare function formatAnnotations(result: JestResult, options: GitHubActionsOptions): string;
|
|
344
|
+
declare function formatJobSummary(result: JestResult, options: GitHubActionsOptions): string;
|
|
345
|
+
//#endregion
|
|
266
346
|
//#region src/formatters/json.d.ts
|
|
267
347
|
declare function formatJson(result: JestResult): string;
|
|
268
348
|
declare function writeJsonFile(result: JestResult, filePath: string): Promise<void>;
|
|
@@ -278,8 +358,8 @@ declare function generateTestScript(options: BackendOptions): string;
|
|
|
278
358
|
//#region src/typecheck/types.d.ts
|
|
279
359
|
interface TscErrorInfo {
|
|
280
360
|
column: number;
|
|
281
|
-
|
|
282
|
-
|
|
361
|
+
errorCode: number;
|
|
362
|
+
errorMessage: string;
|
|
283
363
|
filePath: string;
|
|
284
364
|
line: number;
|
|
285
365
|
}
|
|
@@ -311,4 +391,4 @@ declare function formatGameOutputNotice(filePath: string, entryCount: number): s
|
|
|
311
391
|
declare function parseGameOutput(raw: string | undefined): Array<GameOutputEntry>;
|
|
312
392
|
declare function writeGameOutput(filePath: string, entries: Array<GameOutputEntry>): void;
|
|
313
393
|
//#endregion
|
|
314
|
-
export { type Backend, type BackendOptions, type CliOptions, type Config, DEFAULT_CONFIG, type ExecuteOptions, type ExecuteResult, type GameOutputEntry, type JestArgv, type JestResult, OpenCloudBackend, type ResolvedConfig, StudioBackend, type TestCaseResult, type TestDefinition, type TestFileResult, type TestStatus, type TscErrorInfo, type TypecheckOptions, buildJestArgv, createOpenCloudBackend, createStudioBackend, defineConfig, execute, extractJsonFromOutput, formatFailure, formatGameOutputNotice, formatJson, formatResult, formatTestSummary, generateTestScript, loadConfig, parseGameOutput, parseJestOutput, resolveConfig, runTypecheck, writeGameOutput, writeJsonFile };
|
|
394
|
+
export { type Backend, type BackendOptions, type CliOptions, type Config, DEFAULT_CONFIG, type ExecuteOptions, type ExecuteResult, type FormatterEntry, type GameOutputEntry, type GitHubActionsFormatterOptions, type JestArgv, type JestResult, OpenCloudBackend, type ResolvedConfig, StudioBackend, type TestCaseResult, type TestDefinition, type TestFileResult, type TestStatus, type TscErrorInfo, type TypecheckOptions, buildJestArgv, createOpenCloudBackend, createStudioBackend, defineConfig, execute, extractJsonFromOutput, formatAnnotations, formatFailure, formatGameOutputNotice, formatJobSummary, formatJson, formatResult, formatTestSummary, generateTestScript, loadConfig, parseGameOutput, parseJestOutput, resolveConfig, runTypecheck, writeGameOutput, writeJsonFile };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as generateTestScript, E as
|
|
1
|
+
import { A as parseJestOutput, C as createStudioBackend, D as generateTestScript, E as buildJestArgv, S as StudioBackend, T as createOpenCloudBackend, _ as resolveConfig, a as formatAnnotations, b as defineConfig, c as execute, d as writeJsonFile, f as formatFailure, g as loadConfig, i as runTypecheck, k as extractJsonFromOutput, m as formatTestSummary, n as parseGameOutput, o as formatJobSummary, p as formatResult, r as writeGameOutput, t as formatGameOutputNotice, u as formatJson, v as DEFAULT_CONFIG, w as OpenCloudBackend } from "./game-output-M8du29nj.mjs";
|
|
2
2
|
|
|
3
|
-
export { DEFAULT_CONFIG, OpenCloudBackend, StudioBackend, buildJestArgv, createOpenCloudBackend, createStudioBackend, defineConfig, execute, extractJsonFromOutput, formatFailure, formatGameOutputNotice, formatJson, formatResult, formatTestSummary, generateTestScript, loadConfig, parseGameOutput, parseJestOutput, resolveConfig, runTypecheck, writeGameOutput, writeJsonFile };
|
|
3
|
+
export { DEFAULT_CONFIG, OpenCloudBackend, StudioBackend, buildJestArgv, createOpenCloudBackend, createStudioBackend, defineConfig, execute, extractJsonFromOutput, formatAnnotations, formatFailure, formatGameOutputNotice, formatJobSummary, formatJson, formatResult, formatTestSummary, generateTestScript, loadConfig, parseGameOutput, parseJestOutput, resolveConfig, runTypecheck, writeGameOutput, writeJsonFile };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReportOptions } from "istanbul-reports";
|
|
2
2
|
|
|
3
3
|
//#region ../../node_modules/.pnpm/@rbxts+jest@3.13.3-ts.1/node_modules/@rbxts/jest/src/config.d.ts
|
|
4
4
|
interface ReporterConfig {
|
|
@@ -760,6 +760,8 @@ type _Except<ObjectType, KeysType extends keyof ObjectType, Options extends Requ
|
|
|
760
760
|
//#endregion
|
|
761
761
|
//#region src/config/schema.d.ts
|
|
762
762
|
type Backend = "auto" | "open-cloud" | "studio";
|
|
763
|
+
type CoverageReporter = keyof ReportOptions;
|
|
764
|
+
type FormatterEntry = [string, Record<string, unknown>] | string;
|
|
763
765
|
interface SnapshotFormatOptions {
|
|
764
766
|
callToJSON?: boolean;
|
|
765
767
|
escapeRegex?: boolean;
|
|
@@ -779,19 +781,21 @@ interface Config extends Except<Argv, "rootDir" | "setupFiles" | "setupFilesAfte
|
|
|
779
781
|
compactMaxFailures?: number;
|
|
780
782
|
coverageDirectory?: string;
|
|
781
783
|
coveragePathIgnorePatterns?: Array<string>;
|
|
782
|
-
coverageReporters?: Array<
|
|
784
|
+
coverageReporters?: Array<CoverageReporter>;
|
|
783
785
|
coverageThreshold?: {
|
|
784
786
|
branches?: number;
|
|
785
787
|
functions?: number;
|
|
786
788
|
lines?: number;
|
|
787
789
|
statements?: number;
|
|
788
790
|
};
|
|
791
|
+
formatters?: Array<FormatterEntry>;
|
|
789
792
|
gameOutput?: string;
|
|
790
793
|
jestPath?: string;
|
|
791
794
|
luauRoots?: Array<string>;
|
|
792
795
|
placeFile?: string;
|
|
793
796
|
pollInterval?: number;
|
|
794
797
|
port?: number;
|
|
798
|
+
reporters?: Array<string>;
|
|
795
799
|
rojoProject?: string;
|
|
796
800
|
rootDir?: string;
|
|
797
801
|
setupFiles?: Array<string>;
|
|
@@ -815,7 +819,7 @@ interface ResolvedConfig extends Config {
|
|
|
815
819
|
compactMaxFailures: number;
|
|
816
820
|
coverageDirectory: string;
|
|
817
821
|
coveragePathIgnorePatterns: Array<string>;
|
|
818
|
-
coverageReporters: Array<
|
|
822
|
+
coverageReporters: Array<CoverageReporter>;
|
|
819
823
|
json: boolean;
|
|
820
824
|
placeFile: string;
|
|
821
825
|
pollInterval: number;
|
|
@@ -843,8 +847,9 @@ interface CliOptions {
|
|
|
843
847
|
compactMaxFailures?: number;
|
|
844
848
|
config?: string;
|
|
845
849
|
coverageDirectory?: string;
|
|
846
|
-
coverageReporters?: Array<
|
|
850
|
+
coverageReporters?: Array<CoverageReporter>;
|
|
847
851
|
files?: Array<string>;
|
|
852
|
+
formatters?: Array<string>;
|
|
848
853
|
gameOutput?: string;
|
|
849
854
|
help?: boolean;
|
|
850
855
|
json?: boolean;
|
|
@@ -852,6 +857,7 @@ interface CliOptions {
|
|
|
852
857
|
pollInterval?: number;
|
|
853
858
|
port?: number;
|
|
854
859
|
projects?: Array<string>;
|
|
860
|
+
reporters?: Array<string>;
|
|
855
861
|
rojoProject?: string;
|
|
856
862
|
setupFiles?: Array<string>;
|
|
857
863
|
setupFilesAfterEnv?: Array<string>;
|
|
@@ -868,6 +874,6 @@ interface CliOptions {
|
|
|
868
874
|
verbose?: boolean;
|
|
869
875
|
version?: boolean;
|
|
870
876
|
}
|
|
871
|
-
declare const defineConfig:
|
|
877
|
+
declare const defineConfig: (input: Config) => Config;
|
|
872
878
|
//#endregion
|
|
873
|
-
export {
|
|
879
|
+
export { ResolvedConfig as a, Argv as c, FormatterEntry as i, Config as n, SnapshotFormatOptions as o, DEFAULT_CONFIG as r, defineConfig as s, CliOptions as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isentinel/jest-roblox",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Jest-compatible CLI for running roblox-ts tests via Roblox Open Cloud",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jest",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"ws": "8.18.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@isentinel/eslint-config": "5.0.0-beta.
|
|
55
|
+
"@isentinel/eslint-config": "5.0.0-beta.9",
|
|
56
56
|
"@oxc-project/types": "0.120.0",
|
|
57
57
|
"@rbxts/jest": "3.13.3-ts.1",
|
|
58
58
|
"@rbxts/types": "1.0.911",
|
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
"better-typescript-lib": "2.12.0",
|
|
69
69
|
"bumpp": "10.4.1",
|
|
70
70
|
"eslint": "9.39.2",
|
|
71
|
+
"eslint-plugin-jest-extended": "3.0.1",
|
|
72
|
+
"jest-extended": "7.0.0",
|
|
71
73
|
"memfs": "4.56.11",
|
|
72
74
|
"publint": "0.3.15",
|
|
73
75
|
"tsdown": "0.20.1",
|