@isentinel/jest-roblox 0.3.4 → 0.3.5
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 +1 -1
- package/dist/index.d.mts +19 -1
- package/dist/index.mjs +1 -1
- package/dist/{run-D20euZYa.mjs → run-DbEgZMyU.mjs} +130 -59
- package/package.json +4 -4
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as loadConfig, D as outputMultiResult, H as formatBanner, O as outputSingleResult, P as parseGameOutput, Q as mergeCliWithConfig, Y as LuauScriptError, dt as version, f as formatMissingScopes, lt as isValidBackend, n as runJestRoblox, ot as VALID_BACKENDS, p as walkErrorChain, ut as ConfigError } from "./run-
|
|
1
|
+
import { $ as loadConfig, D as outputMultiResult, H as formatBanner, O as outputSingleResult, P as parseGameOutput, Q as mergeCliWithConfig, Y as LuauScriptError, dt as version, f as formatMissingScopes, lt as isValidBackend, n as runJestRoblox, ot as VALID_BACKENDS, p as walkErrorChain, ut as ConfigError } from "./run-DbEgZMyU.mjs";
|
|
2
2
|
import { OpenCloudError } from "@bedrock-rbx/ocale";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { parseArgs as parseArgs$1 } from "node:util";
|
package/dist/index.d.mts
CHANGED
|
@@ -364,6 +364,12 @@ interface RunnerCredentials {
|
|
|
364
364
|
}
|
|
365
365
|
interface UploadPlaceOptions {
|
|
366
366
|
placeFilePath: string;
|
|
367
|
+
/**
|
|
368
|
+
* Publish as the live version instead of a Saved draft. Open Cloud Luau
|
|
369
|
+
* Execution boots the live Published version on fresh and recycled servers,
|
|
370
|
+
* so a Saved-only upload can be ignored mid-run when a warm server recycles.
|
|
371
|
+
*/
|
|
372
|
+
publish?: boolean;
|
|
367
373
|
}
|
|
368
374
|
interface UploadPlaceResult {
|
|
369
375
|
uploadMs: number;
|
|
@@ -511,7 +517,7 @@ type ParsedManifest<T> = {
|
|
|
511
517
|
* probe-inserter is intentionally not formalized here: it has no serialization
|
|
512
518
|
* boundary, so the TypeScript interface is sufficient.
|
|
513
519
|
*/
|
|
514
|
-
declare const MANIFEST_VERSION:
|
|
520
|
+
declare const MANIFEST_VERSION: 4;
|
|
515
521
|
interface InstrumentedFileRecord {
|
|
516
522
|
key: string;
|
|
517
523
|
branchCount?: number;
|
|
@@ -529,6 +535,13 @@ interface InstrumentedFileRecord {
|
|
|
529
535
|
sourceHash: string;
|
|
530
536
|
sourceMapPath: string;
|
|
531
537
|
statementCount: number;
|
|
538
|
+
/**
|
|
539
|
+
* Statement ids hit during the coverage run but credited to no per-test
|
|
540
|
+
* window (executed at module load or in a hook). Computed by the per-test
|
|
541
|
+
* attribution harvester; a consumer marks a mutant on one of these as
|
|
542
|
+
* Ignored (ADR-0003). Absent on a freshly-instrumented manifest (no run yet).
|
|
543
|
+
*/
|
|
544
|
+
staticStatementIds?: Array<string>;
|
|
532
545
|
}
|
|
533
546
|
/**
|
|
534
547
|
* One Jest test case's identity, recorded so Phase 3's differential cache can
|
|
@@ -584,6 +597,11 @@ declare function readManifest(filePath: string): ReadManifestResult;
|
|
|
584
597
|
interface AttributionResult {
|
|
585
598
|
/** Per Luau file: statement id → ids of the tests that covered it. */
|
|
586
599
|
coveringTestIds: Record<string, Record<string, Array<string>>>;
|
|
600
|
+
/**
|
|
601
|
+
* Per Luau file: statement ids hit during the run but credited to no per-test
|
|
602
|
+
* window (module-load or hook code). The static-mutant set of ADR-0003.
|
|
603
|
+
*/
|
|
604
|
+
staticStatementIds: Record<string, Array<string>>;
|
|
587
605
|
tests: Array<TestRecord>;
|
|
588
606
|
}
|
|
589
607
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as loadConfig, A as formatJobSummary, B as formatResult, C as BUILD_MANIFEST_VERSION, E as readBuildManifest, F as writeGameOutput, G as manifestSchema, I as createTimingCollector, J as applyAttribution, K as readManifest, L as formatJson, M as runProjects, N as formatGameOutputNotice, P as parseGameOutput, Q as mergeCliWithConfig, R as writeJsonFile, S as buildPlace, T as emitBuildManifest, U as hashFile, V as formatTestSummary, W as MANIFEST_VERSION, X as extractJsonFromOutput, Z as parseJestOutput, _ as COVERAGE_MANIFEST_PATH, a as loadRojoTree, at as SHARED_TEST_KEYS, b as visitExpression, c as StudioBackend, ct as defineProject, d as createOpenCloudBackend, et as resolveConfig, g as COVERAGE_BUILD_MANIFEST_PATH, h as generateTestScript, i as collectStubMounts, it as ROOT_CLI_KEYS, j as formatExecuteOutput, k as formatAnnotations, l as createStudioBackend, m as buildJestArgv, n as runJestRoblox, nt as GLOBAL_TEST_KEYS, o as runTypecheck, q as writeManifest, r as runSingleOrMulti, rt as JEST_ARGV_EXCLUDED_KEYS, s as resolveAllProjects, st as defineConfig, t as getRawProjects, tt as DEFAULT_CONFIG, u as OpenCloudBackend, v as findRojoProject, w as buildManifestSchema, x as visitStatement, y as visitBlock, z as formatFailure } from "./run-
|
|
1
|
+
import { $ as loadConfig, A as formatJobSummary, B as formatResult, C as BUILD_MANIFEST_VERSION, E as readBuildManifest, F as writeGameOutput, G as manifestSchema, I as createTimingCollector, J as applyAttribution, K as readManifest, L as formatJson, M as runProjects, N as formatGameOutputNotice, P as parseGameOutput, Q as mergeCliWithConfig, R as writeJsonFile, S as buildPlace, T as emitBuildManifest, U as hashFile, V as formatTestSummary, W as MANIFEST_VERSION, X as extractJsonFromOutput, Z as parseJestOutput, _ as COVERAGE_MANIFEST_PATH, a as loadRojoTree, at as SHARED_TEST_KEYS, b as visitExpression, c as StudioBackend, ct as defineProject, d as createOpenCloudBackend, et as resolveConfig, g as COVERAGE_BUILD_MANIFEST_PATH, h as generateTestScript, i as collectStubMounts, it as ROOT_CLI_KEYS, j as formatExecuteOutput, k as formatAnnotations, l as createStudioBackend, m as buildJestArgv, n as runJestRoblox, nt as GLOBAL_TEST_KEYS, o as runTypecheck, q as writeManifest, r as runSingleOrMulti, rt as JEST_ARGV_EXCLUDED_KEYS, s as resolveAllProjects, st as defineConfig, t as getRawProjects, tt as DEFAULT_CONFIG, u as OpenCloudBackend, v as findRojoProject, w as buildManifestSchema, x as visitStatement, y as visitBlock, z as formatFailure } from "./run-DbEgZMyU.mjs";
|
|
2
2
|
import * as path$1 from "node:path";
|
|
3
3
|
//#region src/artifacts/prepare-artifacts.ts
|
|
4
4
|
const COVERAGE_DIR = path$1.dirname(COVERAGE_BUILD_MANIFEST_PATH);
|
|
@@ -34,7 +34,7 @@ import { StorageClient } from "@bedrock-rbx/ocale/storage";
|
|
|
34
34
|
import { parseJSONC, parseYAML } from "confbox";
|
|
35
35
|
import { Visitor, parseSync } from "oxc-parser";
|
|
36
36
|
//#region package.json
|
|
37
|
-
var version = "0.3.
|
|
37
|
+
var version = "0.3.5";
|
|
38
38
|
//#endregion
|
|
39
39
|
//#region src/config/errors.ts
|
|
40
40
|
var ConfigError = class extends Error {
|
|
@@ -2115,10 +2115,12 @@ function buildProjectResult(entry, job, fallbackGameOutput) {
|
|
|
2115
2115
|
//#region src/coverage-pipeline/attribution.ts
|
|
2116
2116
|
/**
|
|
2117
2117
|
* Assemble the per-test attribution records the coverage manifest carries from
|
|
2118
|
-
* the runner's per-test deltas. `
|
|
2119
|
-
*
|
|
2118
|
+
* the runner's per-test deltas. `cumulative` is the whole-run hit table, used to
|
|
2119
|
+
* derive the static set (statements hit but credited to no test window).
|
|
2120
|
+
* `resolveSourceHash` maps a test file path to its source hash (an injected seam
|
|
2121
|
+
* so the harvester stays pure and fs-free).
|
|
2120
2122
|
*/
|
|
2121
|
-
function harvestAttribution(entries, resolveSourceHash) {
|
|
2123
|
+
function harvestAttribution(entries, cumulative, resolveSourceHash) {
|
|
2122
2124
|
const tests = [];
|
|
2123
2125
|
const coveringTestIds = {};
|
|
2124
2126
|
for (const entry of entries) {
|
|
@@ -2137,14 +2139,16 @@ function harvestAttribution(entries, resolveSourceHash) {
|
|
|
2137
2139
|
}
|
|
2138
2140
|
return {
|
|
2139
2141
|
coveringTestIds,
|
|
2142
|
+
staticStatementIds: deriveStatic(cumulative, coveringTestIds),
|
|
2140
2143
|
tests
|
|
2141
2144
|
};
|
|
2142
2145
|
}
|
|
2143
2146
|
/**
|
|
2144
2147
|
* Write an attribution result into a coverage manifest: set `tests[]` and place
|
|
2145
|
-
* each file's per-statement `coveringTestIds` on its
|
|
2146
|
-
* file absent from the manifest (e.g. a covered helper
|
|
2147
|
-
* universe) is dropped — the manifest's file set stays the
|
|
2148
|
+
* each file's per-statement `coveringTestIds` and `staticStatementIds` on its
|
|
2149
|
+
* record. Attribution for a file absent from the manifest (e.g. a covered helper
|
|
2150
|
+
* outside the report universe) is dropped — the manifest's file set stays the
|
|
2151
|
+
* source of truth.
|
|
2148
2152
|
*/
|
|
2149
2153
|
function applyAttribution(manifest, attribution) {
|
|
2150
2154
|
const files = { ...manifest.files };
|
|
@@ -2155,6 +2159,13 @@ function applyAttribution(manifest, attribution) {
|
|
|
2155
2159
|
coveringTestIds
|
|
2156
2160
|
};
|
|
2157
2161
|
}
|
|
2162
|
+
for (const [fileKey, staticStatementIds] of Object.entries(attribution.staticStatementIds)) {
|
|
2163
|
+
const record = files[fileKey];
|
|
2164
|
+
if (record !== void 0) files[fileKey] = {
|
|
2165
|
+
...record,
|
|
2166
|
+
staticStatementIds
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2158
2169
|
return {
|
|
2159
2170
|
...manifest,
|
|
2160
2171
|
files,
|
|
@@ -2162,9 +2173,11 @@ function applyAttribution(manifest, attribution) {
|
|
|
2162
2173
|
};
|
|
2163
2174
|
}
|
|
2164
2175
|
/**
|
|
2165
|
-
* Merge two attribution results: concatenate the test records
|
|
2166
|
-
* per-statement covering ids
|
|
2167
|
-
*
|
|
2176
|
+
* Merge two attribution results: concatenate the test records, union the
|
|
2177
|
+
* per-statement covering ids, and union the static sets. A statement static in
|
|
2178
|
+
* one project but credited to a test in the other is not static across the
|
|
2179
|
+
* merged run, so the union drops any id credited in the merged `coveringTestIds`.
|
|
2180
|
+
* Used to combine per-project attribution from a multi-project run.
|
|
2168
2181
|
*/
|
|
2169
2182
|
function mergeAttribution(a, b) {
|
|
2170
2183
|
const coveringTestIds = {};
|
|
@@ -2174,9 +2187,34 @@ function mergeAttribution(a, b) {
|
|
|
2174
2187
|
}
|
|
2175
2188
|
return {
|
|
2176
2189
|
coveringTestIds,
|
|
2190
|
+
staticStatementIds: mergeStatic(a.staticStatementIds, b.staticStatementIds, coveringTestIds),
|
|
2177
2191
|
tests: [...a.tests, ...b.tests]
|
|
2178
2192
|
};
|
|
2179
2193
|
}
|
|
2194
|
+
/**
|
|
2195
|
+
* A statement is static iff it was hit in the run (`count > 0`) but never
|
|
2196
|
+
* credited to a per-test window — i.e. its id is not a key in `coveringTestIds`
|
|
2197
|
+
* for that file. Ids are sorted numerically so the manifest stays stable.
|
|
2198
|
+
*/
|
|
2199
|
+
function deriveStatic(cumulative, coveringTestIds) {
|
|
2200
|
+
const staticStatementIds = {};
|
|
2201
|
+
for (const [fileKey, fileCoverage] of Object.entries(cumulative)) {
|
|
2202
|
+
const credited = coveringTestIds[fileKey];
|
|
2203
|
+
const ids = Object.entries(fileCoverage.s).filter(([statementId, hitCount]) => hitCount > 0 && credited?.[statementId] === void 0).map(([statementId]) => statementId).sort((a, b) => Number(a) - Number(b));
|
|
2204
|
+
if (ids.length > 0) staticStatementIds[fileKey] = ids;
|
|
2205
|
+
}
|
|
2206
|
+
return staticStatementIds;
|
|
2207
|
+
}
|
|
2208
|
+
function mergeStatic(a, b, coveringTestIds) {
|
|
2209
|
+
const merged = {};
|
|
2210
|
+
const fileKeys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
2211
|
+
for (const fileKey of fileKeys) {
|
|
2212
|
+
const credited = coveringTestIds[fileKey];
|
|
2213
|
+
const ids = [...new Set([...a[fileKey] ?? [], ...b[fileKey] ?? []])].filter((statementId) => credited?.[statementId] === void 0).sort((first, second) => Number(first) - Number(second));
|
|
2214
|
+
if (ids.length > 0) merged[fileKey] = ids;
|
|
2215
|
+
}
|
|
2216
|
+
return merged;
|
|
2217
|
+
}
|
|
2180
2218
|
//#endregion
|
|
2181
2219
|
//#region src/utils/atomic-write.ts
|
|
2182
2220
|
/**
|
|
@@ -2250,7 +2288,7 @@ function parseVersionedManifest(filePath, schema, expectedVersion) {
|
|
|
2250
2288
|
* probe-inserter is intentionally not formalized here: it has no serialization
|
|
2251
2289
|
* boundary, so the TypeScript interface is sufficient.
|
|
2252
2290
|
*/
|
|
2253
|
-
const MANIFEST_VERSION =
|
|
2291
|
+
const MANIFEST_VERSION = 4;
|
|
2254
2292
|
const instrumentedFileRecordSchema = type({
|
|
2255
2293
|
"key": "string",
|
|
2256
2294
|
"branchCount?": "number",
|
|
@@ -2261,7 +2299,8 @@ const instrumentedFileRecordSchema = type({
|
|
|
2261
2299
|
"originalLuauPath": "string",
|
|
2262
2300
|
"sourceHash": "string",
|
|
2263
2301
|
"sourceMapPath": "string",
|
|
2264
|
-
"statementCount": "number"
|
|
2302
|
+
"statementCount": "number",
|
|
2303
|
+
"staticStatementIds?": "string[]"
|
|
2265
2304
|
}).as();
|
|
2266
2305
|
const testRecordSchema = type({
|
|
2267
2306
|
testCaseId: "string",
|
|
@@ -2285,13 +2324,13 @@ const manifestSchema = type({
|
|
|
2285
2324
|
"rojoInputsHash?": "string",
|
|
2286
2325
|
"shadowDir": "string",
|
|
2287
2326
|
"tests?": testRecordSchema.array(),
|
|
2288
|
-
"version": type.unit(
|
|
2327
|
+
"version": type.unit(4)
|
|
2289
2328
|
}).as();
|
|
2290
2329
|
function writeManifest(filePath, manifest) {
|
|
2291
2330
|
atomicWrite(filePath, JSON.stringify(manifest, void 0, " "));
|
|
2292
2331
|
}
|
|
2293
2332
|
function readManifest(filePath) {
|
|
2294
|
-
return parseVersionedManifest(filePath, manifestSchema,
|
|
2333
|
+
return parseVersionedManifest(filePath, manifestSchema, 4);
|
|
2295
2334
|
}
|
|
2296
2335
|
//#endregion
|
|
2297
2336
|
//#region src/utils/hash.ts
|
|
@@ -4562,7 +4601,8 @@ function processProjectResult(entry, options) {
|
|
|
4562
4601
|
const testsMs = calculateTestsMs(result.testResults);
|
|
4563
4602
|
const sourceMapper = config.sourceMap ? timing.profile("buildSourceMapper", () => buildSourceMapper(config, tsconfigMappings)) : void 0;
|
|
4564
4603
|
resolveTestFilePaths(result, sourceMapper);
|
|
4565
|
-
const
|
|
4604
|
+
const harvestStatic = config.collectPerTestCoverage === true && coverageData !== void 0;
|
|
4605
|
+
const attribution = perTestCoverage !== void 0 || harvestStatic ? harvestAttribution(perTestCoverage ?? [], coverageData ?? {}, (testFilePath) => {
|
|
4566
4606
|
return resolveTestFileHash(sourceMapper, testFilePath);
|
|
4567
4607
|
}) : void 0;
|
|
4568
4608
|
const totalMs = Date.now() - startTime;
|
|
@@ -6321,13 +6361,14 @@ function discoverInstrumentableFiles(luauRoot) {
|
|
|
6321
6361
|
return new Set(results);
|
|
6322
6362
|
}
|
|
6323
6363
|
/**
|
|
6324
|
-
* Populate a shadow dir from one luauRoot: bulk-copy
|
|
6325
|
-
*
|
|
6326
|
-
*
|
|
6327
|
-
* complete mirror that satisfies rojo +
|
|
6364
|
+
* Populate a shadow dir from one luauRoot: bulk-copy every file (cold path),
|
|
6365
|
+
* run the instrumenter to overlay instrumented prod files, then sync the files
|
|
6366
|
+
* the instrumenter never emits (spec/test/snap plus non-luau rojo files) with
|
|
6367
|
+
* hash-tracked records so the shadow is a complete mirror that satisfies rojo +
|
|
6368
|
+
* testMatch.
|
|
6328
6369
|
*
|
|
6329
|
-
* On a warm run (cache hit) only changed files are re-instrumented and
|
|
6330
|
-
*
|
|
6370
|
+
* On a warm run (cache hit) only changed files are re-instrumented, and the
|
|
6371
|
+
* shadow is reconciled against source so files deleted upstream don't linger.
|
|
6331
6372
|
*/
|
|
6332
6373
|
function prepareShadowRoot(options) {
|
|
6333
6374
|
const { luauRoot, previousManifest, shadowDir, useIncremental } = options;
|
|
@@ -6360,6 +6401,7 @@ function prepareShadowRoot(options) {
|
|
|
6360
6401
|
if (useIncremental && previousManifest !== void 0 && skipFiles !== void 0) carryForwardRecords(luauRoot, previousManifest, allFiles, skipFiles);
|
|
6361
6402
|
const syncResult = syncNonInstrumentedFiles(luauRoot, shadowDir, previousManifest?.nonInstrumentedFiles);
|
|
6362
6403
|
if (syncResult.changed) changed = true;
|
|
6404
|
+
if (useIncremental && reconcileShadowToSource(luauRoot, shadowDir)) changed = true;
|
|
6363
6405
|
return {
|
|
6364
6406
|
changed,
|
|
6365
6407
|
files: allFiles,
|
|
@@ -6368,16 +6410,17 @@ function prepareShadowRoot(options) {
|
|
|
6368
6410
|
shadowDir
|
|
6369
6411
|
};
|
|
6370
6412
|
}
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
function
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
}
|
|
6413
|
+
const COV_MAP_SUFFIX = ".cov-map.json";
|
|
6414
|
+
/**
|
|
6415
|
+
* Does the source file backing a shadow entry still exist? A `.cov-map.json`
|
|
6416
|
+
* sidecar has no direct twin — it is keyed to its base `.luau`/`.lua`.
|
|
6417
|
+
*/
|
|
6418
|
+
function sourceTwinExists(luauRoot, relativePath) {
|
|
6419
|
+
if (relativePath.endsWith(COV_MAP_SUFFIX)) {
|
|
6420
|
+
const base = relativePath.slice(0, -13);
|
|
6421
|
+
return fs$1.existsSync(path$1.resolve(luauRoot, `${base}.luau`)) || fs$1.existsSync(path$1.resolve(luauRoot, `${base}.lua`));
|
|
6422
|
+
}
|
|
6423
|
+
return fs$1.existsSync(path$1.resolve(luauRoot, relativePath));
|
|
6381
6424
|
}
|
|
6382
6425
|
/**
|
|
6383
6426
|
* Shared directory walker. Skips node_modules and dot-prefixed directories —
|
|
@@ -6398,9 +6441,50 @@ function walkLuauDirectory(directory, relativeTo, predicate, results) {
|
|
|
6398
6441
|
}
|
|
6399
6442
|
}
|
|
6400
6443
|
}
|
|
6444
|
+
/**
|
|
6445
|
+
* Reconcile a warm shadow dir against its source root: unlink every shadow file
|
|
6446
|
+
* whose source no longer exists. This is the warm-run deletion mechanism across
|
|
6447
|
+
* every file category the pipeline manages — instrumented prod `.luau`,
|
|
6448
|
+
* spec/test/snap, and non-luau rojo files (`init.meta.json`, `*.model.json`, …)
|
|
6449
|
+
* alike. Diffing against source (rather than a recorded file set) means a file
|
|
6450
|
+
* category the sync never tracked still gets cleaned up, so a stale
|
|
6451
|
+
* `init.meta.json` can't survive into the rojo build and fail it. It walks with
|
|
6452
|
+
* the same scope as the rest of the pipeline (`walkLuauDirectory` skips
|
|
6453
|
+
* `node_modules`/dot-dirs); vendored content under those dirs is governed by
|
|
6454
|
+
* `rojoInputsHash` instead, which forces a cold rebuild when it changes.
|
|
6455
|
+
* `.cov-map.json` sidecars are instrumenter output with no 1:1 source twin;
|
|
6456
|
+
* they map back to their base `.luau`/`.lua`. Returns whether anything was
|
|
6457
|
+
* removed, so the caller forces a place rebuild.
|
|
6458
|
+
*/
|
|
6459
|
+
function reconcileShadowToSource(luauRoot, shadowDirectory) {
|
|
6460
|
+
if (!fs$1.existsSync(shadowDirectory)) return false;
|
|
6461
|
+
const posixShadow = normalizeWindowsPath(shadowDirectory);
|
|
6462
|
+
const shadowFiles = [];
|
|
6463
|
+
walkLuauDirectory(posixShadow, posixShadow, () => true, shadowFiles);
|
|
6464
|
+
let deleted = false;
|
|
6465
|
+
for (const relativePath of shadowFiles) {
|
|
6466
|
+
if (sourceTwinExists(luauRoot, relativePath)) continue;
|
|
6467
|
+
try {
|
|
6468
|
+
fs$1.unlinkSync(path$1.resolve(shadowDirectory, relativePath));
|
|
6469
|
+
deleted = true;
|
|
6470
|
+
} catch {}
|
|
6471
|
+
}
|
|
6472
|
+
return deleted;
|
|
6473
|
+
}
|
|
6401
6474
|
function isInstrumentableFile(name) {
|
|
6402
6475
|
return (name.endsWith(".luau") || name.endsWith(".lua")) && !isNonInstrumentedFile(name);
|
|
6403
6476
|
}
|
|
6477
|
+
/**
|
|
6478
|
+
* Every file the shadow dir must carry verbatim because the instrumenter never
|
|
6479
|
+
* emits it: spec/test/snap `.luau` plus all non-luau rojo files
|
|
6480
|
+
* (`init.meta.json`, `*.model.json`, …). The complement of
|
|
6481
|
+
* `isInstrumentableFile` — prod `.luau` is excluded because `instrumentRoot`
|
|
6482
|
+
* writes its instrumented copy into the shadow. `.cov-map.json` sidecars are
|
|
6483
|
+
* instrumenter output, not source, so they are excluded too.
|
|
6484
|
+
*/
|
|
6485
|
+
function shouldSyncToShadow(name) {
|
|
6486
|
+
return !isInstrumentableFile(name) && !name.endsWith(COV_MAP_SUFFIX);
|
|
6487
|
+
}
|
|
6404
6488
|
function carryForwardRecords(luauRoot, previousManifest, allFiles, skipFiles) {
|
|
6405
6489
|
const posixRoot = normalizeWindowsPath(luauRoot);
|
|
6406
6490
|
for (const relativePath of skipFiles) {
|
|
@@ -6408,26 +6492,13 @@ function carryForwardRecords(luauRoot, previousManifest, allFiles, skipFiles) {
|
|
|
6408
6492
|
Object.assign(allFiles, { [fileKey]: previousManifest.files[fileKey] });
|
|
6409
6493
|
}
|
|
6410
6494
|
}
|
|
6411
|
-
function
|
|
6412
|
-
walkLuauDirectory(directory, relativeTo,
|
|
6413
|
-
}
|
|
6414
|
-
function pruneStaleNonInstrumented(posixRoot, previousNonInstrumented, currentFiles) {
|
|
6415
|
-
if (previousNonInstrumented === void 0) return false;
|
|
6416
|
-
let changed = false;
|
|
6417
|
-
for (const [fileKey, record] of Object.entries(previousNonInstrumented)) {
|
|
6418
|
-
if (!fileKey.startsWith(`${posixRoot}/`)) continue;
|
|
6419
|
-
if (fileKey in currentFiles) continue;
|
|
6420
|
-
try {
|
|
6421
|
-
if (fs$1.existsSync(record.shadowPath)) fs$1.unlinkSync(record.shadowPath);
|
|
6422
|
-
} catch {}
|
|
6423
|
-
changed = true;
|
|
6424
|
-
}
|
|
6425
|
-
return changed;
|
|
6495
|
+
function discoverShadowSyncFiles(directory, relativeTo, results) {
|
|
6496
|
+
walkLuauDirectory(directory, relativeTo, shouldSyncToShadow, results);
|
|
6426
6497
|
}
|
|
6427
6498
|
function syncNonInstrumentedFiles(luauRoot, shadowDirectory, previousNonInstrumented) {
|
|
6428
6499
|
const posixRoot = normalizeWindowsPath(luauRoot);
|
|
6429
6500
|
const discovered = [];
|
|
6430
|
-
|
|
6501
|
+
discoverShadowSyncFiles(posixRoot, posixRoot, discovered);
|
|
6431
6502
|
const files = {};
|
|
6432
6503
|
let changed = false;
|
|
6433
6504
|
for (const relativePath of discovered) {
|
|
@@ -6449,7 +6520,6 @@ function syncNonInstrumentedFiles(luauRoot, shadowDirectory, previousNonInstrume
|
|
|
6449
6520
|
};
|
|
6450
6521
|
changed = true;
|
|
6451
6522
|
}
|
|
6452
|
-
changed = pruneStaleNonInstrumented(posixRoot, previousNonInstrumented, files) || changed;
|
|
6453
6523
|
return {
|
|
6454
6524
|
changed,
|
|
6455
6525
|
files
|
|
@@ -6503,8 +6573,9 @@ function buildFullCacheResult(options) {
|
|
|
6503
6573
|
const allFiles = {};
|
|
6504
6574
|
carryForwardRecords(luauRoot, previousManifest, allFiles, skipFiles);
|
|
6505
6575
|
const syncResult = syncNonInstrumentedFiles(luauRoot, shadowDirectory, previousManifest.nonInstrumentedFiles);
|
|
6576
|
+
const reconciled = reconcileShadowToSource(luauRoot, shadowDirectory);
|
|
6506
6577
|
return {
|
|
6507
|
-
changed: syncResult.changed,
|
|
6578
|
+
changed: syncResult.changed || reconciled,
|
|
6508
6579
|
files: allFiles,
|
|
6509
6580
|
luauRoot,
|
|
6510
6581
|
nonInstrumentedFiles: syncResult.files,
|
|
@@ -6560,7 +6631,8 @@ function prepareCoverage(config, beforeBuild) {
|
|
|
6560
6631
|
const manifestPath = path$1.join(COVERAGE_DIR, COVERAGE_MANIFEST);
|
|
6561
6632
|
const buildManifestPath = path$1.join(COVERAGE_DIR, BUILD_MANIFEST_FILE);
|
|
6562
6633
|
const previousManifest = loadCoverageManifest(manifestPath);
|
|
6563
|
-
|
|
6634
|
+
let useIncremental = canUseIncremental$1(previousManifest, config);
|
|
6635
|
+
if (useIncremental && previousManifest !== void 0 && hasDroppedLuauRoot(previousManifest.luauRoots, luauRoots)) useIncremental = false;
|
|
6564
6636
|
if (!useIncremental && fs$1.existsSync(COVERAGE_DIR)) fs$1.rmSync(COVERAGE_DIR, { recursive: true });
|
|
6565
6637
|
const allFiles = {};
|
|
6566
6638
|
const allNonInstrumented = {};
|
|
@@ -6581,11 +6653,6 @@ function prepareCoverage(config, beforeBuild) {
|
|
|
6581
6653
|
shadowDir: normalizeWindowsPath(path$1.resolve(result.shadowDir))
|
|
6582
6654
|
});
|
|
6583
6655
|
}
|
|
6584
|
-
if (useIncremental && previousManifest !== void 0) {
|
|
6585
|
-
const deleted = detectDeletedFiles(previousManifest, allFiles);
|
|
6586
|
-
cleanupDeletedFiles(deleted);
|
|
6587
|
-
if (deleted.length > 0) hasChanges = true;
|
|
6588
|
-
}
|
|
6589
6656
|
if (beforeBuild !== void 0) {
|
|
6590
6657
|
if (beforeBuild(COVERAGE_DIR)) hasChanges = true;
|
|
6591
6658
|
}
|
|
@@ -6720,7 +6787,7 @@ function buildAndWriteManifest(options) {
|
|
|
6720
6787
|
placeFilePath: placeFile,
|
|
6721
6788
|
rojoInputsHash,
|
|
6722
6789
|
shadowDir: COVERAGE_DIR,
|
|
6723
|
-
version:
|
|
6790
|
+
version: 4
|
|
6724
6791
|
};
|
|
6725
6792
|
writeManifest(manifestPath, manifest);
|
|
6726
6793
|
return manifest;
|
|
@@ -6763,6 +6830,10 @@ function reuseCoverageResult(options) {
|
|
|
6763
6830
|
rebuilt: false
|
|
6764
6831
|
};
|
|
6765
6832
|
}
|
|
6833
|
+
function hasDroppedLuauRoot(previous, current) {
|
|
6834
|
+
const currentSet = new Set(current.map(normalizeWindowsPath));
|
|
6835
|
+
return previous.some((root) => !currentSet.has(normalizeWindowsPath(root)));
|
|
6836
|
+
}
|
|
6766
6837
|
//#endregion
|
|
6767
6838
|
//#region packages/roblox-runner/dist/index.mjs
|
|
6768
6839
|
const FIELD_SPECS = [
|
|
@@ -6860,7 +6931,8 @@ var OcaleRunner = class {
|
|
|
6860
6931
|
placeId: this.credentials.placeId,
|
|
6861
6932
|
universeId: this.credentials.universeId
|
|
6862
6933
|
};
|
|
6863
|
-
const
|
|
6934
|
+
const requestOptions = { retryableTransportCodes: TRANSIENT_TRANSPORT_CODES };
|
|
6935
|
+
const result = options.publish === true ? await this.places.publish(parameters, requestOptions) : await this.places.save(parameters, requestOptions);
|
|
6864
6936
|
if (!result.success) throw new Error(`Failed to upload place: ${result.err.message}`, { cause: result.err });
|
|
6865
6937
|
return {
|
|
6866
6938
|
uploadMs: Date.now() - uploadStart,
|
|
@@ -9680,7 +9752,6 @@ function prepareForPackage(descriptor, workspaceRoot, ignore, timing) {
|
|
|
9680
9752
|
shadowDir: shadowDirectory
|
|
9681
9753
|
});
|
|
9682
9754
|
}
|
|
9683
|
-
if (useIncremental && previousManifest !== void 0) cleanupDeletedFiles(detectDeletedFiles(previousManifest, allFiles));
|
|
9684
9755
|
const manifest = {
|
|
9685
9756
|
buildId: crypto.randomUUID(),
|
|
9686
9757
|
files: allFiles,
|
|
@@ -9689,7 +9760,7 @@ function prepareForPackage(descriptor, workspaceRoot, ignore, timing) {
|
|
|
9689
9760
|
luauRoots: coverageRoots.map((entry) => entry.shadowDir),
|
|
9690
9761
|
nonInstrumentedFiles: allNonInstrumented,
|
|
9691
9762
|
shadowDir: normalizeWindowsPath(packageShadowRoot),
|
|
9692
|
-
version:
|
|
9763
|
+
version: 4
|
|
9693
9764
|
};
|
|
9694
9765
|
atomicWrite(manifestPath, JSON.stringify(manifest, void 0, " "));
|
|
9695
9766
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isentinel/jest-roblox",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Jest-compatible CLI for running roblox-ts tests via Roblox Open Cloud",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jest",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"!dist/**/*.tsbuildinfo"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@bedrock-rbx/ocale": "0.1.0-beta.
|
|
51
|
+
"@bedrock-rbx/ocale": "0.1.0-beta.17",
|
|
52
52
|
"@jridgewell/trace-mapping": "0.3.31",
|
|
53
53
|
"arktype": "2.2.0",
|
|
54
54
|
"c12": "4.0.0-beta.5",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@isentinel/eslint-config": "5.2.0",
|
|
70
|
-
"@isentinel/roblox-ts": "4.0.
|
|
70
|
+
"@isentinel/roblox-ts": "4.0.7",
|
|
71
71
|
"@isentinel/tsconfig": "1.2.0",
|
|
72
72
|
"@isentinel/weld": "0.2.0",
|
|
73
73
|
"@oxc-project/types": "0.123.0",
|
|
@@ -97,8 +97,8 @@
|
|
|
97
97
|
"type-fest": "5.7.0",
|
|
98
98
|
"typescript": "6.0.3",
|
|
99
99
|
"vitest": "4.1.8",
|
|
100
|
-
"@isentinel/roblox-runner": "0.1.0",
|
|
101
100
|
"@isentinel/luau-ast": "0.1.0",
|
|
101
|
+
"@isentinel/roblox-runner": "0.1.0",
|
|
102
102
|
"@isentinel/rojo-utils": "0.1.0"
|
|
103
103
|
},
|
|
104
104
|
"engines": {
|