@isentinel/jest-roblox 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -83
- package/dist/cli.mjs +46 -9
- package/dist/{game-output-C0_-YIAY.mjs → game-output-BMGxhjkE.mjs} +12 -1
- package/dist/index.mjs +1 -1
- package/dist/sea/jest-roblox +0 -0
- package/dist/sea-entry.cjs +73 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">jest-roblox-cli</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmx.dev/package/@isentinel/jest-roblox"><img src="https://img.shields.io/npm/v/@isentinel/jest-roblox" alt="npm version"></a>
|
|
5
|
+
<a href="https://github.com/christopher-buss/jest-roblox-cli/actions/workflows/ci.yaml"><img src="https://github.com/christopher-buss/jest-roblox-cli/actions/workflows/ci.yaml/badge.svg" alt="CI"></a>
|
|
6
|
+
<a href="https://github.com/christopher-buss/jest-roblox-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
7
|
+
</p>
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
Run your
|
|
9
|
-
terminal.
|
|
10
|
+
Run your roblox-ts and Luau tests inside Roblox, get results in your terminal.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Why?
|
|
16
|
-
|
|
17
|
-
Roblox code can only run inside the Roblox engine. Standard test runners
|
|
18
|
-
can't access the Roblox API. This tool bridges that gap by running tests in a
|
|
19
|
-
real Roblox session and piping results back to your terminal.
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="assets/cli-example.png" alt="jest-roblox-cli output" width="700">
|
|
14
|
+
</p>
|
|
20
15
|
|
|
21
16
|
- roblox-ts and pure Luau
|
|
22
17
|
- Source-mapped errors (Luau line numbers back to `.ts` files)
|
|
23
|
-
- Code coverage
|
|
18
|
+
- Code coverage (via [Lute](https://github.com/luau-lang/lute) instrumentation)
|
|
24
19
|
- Two backends: Open Cloud (remote) and Studio (local)
|
|
20
|
+
- Multiple output formatters (human, agent, JSON, GitHub Actions)
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
> roblox-ts projects currently require
|
|
24
|
+
> [@isentinel/roblox-ts](https://npmx.dev/package/@isentinel/roblox-ts) for
|
|
25
|
+
> source maps and coverage support.
|
|
25
26
|
|
|
26
27
|
## Install
|
|
27
28
|
|
|
@@ -41,7 +42,7 @@ rokit add christopher-buss/jest-roblox-cli
|
|
|
41
42
|
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
Limitations vs the npm package:
|
|
45
46
|
|
|
46
47
|
- `--typecheck` and `--typecheckOnly` are not available
|
|
47
48
|
- `.ts` config files are not supported (use `.json`, `.js`, or `.mjs`)
|
|
@@ -82,6 +83,7 @@ jest-roblox -t "should spawn"
|
|
|
82
83
|
|
|
83
84
|
# Filter by file path
|
|
84
85
|
jest-roblox --testPathPattern player
|
|
86
|
+
jest-roblox --testPathPattern="modifiers|define\\.spec|triggers"
|
|
85
87
|
|
|
86
88
|
# Use a specific backend
|
|
87
89
|
jest-roblox --backend studio
|
|
@@ -90,12 +92,6 @@ jest-roblox --backend open-cloud
|
|
|
90
92
|
# Collect coverage
|
|
91
93
|
jest-roblox --coverage
|
|
92
94
|
|
|
93
|
-
# Output JSON results
|
|
94
|
-
jest-roblox --formatters json --outputFile results.json
|
|
95
|
-
|
|
96
|
-
# Short output for AI tools
|
|
97
|
-
jest-roblox --formatters agent
|
|
98
|
-
|
|
99
95
|
# Save game output (print/warn/error) to file
|
|
100
96
|
jest-roblox --gameOutput game-logs.txt
|
|
101
97
|
|
|
@@ -145,7 +141,7 @@ Precedence: CLI flags > config file > extended config > defaults.
|
|
|
145
141
|
### Coverage fields
|
|
146
142
|
|
|
147
143
|
> [!IMPORTANT]
|
|
148
|
-
> Coverage requires [Lute](https://github.com/
|
|
144
|
+
> Coverage requires [Lute](https://github.com/luau-lang/lute) to be installed and
|
|
149
145
|
> on your `PATH`. Lute parses Luau ASTs so the CLI can insert coverage probes.
|
|
150
146
|
|
|
151
147
|
| Field | What it does | Default |
|
|
@@ -171,29 +167,23 @@ export default defineConfig({
|
|
|
171
167
|
projects: [
|
|
172
168
|
{
|
|
173
169
|
test: defineProject({
|
|
174
|
-
displayName: { name: "
|
|
175
|
-
include: ["
|
|
170
|
+
displayName: { name: "client", color: "magenta" },
|
|
171
|
+
include: ["**/*.spec.ts"],
|
|
176
172
|
mockDataModel: true,
|
|
177
|
-
outDir: "out
|
|
173
|
+
outDir: "out/src/client",
|
|
178
174
|
}),
|
|
179
175
|
},
|
|
180
176
|
{
|
|
181
177
|
test: defineProject({
|
|
182
|
-
displayName: { name: "
|
|
183
|
-
include: ["
|
|
184
|
-
|
|
185
|
-
outDir: "out-test/test",
|
|
178
|
+
displayName: { name: "server", color: "white" },
|
|
179
|
+
include: ["**/*.spec.ts"],
|
|
180
|
+
outDir: "out/src/server",
|
|
186
181
|
}),
|
|
187
182
|
},
|
|
188
183
|
],
|
|
189
184
|
});
|
|
190
185
|
```
|
|
191
186
|
|
|
192
|
-
Available per-project fields: `displayName`, `include`, `exclude`, `testMatch`,
|
|
193
|
-
`testRegex`, `testPathIgnorePatterns`, `setupFiles`, `setupFilesAfterEnv`,
|
|
194
|
-
`testTimeout`, `slowTestThreshold`, `testEnvironment`, `snapshotFormat`,
|
|
195
|
-
`outDir`, `root`, and the Jest mock flags (`clearMocks`, `resetMocks`, etc.).
|
|
196
|
-
|
|
197
187
|
### Full example
|
|
198
188
|
|
|
199
189
|
```typescript
|
|
@@ -233,7 +223,18 @@ You need these environment variables:
|
|
|
233
223
|
### Studio (local)
|
|
234
224
|
|
|
235
225
|
Connects to Roblox Studio over WebSocket. Faster than Open Cloud (no upload
|
|
236
|
-
step), but Studio must be open with the plugin running.
|
|
226
|
+
step), but Studio must be open with the plugin running. Studio doesn't expose which place is open, so
|
|
227
|
+
multiple concurrent projects aren't supported yet.
|
|
228
|
+
|
|
229
|
+
Install the plugin with [Drillbit](https://github.com/jacktabscode/drillbit):
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
drillbit install christopher-buss/jest-roblox-cli
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Or download `JestRobloxRunner.rbxm` from the
|
|
236
|
+
[latest release](https://github.com/christopher-buss/jest-roblox-cli/releases)
|
|
237
|
+
and drop it into your Studio plugins folder.
|
|
237
238
|
|
|
238
239
|
## CLI flags
|
|
239
240
|
|
|
@@ -290,54 +291,9 @@ Default `testMatch` patterns (configurable):
|
|
|
290
291
|
- Luau: `*.spec.lua`, `*.test.lua`, `*.spec.luau`, `*.test.luau`
|
|
291
292
|
- Type tests: `*.spec-d.ts`, `*.test-d.ts`
|
|
292
293
|
|
|
293
|
-
## Project structure
|
|
294
|
-
|
|
295
|
-
```text
|
|
296
|
-
jest-roblox-cli/
|
|
297
|
-
├── bin/ CLI entry point
|
|
298
|
-
├── src/
|
|
299
|
-
│ ├── backends/ Open Cloud and Studio backends
|
|
300
|
-
│ ├── config/ Config loading and validation
|
|
301
|
-
│ ├── coverage/ Coverage instrumentation pipeline
|
|
302
|
-
│ ├── formatters/ Output formatters (default, agent, JSON, GitHub Actions)
|
|
303
|
-
│ ├── highlighter/ Luau syntax highlighting
|
|
304
|
-
│ ├── reporter/ Result parsing and validation
|
|
305
|
-
│ ├── source-mapper/ Luau-to-TypeScript error mapping
|
|
306
|
-
│ ├── snapshot/ Snapshot file handling
|
|
307
|
-
│ ├── typecheck/ Type test runner
|
|
308
|
-
│ ├── types/ Shared type definitions
|
|
309
|
-
│ └── utils/ Helpers (glob, hash, cache, paths)
|
|
310
|
-
├── luau/ Luau code that runs inside Roblox
|
|
311
|
-
├── plugin/ Roblox Studio WebSocket plugin
|
|
312
|
-
└── test/ Test fixtures and mocks
|
|
313
|
-
```
|
|
314
|
-
|
|
315
294
|
## Contributing
|
|
316
295
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
```bash
|
|
320
|
-
pnpm build # Full build
|
|
321
|
-
pnpm watch # Watch mode
|
|
322
|
-
pnpm typecheck # Check types
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### Test
|
|
326
|
-
|
|
327
|
-
```bash
|
|
328
|
-
vitest run # All tests
|
|
329
|
-
vitest run src/formatters # One folder
|
|
330
|
-
vitest run src/cli.spec.ts # One file
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### Lint
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
eslint .
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
> [!IMPORTANT]
|
|
340
|
-
> 100% test coverage is enforced. Write tests first. Every PR must maintain full coverage.
|
|
296
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
341
297
|
|
|
342
298
|
## License
|
|
343
299
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { D as createOpenCloudBackend, I as isValidBackend, L as LuauScriptError, M as ROOT_ONLY_KEYS, N as VALID_BACKENDS, O as hashBuffer, S as loadConfig$1, T as createStudioBackend, _ as formatResult, a as formatAnnotations, b as formatBanner, c as execute, d as findFormatterOptions, g as formatMultiProjectResult, i as runTypecheck, l as formatExecuteOutput, m as formatCompactMultiProject, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, s as resolveGitHubActionsOptions, t as formatGameOutputNotice, u as loadCoverageManifest, x as rojoProjectSchema, y as formatTypecheckSummary } from "./game-output-
|
|
1
|
+
import { D as createOpenCloudBackend, I as isValidBackend, L as LuauScriptError, M as ROOT_ONLY_KEYS, N as VALID_BACKENDS, O as hashBuffer, S as loadConfig$1, T as createStudioBackend, _ as formatResult, a as formatAnnotations, b as formatBanner, c as execute, d as findFormatterOptions, g as formatMultiProjectResult, i as runTypecheck, l as formatExecuteOutput, m as formatCompactMultiProject, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, s as resolveGitHubActionsOptions, t as formatGameOutputNotice, u as loadCoverageManifest, x as rojoProjectSchema, y as formatTypecheckSummary } from "./game-output-BMGxhjkE.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { type } from "arktype";
|
|
4
4
|
import assert from "node:assert";
|
|
5
5
|
import * as fs$1 from "node:fs";
|
|
6
|
-
import fs from "node:fs";
|
|
6
|
+
import fs, { readFileSync } from "node:fs";
|
|
7
7
|
import * as path$1 from "node:path";
|
|
8
|
-
import path from "node:path";
|
|
8
|
+
import path, { dirname, join } from "node:path";
|
|
9
9
|
import process from "node:process";
|
|
10
10
|
import { parseArgs as parseArgs$1 } from "node:util";
|
|
11
11
|
import { isAgent } from "std-env";
|
|
@@ -23,7 +23,7 @@ import istanbulCoverage from "istanbul-lib-coverage";
|
|
|
23
23
|
import istanbulReport from "istanbul-lib-report";
|
|
24
24
|
import istanbulReports from "istanbul-reports";
|
|
25
25
|
//#region package.json
|
|
26
|
-
var version = "0.1.
|
|
26
|
+
var version = "0.1.2";
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region src/backends/auto.ts
|
|
29
29
|
var StudioWithFallback = class {
|
|
@@ -116,10 +116,39 @@ function stripTsExtension(pattern) {
|
|
|
116
116
|
}
|
|
117
117
|
//#endregion
|
|
118
118
|
//#region src/utils/rojo-tree.ts
|
|
119
|
+
function resolveNestedProjects(tree, rootDirectory) {
|
|
120
|
+
return resolveTree(tree, rootDirectory, /* @__PURE__ */ new Set());
|
|
121
|
+
}
|
|
119
122
|
function collectPaths(node, result) {
|
|
120
123
|
for (const [key, value] of Object.entries(node)) if (key === "$path" && typeof value === "string") result.push(value.replaceAll("\\", "/"));
|
|
121
124
|
else if (typeof value === "object" && !Array.isArray(value) && !key.startsWith("$")) collectPaths(value, result);
|
|
122
125
|
}
|
|
126
|
+
function resolveTree(node, rootDirectory, visited) {
|
|
127
|
+
const resolved = {};
|
|
128
|
+
for (const [key, value] of Object.entries(node)) {
|
|
129
|
+
if (key === "$path" && typeof value === "string" && value.endsWith(".project.json")) {
|
|
130
|
+
const projectPath = join(rootDirectory, value);
|
|
131
|
+
if (visited.has(projectPath)) throw new Error(`Circular project reference: ${value}`);
|
|
132
|
+
const chain = new Set(visited);
|
|
133
|
+
chain.add(projectPath);
|
|
134
|
+
let content;
|
|
135
|
+
try {
|
|
136
|
+
content = readFileSync(projectPath, "utf-8");
|
|
137
|
+
} catch (err) {
|
|
138
|
+
throw new Error(`Could not read nested Rojo project: ${value}`, { cause: err });
|
|
139
|
+
}
|
|
140
|
+
const innerTree = resolveTree(JSON.parse(content).tree, dirname(projectPath), chain);
|
|
141
|
+
for (const [innerKey, innerValue] of Object.entries(innerTree)) resolved[innerKey] = innerValue;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
|
|
145
|
+
resolved[key] = value;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
resolved[key] = resolveTree(value, rootDirectory, visited);
|
|
149
|
+
}
|
|
150
|
+
return resolved;
|
|
151
|
+
}
|
|
123
152
|
//#endregion
|
|
124
153
|
//#region src/config/projects.ts
|
|
125
154
|
function extractStaticRoot(pattern) {
|
|
@@ -1653,8 +1682,12 @@ function writeManifest(manifestPath, allFiles, luauRoots, placeFile) {
|
|
|
1653
1682
|
function buildRojoProject(rojoProjectPath, roots, placeFile) {
|
|
1654
1683
|
const rojoProjectRaw = rojoProjectSchema(JSON.parse(fs$1.readFileSync(rojoProjectPath, "utf-8")));
|
|
1655
1684
|
if (rojoProjectRaw instanceof type.errors) throw new Error(`Malformed Rojo project JSON: ${rojoProjectRaw.toString()}`);
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1685
|
+
const projectRelocation = path$1.relative(COVERAGE_DIR, path$1.dirname(rojoProjectPath)).replaceAll("\\", "/");
|
|
1686
|
+
const rewritten = rewriteRojoProject({
|
|
1687
|
+
...rojoProjectRaw,
|
|
1688
|
+
tree: resolveNestedProjects(rojoProjectRaw.tree, path$1.dirname(rojoProjectPath))
|
|
1689
|
+
}, {
|
|
1690
|
+
projectRelocation,
|
|
1658
1691
|
roots
|
|
1659
1692
|
});
|
|
1660
1693
|
const rewrittenProjectPath = path$1.join(COVERAGE_DIR, path$1.basename(rojoProjectPath));
|
|
@@ -1798,7 +1831,7 @@ function globSync(pattern, options = {}) {
|
|
|
1798
1831
|
return walkDirectory(cwd, cwd).filter((file) => matchesGlobPattern(file, pattern));
|
|
1799
1832
|
}
|
|
1800
1833
|
function matchesGlobPattern(filePath, pattern) {
|
|
1801
|
-
const regexPattern = pattern.replace(/\./g, "\\.").replace(
|
|
1834
|
+
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*\//g, "{{DOUBLESTAR_SLASH}}").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLESTAR_SLASH\}\}/g, "(.+/)?");
|
|
1802
1835
|
return new RegExp(`^${regexPattern}$`).test(filePath);
|
|
1803
1836
|
}
|
|
1804
1837
|
function walkDirectory(directoryPath, baseDirectory) {
|
|
@@ -2095,7 +2128,11 @@ function printFinalStatus(passed) {
|
|
|
2095
2128
|
process.stdout.write(`${badge}\n`);
|
|
2096
2129
|
}
|
|
2097
2130
|
function processCoverage(config, coverageData) {
|
|
2098
|
-
if (!config.collectCoverage
|
|
2131
|
+
if (!config.collectCoverage) return true;
|
|
2132
|
+
if (coverageData === void 0) {
|
|
2133
|
+
if (!config.silent) process.stderr.write("Warning: coverage data was empty — the Rojo project may point at uninstrumented source\n");
|
|
2134
|
+
return true;
|
|
2135
|
+
}
|
|
2099
2136
|
const manifest = loadCoverageManifest(config.rootDir);
|
|
2100
2137
|
if (manifest === void 0) {
|
|
2101
2138
|
if (!config.silent) process.stderr.write("Warning: Coverage manifest not found, skipping TS mapping\n");
|
|
@@ -2277,7 +2314,7 @@ function loadRojoTree(config) {
|
|
|
2277
2314
|
const content = fs$1.readFileSync(rojoPath, "utf8");
|
|
2278
2315
|
const validated = rojoProjectSchema(JSON.parse(content));
|
|
2279
2316
|
if (validated instanceof type.errors) throw new Error(`Invalid Rojo project: ${validated.summary}`);
|
|
2280
|
-
return validated.tree;
|
|
2317
|
+
return resolveNestedProjects(validated.tree, path$1.dirname(rojoPath));
|
|
2281
2318
|
}
|
|
2282
2319
|
const STUB_SKIP_KEYS = new Set([
|
|
2283
2320
|
"outDir",
|
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { type } from "arktype";
|
|
3
3
|
import assert from "node:assert";
|
|
4
4
|
import * as fs$1 from "node:fs";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import * as path$1 from "node:path";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import process from "node:process";
|
|
@@ -780,6 +780,7 @@ async function loadConfig$1(configPath, cwd = process.cwd()) {
|
|
|
780
780
|
cwd,
|
|
781
781
|
dotenv: false,
|
|
782
782
|
globalRc: false,
|
|
783
|
+
import: isSea() ? seaImport : void 0,
|
|
783
784
|
merger,
|
|
784
785
|
omit$Keys: true,
|
|
785
786
|
packageJson: false,
|
|
@@ -799,6 +800,16 @@ async function loadConfig$1(configPath, cwd = process.cwd()) {
|
|
|
799
800
|
config.rootDir ??= cwd;
|
|
800
801
|
return resolveConfig(config);
|
|
801
802
|
}
|
|
803
|
+
function isSea() {
|
|
804
|
+
return process.env["JEST_ROBLOX_SEA"] === "true";
|
|
805
|
+
}
|
|
806
|
+
async function seaImport(id) {
|
|
807
|
+
if (id.endsWith(".json")) {
|
|
808
|
+
const content = readFileSync(id, "utf-8");
|
|
809
|
+
return JSON.parse(content);
|
|
810
|
+
}
|
|
811
|
+
return import(id);
|
|
812
|
+
}
|
|
802
813
|
function merger(...sources) {
|
|
803
814
|
return defuFn(...sources.filter(Boolean));
|
|
804
815
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as generateTestScript, C as resolveConfig, D as createOpenCloudBackend, E as OpenCloudBackend, F as defineProject, M as ROOT_ONLY_KEYS, P as defineConfig, R as extractJsonFromOutput, S as loadConfig, T as createStudioBackend, _ as formatResult, a as formatAnnotations, c as execute, f as formatJson, h as formatFailure, i as runTypecheck, j as DEFAULT_CONFIG, k as buildJestArgv, l as formatExecuteOutput, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, t as formatGameOutputNotice, v as formatTestSummary, w as StudioBackend, z as parseJestOutput } from "./game-output-
|
|
1
|
+
import { A as generateTestScript, C as resolveConfig, D as createOpenCloudBackend, E as OpenCloudBackend, F as defineProject, M as ROOT_ONLY_KEYS, P as defineConfig, R as extractJsonFromOutput, S as loadConfig, T as createStudioBackend, _ as formatResult, a as formatAnnotations, c as execute, f as formatJson, h as formatFailure, i as runTypecheck, j as DEFAULT_CONFIG, k as buildJestArgv, l as formatExecuteOutput, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, t as formatGameOutputNotice, v as formatTestSummary, w as StudioBackend, z as parseJestOutput } from "./game-output-BMGxhjkE.mjs";
|
|
2
2
|
export { DEFAULT_CONFIG, OpenCloudBackend, ROOT_ONLY_KEYS, StudioBackend, buildJestArgv, createOpenCloudBackend, createStudioBackend, defineConfig, defineProject, execute, extractJsonFromOutput, formatAnnotations, formatExecuteOutput, formatFailure, formatGameOutputNotice, formatJobSummary, formatJson, formatResult, formatTestSummary, generateTestScript, loadConfig, parseGameOutput, parseJestOutput, resolveConfig, runTypecheck, writeGameOutput, writeJsonFile };
|
package/dist/sea/jest-roblox
CHANGED
|
Binary file
|
package/dist/sea-entry.cjs
CHANGED
|
@@ -7640,7 +7640,7 @@ function f$9() {
|
|
|
7640
7640
|
var C$5 = f$9();
|
|
7641
7641
|
//#endregion
|
|
7642
7642
|
//#region package.json
|
|
7643
|
-
var version = "0.1.
|
|
7643
|
+
var version = "0.1.2";
|
|
7644
7644
|
//#endregion
|
|
7645
7645
|
//#region node_modules/.pnpm/ws@8.18.0/node_modules/ws/lib/stream.js
|
|
7646
7646
|
var require_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -11358,7 +11358,7 @@ function normalizeString(path, allowAboveRoot) {
|
|
|
11358
11358
|
}
|
|
11359
11359
|
return res;
|
|
11360
11360
|
}
|
|
11361
|
-
var _DRIVE_LETTER_START_RE, _UNC_REGEX, _IS_ABSOLUTE_RE, _DRIVE_LETTER_RE, _ROOT_FOLDER_RE, _EXTNAME_RE, normalize, join, resolve$2, isAbsolute$1, extname$2, relative, dirname, basename;
|
|
11361
|
+
var _DRIVE_LETTER_START_RE, _UNC_REGEX, _IS_ABSOLUTE_RE, _DRIVE_LETTER_RE, _ROOT_FOLDER_RE, _EXTNAME_RE, normalize, join$2, resolve$2, isAbsolute$1, extname$2, relative, dirname$1, basename;
|
|
11362
11362
|
var init_pathe_M_eThtNZ = __esmMin((() => {
|
|
11363
11363
|
_DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
11364
11364
|
_UNC_REGEX = /^[/\\]{2}/;
|
|
@@ -11385,7 +11385,7 @@ var init_pathe_M_eThtNZ = __esmMin((() => {
|
|
|
11385
11385
|
}
|
|
11386
11386
|
return isPathAbsolute && !isAbsolute$1(path) ? `/${path}` : path;
|
|
11387
11387
|
};
|
|
11388
|
-
join = function(...segments) {
|
|
11388
|
+
join$2 = function(...segments) {
|
|
11389
11389
|
let path = "";
|
|
11390
11390
|
for (const seg of segments) {
|
|
11391
11391
|
if (!seg) continue;
|
|
@@ -11432,7 +11432,7 @@ var init_pathe_M_eThtNZ = __esmMin((() => {
|
|
|
11432
11432
|
}
|
|
11433
11433
|
return [..._from.map(() => ".."), ..._to].join("/");
|
|
11434
11434
|
};
|
|
11435
|
-
dirname = function(p) {
|
|
11435
|
+
dirname$1 = function(p) {
|
|
11436
11436
|
const segments = normalizeWindowsPath$1(p).replace(/\/$/, "").split("/").slice(0, -1);
|
|
11437
11437
|
if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) segments[0] += "/";
|
|
11438
11438
|
return segments.join("/") || (isAbsolute$1(p) ? "/" : ".");
|
|
@@ -13115,11 +13115,11 @@ async function findFile(filename, _options = {}) {
|
|
|
13115
13115
|
let root = segments.findIndex((r) => r.match(options.rootPattern));
|
|
13116
13116
|
if (root === -1) root = 0;
|
|
13117
13117
|
if (options.reverse) for (let index = root + 1; index <= segments.length; index++) for (const filename2 of filenames) {
|
|
13118
|
-
const filePath = join(...segments.slice(0, index), filename2);
|
|
13118
|
+
const filePath = join$2(...segments.slice(0, index), filename2);
|
|
13119
13119
|
if (await options.test(filePath)) return filePath;
|
|
13120
13120
|
}
|
|
13121
13121
|
else for (let index = segments.length; index > root; index--) for (const filename2 of filenames) {
|
|
13122
|
-
const filePath = join(...segments.slice(0, index), filename2);
|
|
13122
|
+
const filePath = join$2(...segments.slice(0, index), filename2);
|
|
13123
13123
|
if (await options.test(filePath)) return filePath;
|
|
13124
13124
|
}
|
|
13125
13125
|
throw new Error(`Cannot find matching ${filename} in ${options.startingFrom} or parent directories`);
|
|
@@ -13179,10 +13179,10 @@ async function resolvePackageJSON(id = process.cwd(), options = {}) {
|
|
|
13179
13179
|
});
|
|
13180
13180
|
}
|
|
13181
13181
|
const workspaceTests = {
|
|
13182
|
-
workspaceFile: (opts) => findFile(workspaceFiles, opts).then((r) => dirname(r)),
|
|
13182
|
+
workspaceFile: (opts) => findFile(workspaceFiles, opts).then((r) => dirname$1(r)),
|
|
13183
13183
|
gitConfig: (opts) => findFile(".git/config", opts).then((r) => resolve$2(r, "../..")),
|
|
13184
|
-
lockFile: (opts) => findFile(lockFiles, opts).then((r) => dirname(r)),
|
|
13185
|
-
packageJson: (opts) => findFile(packageFiles, opts).then((r) => dirname(r))
|
|
13184
|
+
lockFile: (opts) => findFile(lockFiles, opts).then((r) => dirname$1(r)),
|
|
13185
|
+
packageJson: (opts) => findFile(packageFiles, opts).then((r) => dirname$1(r))
|
|
13186
13186
|
};
|
|
13187
13187
|
async function findWorkspaceDir(id = process.cwd(), options = {}) {
|
|
13188
13188
|
const startingFrom = _resolvePath(id, options);
|
|
@@ -17263,7 +17263,7 @@ function parsePackageManagerField(packageManager) {
|
|
|
17263
17263
|
async function detectPackageManager(cwd, options = {}) {
|
|
17264
17264
|
const detected = await findup(resolve$2(cwd || "."), async (path) => {
|
|
17265
17265
|
if (!options.ignorePackageJSON) {
|
|
17266
|
-
const packageJSONPath = join(path, "package.json");
|
|
17266
|
+
const packageJSONPath = join$2(path, "package.json");
|
|
17267
17267
|
if ((0, node_fs.existsSync)(packageJSONPath)) {
|
|
17268
17268
|
const packageJSON = JSON.parse(await (0, node_fs_promises.readFile)(packageJSONPath, "utf8"));
|
|
17269
17269
|
if (packageJSON?.packageManager) {
|
|
@@ -17284,7 +17284,7 @@ async function detectPackageManager(cwd, options = {}) {
|
|
|
17284
17284
|
}
|
|
17285
17285
|
}
|
|
17286
17286
|
}
|
|
17287
|
-
if ((0, node_fs.existsSync)(join(path, "deno.json"))) return packageManagers.find((pm) => pm.name === "deno");
|
|
17287
|
+
if ((0, node_fs.existsSync)(join$2(path, "deno.json"))) return packageManagers.find((pm) => pm.name === "deno");
|
|
17288
17288
|
}
|
|
17289
17289
|
if (!options.ignoreLockFile) {
|
|
17290
17290
|
for (const packageManager of packageManagers) if ([packageManager.lockFile, packageManager.files].flat().filter(Boolean).some((file) => (0, node_fs.existsSync)(resolve$2(path, file)))) return { ...packageManager };
|
|
@@ -35752,7 +35752,7 @@ async function downloadTemplate(input, options = {}) {
|
|
|
35752
35752
|
const tarPath = resolve$2(resolve$2(cacheDirectory(), providerName, template.name), (template.version || template.name) + ".tar.gz");
|
|
35753
35753
|
if (options.preferOffline && (0, node_fs.existsSync)(tarPath)) options.offline = true;
|
|
35754
35754
|
if (!options.offline) {
|
|
35755
|
-
await (0, node_fs_promises.mkdir)(dirname(tarPath), { recursive: true });
|
|
35755
|
+
await (0, node_fs_promises.mkdir)(dirname$1(tarPath), { recursive: true });
|
|
35756
35756
|
const s2 = Date.now();
|
|
35757
35757
|
await download(template.tar, tarPath, { headers: {
|
|
35758
35758
|
Authorization: options.auth ? `Bearer ${options.auth}` : void 0,
|
|
@@ -40894,9 +40894,9 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
|
|
|
40894
40894
|
const cloneName = source.replace(/\W+/g, "_").split("_").splice(0, 3).join("_") + "_" + digest(source).slice(0, 10).replace(/[-_]/g, "");
|
|
40895
40895
|
let cloneDir;
|
|
40896
40896
|
const localNodeModules = resolve$2(options.cwd, "node_modules");
|
|
40897
|
-
const parentDir = dirname(options.cwd);
|
|
40898
|
-
if (basename(parentDir) === ".c12") cloneDir = join(parentDir, cloneName);
|
|
40899
|
-
else if ((0, node_fs.existsSync)(localNodeModules)) cloneDir = join(localNodeModules, ".c12", cloneName);
|
|
40897
|
+
const parentDir = dirname$1(options.cwd);
|
|
40898
|
+
if (basename(parentDir) === ".c12") cloneDir = join$2(parentDir, cloneName);
|
|
40899
|
+
else if ((0, node_fs.existsSync)(localNodeModules)) cloneDir = join$2(localNodeModules, ".c12", cloneName);
|
|
40900
40900
|
else cloneDir = process.env.XDG_CACHE_HOME ? resolve$2(process.env.XDG_CACHE_HOME, "c12", cloneName) : resolve$2((0, node_os.homedir)(), ".cache/c12", cloneName);
|
|
40901
40901
|
if ((0, node_fs.existsSync)(cloneDir) && !sourceOptions.install) await (0, node_fs_promises.rm)(cloneDir, { recursive: true });
|
|
40902
40902
|
source = (await downloadTemplate(source, {
|
|
@@ -40911,7 +40911,7 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
|
|
|
40911
40911
|
if (NPM_PACKAGE_RE.test(source)) source = tryResolve(source, options) || source;
|
|
40912
40912
|
const ext = extname$2(source);
|
|
40913
40913
|
const isDir = _isDirectory(resolve$2(options.cwd, source)) ?? (!ext || ext === basename(source));
|
|
40914
|
-
const cwd = resolve$2(options.cwd, isDir ? source : dirname(source));
|
|
40914
|
+
const cwd = resolve$2(options.cwd, isDir ? source : dirname$1(source));
|
|
40915
40915
|
if (isDir) source = options.configFile;
|
|
40916
40916
|
const res = {
|
|
40917
40917
|
config: void 0,
|
|
@@ -40935,7 +40935,7 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
|
|
|
40935
40935
|
const { createJiti } = await Promise.resolve().then(() => (init_jiti(), jiti_exports)).catch(() => {
|
|
40936
40936
|
throw new Error(`Failed to load config file \`${res.configFile}\`: ${error?.message}. Hint install \`jiti\` for compatibility.`, { cause: error });
|
|
40937
40937
|
});
|
|
40938
|
-
const jiti = createJiti(join(options.cwd || ".", options.configFile || "/"), {
|
|
40938
|
+
const jiti = createJiti(join$2(options.cwd || ".", options.configFile || "/"), {
|
|
40939
40939
|
interopDefault: true,
|
|
40940
40940
|
moduleCache: false,
|
|
40941
40941
|
extensions: [...SUPPORTED_EXTENSIONS]
|
|
@@ -40963,7 +40963,7 @@ async function resolveConfig$1(source, options, sourceOptions = {}) {
|
|
|
40963
40963
|
function tryResolve(id, options) {
|
|
40964
40964
|
const res = resolveModulePath(id, {
|
|
40965
40965
|
try: true,
|
|
40966
|
-
from: (0, node_url.pathToFileURL)(join(options.cwd || ".", options.configFile || "/")),
|
|
40966
|
+
from: (0, node_url.pathToFileURL)(join$2(options.cwd || ".", options.configFile || "/")),
|
|
40967
40967
|
suffixes: ["", "/index"],
|
|
40968
40968
|
extensions: SUPPORTED_EXTENSIONS,
|
|
40969
40969
|
cache: false
|
|
@@ -41654,6 +41654,7 @@ async function loadConfig(configPath, cwd = node_process.default.cwd()) {
|
|
|
41654
41654
|
cwd,
|
|
41655
41655
|
dotenv: false,
|
|
41656
41656
|
globalRc: false,
|
|
41657
|
+
import: isSea() ? seaImport : void 0,
|
|
41657
41658
|
merger,
|
|
41658
41659
|
omit$Keys: true,
|
|
41659
41660
|
packageJson: false,
|
|
@@ -41673,6 +41674,16 @@ async function loadConfig(configPath, cwd = node_process.default.cwd()) {
|
|
|
41673
41674
|
config.rootDir ??= cwd;
|
|
41674
41675
|
return resolveConfig(config);
|
|
41675
41676
|
}
|
|
41677
|
+
function isSea() {
|
|
41678
|
+
return node_process.default.env["JEST_ROBLOX_SEA"] === "true";
|
|
41679
|
+
}
|
|
41680
|
+
async function seaImport(id) {
|
|
41681
|
+
if (id.endsWith(".json")) {
|
|
41682
|
+
const content = (0, node_fs.readFileSync)(id, "utf-8");
|
|
41683
|
+
return JSON.parse(content);
|
|
41684
|
+
}
|
|
41685
|
+
return import(id);
|
|
41686
|
+
}
|
|
41676
41687
|
function merger(...sources) {
|
|
41677
41688
|
return defuFn(...sources.filter(Boolean));
|
|
41678
41689
|
}
|
|
@@ -41691,10 +41702,39 @@ function stripTsExtension(pattern) {
|
|
|
41691
41702
|
}
|
|
41692
41703
|
//#endregion
|
|
41693
41704
|
//#region src/utils/rojo-tree.ts
|
|
41705
|
+
function resolveNestedProjects(tree, rootDirectory) {
|
|
41706
|
+
return resolveTree(tree, rootDirectory, /* @__PURE__ */ new Set());
|
|
41707
|
+
}
|
|
41694
41708
|
function collectPaths(node, result) {
|
|
41695
41709
|
for (const [key, value] of Object.entries(node)) if (key === "$path" && typeof value === "string") result.push(value.replaceAll("\\", "/"));
|
|
41696
41710
|
else if (typeof value === "object" && !Array.isArray(value) && !key.startsWith("$")) collectPaths(value, result);
|
|
41697
41711
|
}
|
|
41712
|
+
function resolveTree(node, rootDirectory, visited) {
|
|
41713
|
+
const resolved = {};
|
|
41714
|
+
for (const [key, value] of Object.entries(node)) {
|
|
41715
|
+
if (key === "$path" && typeof value === "string" && value.endsWith(".project.json")) {
|
|
41716
|
+
const projectPath = (0, node_path.join)(rootDirectory, value);
|
|
41717
|
+
if (visited.has(projectPath)) throw new Error(`Circular project reference: ${value}`);
|
|
41718
|
+
const chain = new Set(visited);
|
|
41719
|
+
chain.add(projectPath);
|
|
41720
|
+
let content;
|
|
41721
|
+
try {
|
|
41722
|
+
content = (0, node_fs.readFileSync)(projectPath, "utf-8");
|
|
41723
|
+
} catch (err) {
|
|
41724
|
+
throw new Error(`Could not read nested Rojo project: ${value}`, { cause: err });
|
|
41725
|
+
}
|
|
41726
|
+
const innerTree = resolveTree(JSON.parse(content).tree, (0, node_path.dirname)(projectPath), chain);
|
|
41727
|
+
for (const [innerKey, innerValue] of Object.entries(innerTree)) resolved[innerKey] = innerValue;
|
|
41728
|
+
continue;
|
|
41729
|
+
}
|
|
41730
|
+
if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
|
|
41731
|
+
resolved[key] = value;
|
|
41732
|
+
continue;
|
|
41733
|
+
}
|
|
41734
|
+
resolved[key] = resolveTree(value, rootDirectory, visited);
|
|
41735
|
+
}
|
|
41736
|
+
return resolved;
|
|
41737
|
+
}
|
|
41698
41738
|
//#endregion
|
|
41699
41739
|
//#region src/config/projects.ts
|
|
41700
41740
|
function extractStaticRoot(pattern) {
|
|
@@ -49643,8 +49683,8 @@ var import_RojoResolver = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
49643
49683
|
this.value = value;
|
|
49644
49684
|
}
|
|
49645
49685
|
};
|
|
49646
|
-
|
|
49647
|
-
const validateRojo = new Lazy(() => ajv.compile(JSON.parse(
|
|
49686
|
+
path_1.default.join(PACKAGE_ROOT, "rojo-schema.json");
|
|
49687
|
+
const validateRojo = new Lazy(() => ajv.compile(JSON.parse("{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"required\": [\"name\", \"tree\"],\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"servePort\": {\n \"type\": \"integer\"\n },\n \"tree\": {\n \"$id\": \"tree\",\n \"type\": \"object\",\n \"properties\": {\n \"$className\": {\n \"type\": \"string\"\n },\n \"$ignoreUnknownInstances\": {\n \"type\": \"boolean\"\n },\n \"$path\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"optional\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n },\n \"$properties\": {\n \"type\": \"object\"\n }\n },\n \"patternProperties\": {\n \"^[^\\\\$].*$\": { \"$ref\": \"tree\" }\n }\n }\n }\n}\n")));
|
|
49648
49688
|
function isValidRojoConfig(value) {
|
|
49649
49689
|
return validateRojo.get()(value) === true;
|
|
49650
49690
|
}
|
|
@@ -54130,8 +54170,12 @@ function writeManifest(manifestPath, allFiles, luauRoots, placeFile) {
|
|
|
54130
54170
|
function buildRojoProject(rojoProjectPath, roots, placeFile) {
|
|
54131
54171
|
const rojoProjectRaw = rojoProjectSchema(JSON.parse(node_fs.readFileSync(rojoProjectPath, "utf-8")));
|
|
54132
54172
|
if (rojoProjectRaw instanceof type.errors) throw new Error(`Malformed Rojo project JSON: ${rojoProjectRaw.toString()}`);
|
|
54133
|
-
const
|
|
54134
|
-
|
|
54173
|
+
const projectRelocation = node_path.relative(COVERAGE_DIR, node_path.dirname(rojoProjectPath)).replaceAll("\\", "/");
|
|
54174
|
+
const rewritten = rewriteRojoProject({
|
|
54175
|
+
...rojoProjectRaw,
|
|
54176
|
+
tree: resolveNestedProjects(rojoProjectRaw.tree, node_path.dirname(rojoProjectPath))
|
|
54177
|
+
}, {
|
|
54178
|
+
projectRelocation,
|
|
54135
54179
|
roots
|
|
54136
54180
|
});
|
|
54137
54181
|
const rewrittenProjectPath = node_path.join(COVERAGE_DIR, node_path.basename(rojoProjectPath));
|
|
@@ -60807,7 +60851,7 @@ function globSync(pattern, options = {}) {
|
|
|
60807
60851
|
return walkDirectory(cwd, cwd).filter((file) => matchesGlobPattern(file, pattern));
|
|
60808
60852
|
}
|
|
60809
60853
|
function matchesGlobPattern(filePath, pattern) {
|
|
60810
|
-
const regexPattern = pattern.replace(/\./g, "\\.").replace(
|
|
60854
|
+
const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*\//g, "{{DOUBLESTAR_SLASH}}").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLESTAR_SLASH\}\}/g, "(.+/)?");
|
|
60811
60855
|
return new RegExp(`^${regexPattern}$`).test(filePath);
|
|
60812
60856
|
}
|
|
60813
60857
|
function walkDirectory(directoryPath, baseDirectory) {
|
|
@@ -61104,7 +61148,11 @@ function printFinalStatus(passed) {
|
|
|
61104
61148
|
node_process.default.stdout.write(`${badge}\n`);
|
|
61105
61149
|
}
|
|
61106
61150
|
function processCoverage(config, coverageData) {
|
|
61107
|
-
if (!config.collectCoverage
|
|
61151
|
+
if (!config.collectCoverage) return true;
|
|
61152
|
+
if (coverageData === void 0) {
|
|
61153
|
+
if (!config.silent) node_process.default.stderr.write("Warning: coverage data was empty — the Rojo project may point at uninstrumented source\n");
|
|
61154
|
+
return true;
|
|
61155
|
+
}
|
|
61108
61156
|
const manifest = loadCoverageManifest(config.rootDir);
|
|
61109
61157
|
if (manifest === void 0) {
|
|
61110
61158
|
if (!config.silent) node_process.default.stderr.write("Warning: Coverage manifest not found, skipping TS mapping\n");
|
|
@@ -61286,7 +61334,7 @@ function loadRojoTree(config) {
|
|
|
61286
61334
|
const content = node_fs.readFileSync(rojoPath, "utf8");
|
|
61287
61335
|
const validated = rojoProjectSchema(JSON.parse(content));
|
|
61288
61336
|
if (validated instanceof type.errors) throw new Error(`Invalid Rojo project: ${validated.summary}`);
|
|
61289
|
-
return validated.tree;
|
|
61337
|
+
return resolveNestedProjects(validated.tree, node_path.dirname(rojoPath));
|
|
61290
61338
|
}
|
|
61291
61339
|
const STUB_SKIP_KEYS = new Set([
|
|
61292
61340
|
"outDir",
|