@quenty/nevermore-cli 4.18.0 → 4.19.0
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/CHANGELOG.md +11 -0
- package/dist/commands/tools-command/ci-post-test-results.d.ts.map +1 -1
- package/dist/commands/tools-command/ci-post-test-results.js +18 -0
- package/dist/commands/tools-command/ci-post-test-results.js.map +1 -1
- package/dist/commands/tools-command/strip-sourcemap-jest-command.d.ts.map +1 -1
- package/dist/commands/tools-command/strip-sourcemap-jest-command.js.map +1 -1
- package/dist/utils/linting/parsers/moonwave-parser.d.ts.map +1 -1
- package/dist/utils/linting/parsers/moonwave-parser.js +2 -6
- package/dist/utils/linting/parsers/moonwave-parser.js.map +1 -1
- package/dist/utils/sourcemap/index.d.ts +4 -0
- package/dist/utils/sourcemap/index.d.ts.map +1 -0
- package/dist/utils/sourcemap/index.js +3 -0
- package/dist/utils/sourcemap/index.js.map +1 -0
- package/dist/utils/sourcemap/sourcemap-loader.d.ts +10 -0
- package/dist/utils/sourcemap/sourcemap-loader.d.ts.map +1 -0
- package/dist/utils/sourcemap/sourcemap-loader.js +29 -0
- package/dist/utils/sourcemap/sourcemap-loader.js.map +1 -0
- package/dist/utils/sourcemap/sourcemap-resolver.d.ts +30 -0
- package/dist/utils/sourcemap/sourcemap-resolver.d.ts.map +1 -0
- package/dist/utils/sourcemap/sourcemap-resolver.js +58 -0
- package/dist/utils/sourcemap/sourcemap-resolver.js.map +1 -0
- package/dist/utils/sourcemap/sourcemap-resolver.test.d.ts +2 -0
- package/dist/utils/sourcemap/sourcemap-resolver.test.d.ts.map +1 -0
- package/dist/utils/sourcemap/sourcemap-resolver.test.js +106 -0
- package/dist/utils/sourcemap/sourcemap-resolver.test.js.map +1 -0
- package/dist/utils/sourcemap/sourcemap-types.d.ts +13 -0
- package/dist/utils/sourcemap/sourcemap-types.d.ts.map +1 -0
- package/dist/utils/sourcemap/sourcemap-types.js +2 -0
- package/dist/utils/sourcemap/sourcemap-types.js.map +1 -0
- package/dist/utils/testing/parsers/index.d.ts +3 -0
- package/dist/utils/testing/parsers/index.d.ts.map +1 -0
- package/dist/utils/testing/parsers/index.js +3 -0
- package/dist/utils/testing/parsers/index.js.map +1 -0
- package/dist/utils/testing/parsers/jest-lua-parser.d.ts +41 -0
- package/dist/utils/testing/parsers/jest-lua-parser.d.ts.map +1 -0
- package/dist/utils/testing/parsers/jest-lua-parser.js +222 -0
- package/dist/utils/testing/parsers/jest-lua-parser.js.map +1 -0
- package/dist/utils/testing/parsers/jest-lua-parser.test.d.ts +2 -0
- package/dist/utils/testing/parsers/jest-lua-parser.test.d.ts.map +1 -0
- package/dist/utils/testing/parsers/jest-lua-parser.test.js +297 -0
- package/dist/utils/testing/parsers/jest-lua-parser.test.js.map +1 -0
- package/dist/utils/testing/parsers/roblox-path-resolver.d.ts +24 -0
- package/dist/utils/testing/parsers/roblox-path-resolver.d.ts.map +1 -0
- package/dist/utils/testing/parsers/roblox-path-resolver.js +71 -0
- package/dist/utils/testing/parsers/roblox-path-resolver.js.map +1 -0
- package/dist/utils/testing/parsers/roblox-path-resolver.test.d.ts +2 -0
- package/dist/utils/testing/parsers/roblox-path-resolver.test.d.ts.map +1 -0
- package/dist/utils/testing/parsers/roblox-path-resolver.test.js +76 -0
- package/dist/utils/testing/parsers/roblox-path-resolver.test.js.map +1 -0
- package/dist/utils/testing/test-log-parser.d.ts.map +1 -1
- package/dist/utils/testing/test-log-parser.js +2 -1
- package/dist/utils/testing/test-log-parser.js.map +1 -1
- package/package.json +6 -6
- package/src/commands/tools-command/ci-post-test-results.ts +26 -0
- package/src/commands/tools-command/strip-sourcemap-jest-command.ts +1 -7
- package/src/utils/linting/parsers/moonwave-parser.ts +2 -7
- package/src/utils/sourcemap/index.ts +3 -0
- package/src/utils/sourcemap/sourcemap-loader.ts +33 -0
- package/src/utils/sourcemap/sourcemap-resolver.test.ts +150 -0
- package/src/utils/sourcemap/sourcemap-resolver.ts +75 -0
- package/src/utils/sourcemap/sourcemap-types.ts +12 -0
- package/src/utils/testing/parsers/index.ts +2 -0
- package/src/utils/testing/parsers/jest-lua-parser.test.ts +350 -0
- package/src/utils/testing/parsers/jest-lua-parser.ts +279 -0
- package/src/utils/testing/parsers/roblox-path-resolver.test.ts +129 -0
- package/src/utils/testing/parsers/roblox-path-resolver.ts +85 -0
- package/src/utils/testing/test-log-parser.ts +3 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roblox-path-resolver.test.d.ts","sourceRoot":"","sources":["../../../../src/utils/testing/parsers/roblox-path-resolver.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolveRobloxTestPath } from './roblox-path-resolver.js';
|
|
3
|
+
import { SourcemapResolver } from '../../sourcemap/index.js';
|
|
4
|
+
describe('resolveRobloxTestPath', () => {
|
|
5
|
+
it('resolves a standard ServerScriptService path', () => {
|
|
6
|
+
expect(resolveRobloxTestPath('ServerScriptService.observablecollection.Shared.ObservableList.spec')).toBe('src/observablecollection/src/Shared/ObservableList.spec.lua');
|
|
7
|
+
});
|
|
8
|
+
it('resolves a nested subdirectory path', () => {
|
|
9
|
+
expect(resolveRobloxTestPath('ServerScriptService.maid.Shared.Maid.spec')).toBe('src/maid/src/Shared/Maid.spec.lua');
|
|
10
|
+
});
|
|
11
|
+
it('resolves a deeply nested path', () => {
|
|
12
|
+
expect(resolveRobloxTestPath('ServerScriptService.blend.Client.Blend.Spring.SpringObject.spec')).toBe('src/blend/src/Client/Blend/Spring/SpringObject.spec.lua');
|
|
13
|
+
});
|
|
14
|
+
it('handles missing ServerScriptService prefix as fallback', () => {
|
|
15
|
+
expect(resolveRobloxTestPath('observablecollection.Shared.ObservableList.spec')).toBe('src/observablecollection/src/Shared/ObservableList.spec.lua');
|
|
16
|
+
});
|
|
17
|
+
it('strips :LINE suffix', () => {
|
|
18
|
+
expect(resolveRobloxTestPath('ServerScriptService.observablecollection.Shared.ObservableList.spec:45')).toBe('src/observablecollection/src/Shared/ObservableList.spec.lua');
|
|
19
|
+
});
|
|
20
|
+
it('handles path without ServerScriptService prefix and with :LINE suffix', () => {
|
|
21
|
+
expect(resolveRobloxTestPath('maid.Shared.Maid.spec:23')).toBe('src/maid/src/Shared/Maid.spec.lua');
|
|
22
|
+
});
|
|
23
|
+
it('handles a bare package slug', () => {
|
|
24
|
+
expect(resolveRobloxTestPath('ServerScriptService.maid')).toBe('src/maid/src');
|
|
25
|
+
});
|
|
26
|
+
it('handles a single-level spec path', () => {
|
|
27
|
+
expect(resolveRobloxTestPath('ServerScriptService.maid.Maid.spec')).toBe('src/maid/src/Maid.spec.lua');
|
|
28
|
+
});
|
|
29
|
+
describe('with sourcemap resolver', () => {
|
|
30
|
+
const repoRoot = '/repo';
|
|
31
|
+
const sourcemap = {
|
|
32
|
+
name: 'Nevermore',
|
|
33
|
+
className: 'DataModel',
|
|
34
|
+
children: [
|
|
35
|
+
{
|
|
36
|
+
name: 'mypkg',
|
|
37
|
+
className: 'Folder',
|
|
38
|
+
filePaths: [`${repoRoot}/src/mypkg/default.project.json`],
|
|
39
|
+
children: [
|
|
40
|
+
{
|
|
41
|
+
name: 'Shared',
|
|
42
|
+
className: 'Folder',
|
|
43
|
+
children: [
|
|
44
|
+
{
|
|
45
|
+
name: 'MyModule',
|
|
46
|
+
className: 'ModuleScript',
|
|
47
|
+
filePaths: [`${repoRoot}/src/mypkg/src/Shared/MyModule.lua`],
|
|
48
|
+
children: [
|
|
49
|
+
{
|
|
50
|
+
name: 'MyModule.spec',
|
|
51
|
+
className: 'ModuleScript',
|
|
52
|
+
filePaths: [
|
|
53
|
+
`${repoRoot}/src/mypkg/src/Shared/MyModule.spec.lua`,
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
const resolver = SourcemapResolver.fromSourcemap(sourcemap, repoRoot);
|
|
65
|
+
it('uses sourcemap when path is found', () => {
|
|
66
|
+
expect(resolveRobloxTestPath('ServerScriptService.mypkg.Shared.MyModule.MyModule.spec', resolver)).toBe('src/mypkg/src/Shared/MyModule.spec.lua');
|
|
67
|
+
});
|
|
68
|
+
it('falls back to heuristic when sourcemap has no mapping', () => {
|
|
69
|
+
expect(resolveRobloxTestPath('ServerScriptService.unknownpkg.Shared.Foo.spec', resolver)).toBe('src/unknownpkg/src/Shared/Foo.spec.lua');
|
|
70
|
+
});
|
|
71
|
+
it('strips :LINE suffix with sourcemap', () => {
|
|
72
|
+
expect(resolveRobloxTestPath('ServerScriptService.mypkg.Shared.MyModule.MyModule.spec:42', resolver)).toBe('src/mypkg/src/Shared/MyModule.spec.lua');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=roblox-path-resolver.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roblox-path-resolver.test.js","sourceRoot":"","sources":["../../../../src/utils/testing/parsers/roblox-path-resolver.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-log-parser.d.ts","sourceRoot":"","sources":["../../../src/utils/testing/test-log-parser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"test-log-parser.d.ts","sourceRoot":"","sources":["../../../src/utils/testing/test-log-parser.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAiB/D"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
1
2
|
/**
|
|
2
3
|
* Analyze test output for Jest failures and Luau runtime errors.
|
|
3
4
|
* Shared by both Open Cloud log fetching and local run-in-roblox output.
|
|
4
5
|
*/
|
|
5
6
|
export function parseTestLogs(rawOutput) {
|
|
6
|
-
const cleanLogs =
|
|
7
|
+
const cleanLogs = OutputHelper.stripAnsi(rawOutput);
|
|
7
8
|
// Check for Jest-style test failures
|
|
8
9
|
const failedSuites = cleanLogs.match(/Test Suites:\s*(\d+)\s+failed/);
|
|
9
10
|
const failedTests = cleanLogs.match(/Tests:\s*(\d+)\s+failed/);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-log-parser.js","sourceRoot":"","sources":["../../../src/utils/testing/test-log-parser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"test-log-parser.js","sourceRoot":"","sources":["../../../src/utils/testing/test-log-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAO1D;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB;IAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAEpD,qCAAqC;IACrC,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACtE,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC/D,MAAM,eAAe,GACnB,CAAC,YAAY,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QACnD,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEpD,+CAA+C;IAC/C,MAAM,eAAe,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAExD,OAAO;QACL,OAAO,EAAE,CAAC,eAAe,IAAI,CAAC,eAAe;QAC7C,IAAI,EAAE,SAAS;KAChB,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/nevermore-cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.19.0",
|
|
4
4
|
"description": "CLI interface for Nevermore",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"Quenty"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@quenty/cli-output-helpers": "1.
|
|
29
|
-
"@quenty/nevermore-cli-helpers": "1.
|
|
30
|
-
"@quenty/nevermore-template-helpers": "1.
|
|
31
|
-
"@quenty/studio-bridge": "0.
|
|
28
|
+
"@quenty/cli-output-helpers": "1.8.0",
|
|
29
|
+
"@quenty/nevermore-cli-helpers": "1.6.0",
|
|
30
|
+
"@quenty/nevermore-template-helpers": "1.9.0",
|
|
31
|
+
"@quenty/studio-bridge": "0.5.0",
|
|
32
32
|
"execa": "^9.6.1",
|
|
33
33
|
"find-git-root": "^1.0.4",
|
|
34
34
|
"inquirer": "^13.2.0",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=16"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "3e227cd352996f0ee771361ca5140ce7e366de00"
|
|
61
61
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { CommandModule } from 'yargs';
|
|
2
2
|
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
3
|
+
import {
|
|
4
|
+
type Diagnostic,
|
|
5
|
+
emitAnnotations,
|
|
6
|
+
writeAnnotationSummaryAsync,
|
|
7
|
+
} from '@quenty/cli-output-helpers/reporting';
|
|
3
8
|
import { NevermoreGlobalArgs } from '../../args/global-args.js';
|
|
4
9
|
import {
|
|
5
10
|
type IStateTracker,
|
|
@@ -9,6 +14,8 @@ import {
|
|
|
9
14
|
LoadedStateTracker,
|
|
10
15
|
createTestCommentConfig,
|
|
11
16
|
} from '../../utils/testing/reporting/index.js';
|
|
17
|
+
import { parseJestLuaOutput } from '../../utils/testing/parsers/index.js';
|
|
18
|
+
import { tryLoadSourcemapResolver } from '../../utils/sourcemap/index.js';
|
|
12
19
|
|
|
13
20
|
type ErrorReporter = Reporter & {
|
|
14
21
|
setError(error: string): void;
|
|
@@ -86,6 +93,25 @@ export const ciPostTestResultsCommand: CommandModule<
|
|
|
86
93
|
return;
|
|
87
94
|
}
|
|
88
95
|
|
|
96
|
+
// Emit test failure annotations
|
|
97
|
+
const sourcemapResolver = tryLoadSourcemapResolver(process.cwd());
|
|
98
|
+
const allDiagnostics: Diagnostic[] = [];
|
|
99
|
+
for (const failure of state.getFailures()) {
|
|
100
|
+
const diagnostics = parseJestLuaOutput(failure.logs, {
|
|
101
|
+
packageName: failure.packageName,
|
|
102
|
+
sourcemapResolver,
|
|
103
|
+
});
|
|
104
|
+
allDiagnostics.push(...diagnostics);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (allDiagnostics.length > 0) {
|
|
108
|
+
OutputHelper.info(
|
|
109
|
+
`Found ${allDiagnostics.length} test failure(s). Emitting annotations...`
|
|
110
|
+
);
|
|
111
|
+
emitAnnotations(allDiagnostics);
|
|
112
|
+
await writeAnnotationSummaryAsync('Test Failures', allDiagnostics);
|
|
113
|
+
}
|
|
114
|
+
|
|
89
115
|
const reporters = _createGithubReporters(state, 1);
|
|
90
116
|
for (const r of reporters) {
|
|
91
117
|
await r.stopAsync();
|
|
@@ -3,13 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import { CommandModule } from 'yargs';
|
|
4
4
|
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
5
5
|
import { NevermoreGlobalArgs } from '../../args/global-args.js';
|
|
6
|
-
|
|
7
|
-
interface SourcemapNode {
|
|
8
|
-
name: string;
|
|
9
|
-
className?: string;
|
|
10
|
-
children?: SourcemapNode[];
|
|
11
|
-
filePaths?: string[];
|
|
12
|
-
}
|
|
6
|
+
import type { SourcemapNode } from '../../utils/sourcemap/index.js';
|
|
13
7
|
|
|
14
8
|
interface StripSourcemapJestArgs extends NevermoreGlobalArgs {
|
|
15
9
|
sourcemap?: string;
|
|
@@ -21,18 +21,13 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { type Diagnostic, type DiagnosticSeverity } from '@quenty/cli-output-helpers/reporting';
|
|
24
|
+
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
24
25
|
import {
|
|
25
26
|
LERNA_PREFIX_PATTERN,
|
|
26
27
|
LERNA_PREFIX_PATTERN_NC,
|
|
27
28
|
resolvePackagePath,
|
|
28
29
|
} from './lerna-utils.js';
|
|
29
30
|
|
|
30
|
-
/** Strip all ANSI escape sequences from a string. */
|
|
31
|
-
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
32
|
-
function _stripAnsi(text: string): string {
|
|
33
|
-
return text.replace(ANSI_PATTERN, '');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
31
|
/**
|
|
37
32
|
* Matches the severity header line.
|
|
38
33
|
* Optional lerna prefix (captures package name).
|
|
@@ -64,7 +59,7 @@ export function parseMoonwaveOutput(raw: string): Diagnostic[] {
|
|
|
64
59
|
let pending: PendingDiagnostic | undefined;
|
|
65
60
|
|
|
66
61
|
for (const rawLine of lines) {
|
|
67
|
-
const line =
|
|
62
|
+
const line = OutputHelper.stripAnsi(rawLine);
|
|
68
63
|
|
|
69
64
|
// Skip the "aborting due to diagnostic error" summary line
|
|
70
65
|
if (line.includes('aborting due to')) continue;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { SourcemapNode } from './sourcemap-types.js';
|
|
4
|
+
import { SourcemapResolver } from './sourcemap-resolver.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Try to load a `SourcemapResolver` from `sourcemap.json` in the given
|
|
8
|
+
* directory.
|
|
9
|
+
*
|
|
10
|
+
* Returns `undefined` if the file doesn't exist or can't be parsed — callers
|
|
11
|
+
* should fall back to heuristic resolution.
|
|
12
|
+
*/
|
|
13
|
+
export function tryLoadSourcemapResolver(
|
|
14
|
+
repoRoot: string
|
|
15
|
+
): SourcemapResolver | undefined {
|
|
16
|
+
const sourcemapPath = path.join(repoRoot, 'sourcemap.json');
|
|
17
|
+
|
|
18
|
+
let content: string;
|
|
19
|
+
try {
|
|
20
|
+
content = fs.readFileSync(sourcemapPath, 'utf-8');
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let root: SourcemapNode;
|
|
26
|
+
try {
|
|
27
|
+
root = JSON.parse(content) as SourcemapNode;
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return SourcemapResolver.fromSourcemap(root, repoRoot);
|
|
33
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SourcemapResolver } from './sourcemap-resolver.js';
|
|
3
|
+
import type { SourcemapNode } from './sourcemap-types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Minimal sourcemap tree that mirrors a typical Nevermore project:
|
|
7
|
+
*
|
|
8
|
+
* Nevermore (root)
|
|
9
|
+
* └─ observablecollection
|
|
10
|
+
* └─ Shared
|
|
11
|
+
* └─ ObservableList
|
|
12
|
+
* └─ ObservableList.spec
|
|
13
|
+
*/
|
|
14
|
+
function _createTestSourcemap(repoRoot: string): SourcemapNode {
|
|
15
|
+
return {
|
|
16
|
+
name: 'Nevermore',
|
|
17
|
+
className: 'DataModel',
|
|
18
|
+
children: [
|
|
19
|
+
{
|
|
20
|
+
name: 'observablecollection',
|
|
21
|
+
className: 'Folder',
|
|
22
|
+
filePaths: [`${repoRoot}/src/observablecollection/default.project.json`],
|
|
23
|
+
children: [
|
|
24
|
+
{
|
|
25
|
+
name: 'Shared',
|
|
26
|
+
className: 'Folder',
|
|
27
|
+
children: [
|
|
28
|
+
{
|
|
29
|
+
name: 'ObservableList',
|
|
30
|
+
className: 'ModuleScript',
|
|
31
|
+
filePaths: [
|
|
32
|
+
`${repoRoot}/src/observablecollection/src/Shared/ObservableList.lua`,
|
|
33
|
+
],
|
|
34
|
+
children: [
|
|
35
|
+
{
|
|
36
|
+
name: 'ObservableList.spec',
|
|
37
|
+
className: 'ModuleScript',
|
|
38
|
+
filePaths: [
|
|
39
|
+
`${repoRoot}/src/observablecollection/src/Shared/ObservableList.spec.lua`,
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'maid',
|
|
50
|
+
className: 'Folder',
|
|
51
|
+
filePaths: [`${repoRoot}/src/maid/default.project.json`],
|
|
52
|
+
children: [
|
|
53
|
+
{
|
|
54
|
+
name: 'Shared',
|
|
55
|
+
className: 'Folder',
|
|
56
|
+
children: [
|
|
57
|
+
{
|
|
58
|
+
name: 'Maid',
|
|
59
|
+
className: 'ModuleScript',
|
|
60
|
+
filePaths: [`${repoRoot}/src/maid/src/Shared/Maid.lua`],
|
|
61
|
+
children: [
|
|
62
|
+
{
|
|
63
|
+
name: 'Maid.spec',
|
|
64
|
+
className: 'ModuleScript',
|
|
65
|
+
filePaths: [
|
|
66
|
+
`${repoRoot}/src/maid/src/Shared/Maid.spec.lua`,
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describe('SourcemapResolver', () => {
|
|
80
|
+
const repoRoot = '/repo';
|
|
81
|
+
const resolver = SourcemapResolver.fromSourcemap(
|
|
82
|
+
_createTestSourcemap(repoRoot),
|
|
83
|
+
repoRoot
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
it('resolves a spec file instance path', () => {
|
|
87
|
+
expect(
|
|
88
|
+
resolver.resolve(
|
|
89
|
+
'ServerScriptService.observablecollection.Shared.ObservableList.ObservableList.spec'
|
|
90
|
+
)
|
|
91
|
+
).toBe('src/observablecollection/src/Shared/ObservableList.spec.lua');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('resolves a module instance path', () => {
|
|
95
|
+
expect(
|
|
96
|
+
resolver.resolve(
|
|
97
|
+
'ServerScriptService.observablecollection.Shared.ObservableList'
|
|
98
|
+
)
|
|
99
|
+
).toBe('src/observablecollection/src/Shared/ObservableList.lua');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('strips :LINE suffix before lookup', () => {
|
|
103
|
+
expect(
|
|
104
|
+
resolver.resolve(
|
|
105
|
+
'ServerScriptService.maid.Shared.Maid.Maid.spec:23'
|
|
106
|
+
)
|
|
107
|
+
).toBe('src/maid/src/Shared/Maid.spec.lua');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('returns undefined for unknown paths', () => {
|
|
111
|
+
expect(
|
|
112
|
+
resolver.resolve('ServerScriptService.nonexistent.Shared.Foo')
|
|
113
|
+
).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('skips nodes that only have .project.json (no lua files)', () => {
|
|
117
|
+
// The "observablecollection" folder node only has a .project.json filePath.
|
|
118
|
+
// It should not be indexed.
|
|
119
|
+
expect(
|
|
120
|
+
resolver.resolve('ServerScriptService.observablecollection')
|
|
121
|
+
).toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('resolves paths in a different package', () => {
|
|
125
|
+
expect(
|
|
126
|
+
resolver.resolve('ServerScriptService.maid.Shared.Maid')
|
|
127
|
+
).toBe('src/maid/src/Shared/Maid.lua');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('supports a custom root alias', () => {
|
|
131
|
+
const customResolver = SourcemapResolver.fromSourcemap(
|
|
132
|
+
_createTestSourcemap(repoRoot),
|
|
133
|
+
repoRoot,
|
|
134
|
+
'ReplicatedStorage'
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(
|
|
138
|
+
customResolver.resolve(
|
|
139
|
+
'ReplicatedStorage.maid.Shared.Maid'
|
|
140
|
+
)
|
|
141
|
+
).toBe('src/maid/src/Shared/Maid.lua');
|
|
142
|
+
|
|
143
|
+
// ServerScriptService should NOT work with a different alias
|
|
144
|
+
expect(
|
|
145
|
+
customResolver.resolve(
|
|
146
|
+
'ServerScriptService.maid.Shared.Maid'
|
|
147
|
+
)
|
|
148
|
+
).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import type { SourcemapNode } from './sourcemap-types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds a lookup index from a Rojo sourcemap tree so that dotted Roblox
|
|
6
|
+
* instance paths (e.g. `ServerScriptService.maid.Shared.Maid.spec`) can be
|
|
7
|
+
* resolved to repo-relative filesystem paths.
|
|
8
|
+
*/
|
|
9
|
+
export class SourcemapResolver {
|
|
10
|
+
private readonly _index: Map<string, string>;
|
|
11
|
+
|
|
12
|
+
private constructor(index: Map<string, string>) {
|
|
13
|
+
this._index = index;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a resolver from a parsed sourcemap root node.
|
|
18
|
+
*
|
|
19
|
+
* @param root - The top-level sourcemap node (typically named after the project)
|
|
20
|
+
* @param repoRoot - Absolute path to the repo root, used to convert absolute
|
|
21
|
+
* `filePaths` entries to repo-relative paths
|
|
22
|
+
* @param rootAlias - The Roblox service name that the root node maps to in
|
|
23
|
+
* test output. Defaults to `"ServerScriptService"`.
|
|
24
|
+
*/
|
|
25
|
+
static fromSourcemap(
|
|
26
|
+
root: SourcemapNode,
|
|
27
|
+
repoRoot: string,
|
|
28
|
+
rootAlias = 'ServerScriptService'
|
|
29
|
+
): SourcemapResolver {
|
|
30
|
+
const index = new Map<string, string>();
|
|
31
|
+
_walkNode(root, rootAlias, repoRoot, index);
|
|
32
|
+
return new SourcemapResolver(index);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a Roblox instance path to a repo-relative filesystem path.
|
|
37
|
+
*
|
|
38
|
+
* Strips any trailing `:LINE` suffix before lookup.
|
|
39
|
+
*
|
|
40
|
+
* @returns The repo-relative path, or `undefined` if the instance path is
|
|
41
|
+
* not in the sourcemap.
|
|
42
|
+
*/
|
|
43
|
+
resolve(instancePath: string): string | undefined {
|
|
44
|
+
const cleaned = instancePath.replace(/:\d+$/, '');
|
|
45
|
+
return this._index.get(cleaned);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Recursively walk the sourcemap tree, populating the index map. */
|
|
50
|
+
function _walkNode(
|
|
51
|
+
node: SourcemapNode,
|
|
52
|
+
dottedPath: string,
|
|
53
|
+
repoRoot: string,
|
|
54
|
+
index: Map<string, string>
|
|
55
|
+
): void {
|
|
56
|
+
const luaFile = _findLuaFilePath(node.filePaths);
|
|
57
|
+
if (luaFile) {
|
|
58
|
+
const relative = path.relative(repoRoot, luaFile);
|
|
59
|
+
index.set(dottedPath, relative);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!node.children) return;
|
|
63
|
+
|
|
64
|
+
for (const child of node.children) {
|
|
65
|
+
_walkNode(child, `${dottedPath}.${child.name}`, repoRoot, index);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Return the first `.lua` or `.luau` file path from a node's filePaths. */
|
|
70
|
+
function _findLuaFilePath(filePaths?: string[]): string | undefined {
|
|
71
|
+
if (!filePaths) return undefined;
|
|
72
|
+
return filePaths.find(
|
|
73
|
+
(fp) => fp.endsWith('.lua') || fp.endsWith('.luau')
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A node in the Rojo sourcemap tree (generated by `rojo sourcemap --absolute`).
|
|
3
|
+
*
|
|
4
|
+
* Each node represents a Roblox instance and optionally maps to one or more
|
|
5
|
+
* filesystem paths.
|
|
6
|
+
*/
|
|
7
|
+
export interface SourcemapNode {
|
|
8
|
+
name: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
children?: SourcemapNode[];
|
|
11
|
+
filePaths?: string[];
|
|
12
|
+
}
|