@isentinel/jest-roblox 0.0.1

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.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../dist/cli.mjs";
3
+
4
+ main();
package/dist/cli.d.mts ADDED
@@ -0,0 +1,8 @@
1
+ import { t as CliOptions } from "./schema-ryuVGD35.mjs";
2
+
3
+ //#region src/cli.d.ts
4
+ declare function main(): Promise<void>;
5
+ declare function parseArgs(args: Array<string>): CliOptions;
6
+ declare function run(args: Array<string>): Promise<number>;
7
+ //#endregion
8
+ export { main, parseArgs, run };
package/dist/cli.mjs ADDED
@@ -0,0 +1,281 @@
1
+ import { g as createOpenCloudBackend, i as execute, m as createStudioBackend, n as parseGameOutput, o as writeJsonFile, r as writeGameOutput, t as formatGameOutputNotice, u as loadConfig, y as LuauScriptError } from "./game-output-BKBGosEI.mjs";
2
+ import * as path$1 from "node:path";
3
+ import process from "node:process";
4
+ import { parseArgs as parseArgs$1 } from "node:util";
5
+ import * as fs from "node:fs";
6
+
7
+ //#region package.json
8
+ var version = "0.0.1";
9
+
10
+ //#endregion
11
+ //#region src/utils/glob.ts
12
+ function globSync(pattern, options = {}) {
13
+ const cwd = options.cwd ?? process.cwd();
14
+ return walkDirectory(cwd, cwd).filter((file) => matchesGlobPattern(file, pattern));
15
+ }
16
+ function matchesGlobPattern(filePath, pattern) {
17
+ const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLESTAR\}\}/g, ".*");
18
+ return new RegExp(`^${regexPattern}$`).test(filePath);
19
+ }
20
+ function walkDirectory(directoryPath, baseDirectory) {
21
+ const results = [];
22
+ try {
23
+ const entries = fs.readdirSync(directoryPath, { withFileTypes: true });
24
+ for (const entry of entries) {
25
+ const fullPath = path$1.join(directoryPath, entry.name);
26
+ const relativePath = path$1.relative(baseDirectory, fullPath).replace(/\\/g, "/");
27
+ if (entry.isDirectory()) {
28
+ if (!entry.name.startsWith(".") && entry.name !== "node_modules") results.push(...walkDirectory(fullPath, baseDirectory));
29
+ } else results.push(relativePath);
30
+ }
31
+ } catch {}
32
+ return results;
33
+ }
34
+
35
+ //#endregion
36
+ //#region src/cli.ts
37
+ const HELP_TEXT = `
38
+ Usage: jest-roblox [options] [files...]
39
+
40
+ Options:
41
+ --backend <type> Backend: "open-cloud" or "studio" (default: open-cloud)
42
+ --port <number> WebSocket port for studio backend (default: 3001)
43
+ --config <path> Path to config file
44
+ --testPathPattern <regex> Filter test files by path pattern
45
+ -t, --testNamePattern <regex> Filter tests by name pattern
46
+ --json Output results as JSON
47
+ --compact Token-efficient output for AI consumption
48
+ --compactMaxFailures <n> Max failures in compact mode (default: 10)
49
+ --outputFile <path> Write results to file
50
+ --gameOutput <path> Write game output (print/warn/error) to file
51
+ --sourceMap Map Luau stack traces to TypeScript source
52
+ --rojoProject <path> Path to rojo project file (auto-detected if not set)
53
+ --verbose Show individual test results
54
+ --silent Suppress output
55
+ --no-color Disable colored output
56
+ --no-cache Force re-upload place file (skip cache)
57
+ --pollInterval <ms> Open Cloud poll interval in ms (default: 500)
58
+ --projects <path...> DataModel paths to search for tests
59
+ --setupFiles <path...> DataModel paths to setup scripts
60
+ --no-show-luau Hide Luau code in failure output
61
+ --help Show this help message
62
+ --version Show version number
63
+
64
+ Environment Variables (open-cloud backend only):
65
+ ROBLOX_OPEN_CLOUD_API_KEY API key for Roblox Open Cloud
66
+ ROBLOX_UNIVERSE_ID Target universe ID
67
+ ROBLOX_PLACE_ID Target place ID
68
+
69
+ Examples:
70
+ jest-roblox Run all tests (open-cloud)
71
+ jest-roblox --backend studio Run tests via Studio plugin
72
+ jest-roblox src/player.spec.ts Run specific test file
73
+ jest-roblox -t "should spawn" Run tests matching pattern
74
+ jest-roblox --json --outputFile results.json
75
+ `;
76
+ async function main() {
77
+ const exitCode = await run(process.argv.slice(2));
78
+ process.exit(exitCode);
79
+ }
80
+ function parseArgs(args) {
81
+ const { positionals, values } = parseArgs$1({
82
+ allowPositionals: true,
83
+ args,
84
+ options: {
85
+ "backend": { type: "string" },
86
+ "cache": { type: "boolean" },
87
+ "color": { type: "boolean" },
88
+ "compact": { type: "boolean" },
89
+ "compactMaxFailures": { type: "string" },
90
+ "config": { type: "string" },
91
+ "gameOutput": { type: "string" },
92
+ "help": {
93
+ default: false,
94
+ type: "boolean"
95
+ },
96
+ "json": { type: "boolean" },
97
+ "no-cache": { type: "boolean" },
98
+ "no-color": { type: "boolean" },
99
+ "no-show-luau": { type: "boolean" },
100
+ "outputFile": { type: "string" },
101
+ "pollInterval": { type: "string" },
102
+ "port": { type: "string" },
103
+ "projects": {
104
+ multiple: true,
105
+ type: "string"
106
+ },
107
+ "rojoProject": { type: "string" },
108
+ "setupFiles": {
109
+ multiple: true,
110
+ type: "string"
111
+ },
112
+ "showLuau": { type: "boolean" },
113
+ "silent": { type: "boolean" },
114
+ "sourceMap": { type: "boolean" },
115
+ "testNamePattern": {
116
+ short: "t",
117
+ type: "string"
118
+ },
119
+ "testPathPattern": { type: "string" },
120
+ "timeout": { type: "string" },
121
+ "verbose": { type: "boolean" },
122
+ "version": {
123
+ default: false,
124
+ type: "boolean"
125
+ }
126
+ },
127
+ strict: true
128
+ });
129
+ const compactMaxFailures = values.compactMaxFailures !== void 0 ? Number.parseInt(values.compactMaxFailures, 10) : void 0;
130
+ const pollInterval = values.pollInterval !== void 0 ? Number.parseInt(values.pollInterval, 10) : void 0;
131
+ const port = values.port !== void 0 ? Number.parseInt(values.port, 10) : void 0;
132
+ const timeout = values.timeout !== void 0 ? Number.parseInt(values.timeout, 10) : void 0;
133
+ return {
134
+ backend: values.backend,
135
+ cache: values["no-cache"] === true ? false : values.cache,
136
+ color: values["no-color"] === true ? false : values.color,
137
+ compact: values.compact,
138
+ compactMaxFailures,
139
+ config: values.config,
140
+ files: positionals.length > 0 ? positionals : void 0,
141
+ gameOutput: values.gameOutput,
142
+ help: values.help,
143
+ json: values.json,
144
+ outputFile: values.outputFile,
145
+ pollInterval,
146
+ port,
147
+ projects: values.projects,
148
+ rojoProject: values.rojoProject,
149
+ setupFiles: values.setupFiles,
150
+ showLuau: values["no-show-luau"] === true ? false : values.showLuau,
151
+ silent: values.silent,
152
+ sourceMap: values.sourceMap,
153
+ testNamePattern: values.testNamePattern,
154
+ testPathPattern: values.testPathPattern,
155
+ timeout,
156
+ verbose: values.verbose,
157
+ version: values.version
158
+ };
159
+ }
160
+ async function run(args) {
161
+ try {
162
+ return await runInner(args);
163
+ } catch (err) {
164
+ printError(err);
165
+ return 2;
166
+ }
167
+ }
168
+ function printError(err) {
169
+ if (err instanceof LuauScriptError) {
170
+ const hint = getLuauErrorHint(err.message);
171
+ console.error(`\n Luau script error: ${err.message}`);
172
+ if (hint !== void 0) console.error(` Hint: ${hint}`);
173
+ console.error();
174
+ } else if (err instanceof Error) console.error(`Error: ${err.message}`);
175
+ else console.error("An unknown error occurred");
176
+ }
177
+ async function runInner(args) {
178
+ const cli = parseArgs(args);
179
+ if (cli.help === true) {
180
+ console.log(HELP_TEXT);
181
+ return 0;
182
+ }
183
+ if (cli.version === true) {
184
+ console.log(version);
185
+ return 0;
186
+ }
187
+ const config = mergeCliWithConfig(cli, await loadConfig(cli.config));
188
+ const discovery = discoverTestFiles(config, cli.files);
189
+ if (discovery.files.length === 0) {
190
+ console.error("No test files found");
191
+ return 2;
192
+ }
193
+ if (!config.silent && !config.compact && !config.json && discovery.files.length !== discovery.totalFiles) process.stderr.write(`Running ${discovery.files.length} of ${discovery.totalFiles} test files\n`);
194
+ const result = await execute({
195
+ backend: config.backend === "studio" ? createStudioBackend({
196
+ port: config.port,
197
+ timeout: config.timeout
198
+ }) : createOpenCloudBackend(),
199
+ config,
200
+ testFiles: discovery.files,
201
+ version
202
+ });
203
+ if (result.output) console.log(result.output);
204
+ if (config.outputFile !== void 0) await writeJsonFile(result.result, config.outputFile);
205
+ writeGameOutputIfConfigured(config, result.gameOutput);
206
+ return result.exitCode;
207
+ }
208
+ function writeGameOutputIfConfigured(config, gameOutput) {
209
+ if (config.gameOutput === void 0) return;
210
+ const entries = parseGameOutput(gameOutput);
211
+ writeGameOutput(config.gameOutput, entries);
212
+ if (!config.silent) {
213
+ const notice = formatGameOutputNotice(config.gameOutput, entries.length);
214
+ if (notice) console.error(notice);
215
+ }
216
+ }
217
+ const LUAU_ERROR_HINTS = [
218
+ [/Failed to find Jest instance in ReplicatedStorage/, "Set \"jestPath\" in your config to specify the Jest module location, e.g. \"ReplicatedStorage/rbxts_include/node_modules/@rbxts/jest/src\""],
219
+ [/Failed to find Jest instance at path/, "The configured jestPath does not resolve to a valid instance. Verify the path matches your Rojo project tree."],
220
+ [/Failed to find service/, "The first segment of jestPath must be a valid Roblox service name (e.g. ReplicatedStorage, ServerScriptService)."],
221
+ [/No projects configured/, "Set \"projects\" in jest.config.ts (e.g. [\"ReplicatedStorage/client\", \"ServerScriptService/server\"]) or pass --projects."]
222
+ ];
223
+ function discoverTestFiles(config, cliFiles) {
224
+ if (cliFiles && cliFiles.length > 0) {
225
+ const files = cliFiles.map((file) => path$1.resolve(config.rootDir, file));
226
+ return {
227
+ files,
228
+ totalFiles: files.length
229
+ };
230
+ }
231
+ const allFiles = [];
232
+ for (const pattern of config.testMatch) {
233
+ const matches = globSync(pattern, { cwd: config.rootDir });
234
+ allFiles.push(...matches);
235
+ }
236
+ const ignoredPatterns = config.testPathIgnorePatterns.map((pat) => new RegExp(pat));
237
+ const baseFiles = allFiles.filter((file) => {
238
+ return !ignoredPatterns.some((pattern) => pattern.test(file));
239
+ });
240
+ const totalFiles = new Set(baseFiles).size;
241
+ let filtered = baseFiles;
242
+ if (config.testPathPattern !== void 0) {
243
+ const pathPattern = new RegExp(config.testPathPattern);
244
+ filtered = filtered.filter((file) => pathPattern.test(file));
245
+ }
246
+ return {
247
+ files: [...new Set(filtered)],
248
+ totalFiles
249
+ };
250
+ }
251
+ function getLuauErrorHint(message) {
252
+ for (const [pattern, hint] of LUAU_ERROR_HINTS) if (pattern.test(message)) return hint;
253
+ }
254
+ function mergeCliWithConfig(cli, config) {
255
+ return {
256
+ ...config,
257
+ backend: cli.backend ?? config.backend,
258
+ cache: cli.cache ?? config.cache,
259
+ color: cli.color ?? config.color,
260
+ compact: cli.compact ?? config.compact,
261
+ compactMaxFailures: cli.compactMaxFailures ?? config.compactMaxFailures,
262
+ gameOutput: cli.gameOutput ?? config.gameOutput,
263
+ json: cli.json ?? config.json,
264
+ outputFile: cli.outputFile ?? config.outputFile,
265
+ pollInterval: cli.pollInterval ?? config.pollInterval,
266
+ port: cli.port ?? config.port,
267
+ projects: cli.projects ?? config.projects,
268
+ rojoProject: cli.rojoProject ?? config.rojoProject,
269
+ setupFiles: cli.setupFiles ?? config.setupFiles,
270
+ showLuau: cli.showLuau ?? config.showLuau,
271
+ silent: cli.silent ?? config.silent,
272
+ sourceMap: cli.sourceMap ?? config.sourceMap,
273
+ testNamePattern: cli.testNamePattern ?? config.testNamePattern,
274
+ testPathPattern: cli.testPathPattern ?? config.testPathPattern,
275
+ timeout: cli.timeout ?? config.timeout,
276
+ verbose: cli.verbose ?? config.verbose
277
+ };
278
+ }
279
+
280
+ //#endregion
281
+ export { main, parseArgs, run };