@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,129 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolveRobloxTestPath } from './roblox-path-resolver.js';
|
|
3
|
+
import { SourcemapResolver } from '../../sourcemap/index.js';
|
|
4
|
+
import type { SourcemapNode } from '../../sourcemap/index.js';
|
|
5
|
+
|
|
6
|
+
describe('resolveRobloxTestPath', () => {
|
|
7
|
+
it('resolves a standard ServerScriptService path', () => {
|
|
8
|
+
expect(
|
|
9
|
+
resolveRobloxTestPath(
|
|
10
|
+
'ServerScriptService.observablecollection.Shared.ObservableList.spec'
|
|
11
|
+
)
|
|
12
|
+
).toBe('src/observablecollection/src/Shared/ObservableList.spec.lua');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('resolves a nested subdirectory path', () => {
|
|
16
|
+
expect(
|
|
17
|
+
resolveRobloxTestPath(
|
|
18
|
+
'ServerScriptService.maid.Shared.Maid.spec'
|
|
19
|
+
)
|
|
20
|
+
).toBe('src/maid/src/Shared/Maid.spec.lua');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('resolves a deeply nested path', () => {
|
|
24
|
+
expect(
|
|
25
|
+
resolveRobloxTestPath(
|
|
26
|
+
'ServerScriptService.blend.Client.Blend.Spring.SpringObject.spec'
|
|
27
|
+
)
|
|
28
|
+
).toBe('src/blend/src/Client/Blend/Spring/SpringObject.spec.lua');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('handles missing ServerScriptService prefix as fallback', () => {
|
|
32
|
+
expect(
|
|
33
|
+
resolveRobloxTestPath('observablecollection.Shared.ObservableList.spec')
|
|
34
|
+
).toBe('src/observablecollection/src/Shared/ObservableList.spec.lua');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('strips :LINE suffix', () => {
|
|
38
|
+
expect(
|
|
39
|
+
resolveRobloxTestPath(
|
|
40
|
+
'ServerScriptService.observablecollection.Shared.ObservableList.spec:45'
|
|
41
|
+
)
|
|
42
|
+
).toBe('src/observablecollection/src/Shared/ObservableList.spec.lua');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('handles path without ServerScriptService prefix and with :LINE suffix', () => {
|
|
46
|
+
expect(
|
|
47
|
+
resolveRobloxTestPath('maid.Shared.Maid.spec:23')
|
|
48
|
+
).toBe('src/maid/src/Shared/Maid.spec.lua');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('handles a bare package slug', () => {
|
|
52
|
+
expect(
|
|
53
|
+
resolveRobloxTestPath('ServerScriptService.maid')
|
|
54
|
+
).toBe('src/maid/src');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('handles a single-level spec path', () => {
|
|
58
|
+
expect(
|
|
59
|
+
resolveRobloxTestPath('ServerScriptService.maid.Maid.spec')
|
|
60
|
+
).toBe('src/maid/src/Maid.spec.lua');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('with sourcemap resolver', () => {
|
|
64
|
+
const repoRoot = '/repo';
|
|
65
|
+
|
|
66
|
+
const sourcemap: SourcemapNode = {
|
|
67
|
+
name: 'Nevermore',
|
|
68
|
+
className: 'DataModel',
|
|
69
|
+
children: [
|
|
70
|
+
{
|
|
71
|
+
name: 'mypkg',
|
|
72
|
+
className: 'Folder',
|
|
73
|
+
filePaths: [`${repoRoot}/src/mypkg/default.project.json`],
|
|
74
|
+
children: [
|
|
75
|
+
{
|
|
76
|
+
name: 'Shared',
|
|
77
|
+
className: 'Folder',
|
|
78
|
+
children: [
|
|
79
|
+
{
|
|
80
|
+
name: 'MyModule',
|
|
81
|
+
className: 'ModuleScript',
|
|
82
|
+
filePaths: [`${repoRoot}/src/mypkg/src/Shared/MyModule.lua`],
|
|
83
|
+
children: [
|
|
84
|
+
{
|
|
85
|
+
name: 'MyModule.spec',
|
|
86
|
+
className: 'ModuleScript',
|
|
87
|
+
filePaths: [
|
|
88
|
+
`${repoRoot}/src/mypkg/src/Shared/MyModule.spec.lua`,
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const resolver = SourcemapResolver.fromSourcemap(sourcemap, repoRoot);
|
|
101
|
+
|
|
102
|
+
it('uses sourcemap when path is found', () => {
|
|
103
|
+
expect(
|
|
104
|
+
resolveRobloxTestPath(
|
|
105
|
+
'ServerScriptService.mypkg.Shared.MyModule.MyModule.spec',
|
|
106
|
+
resolver
|
|
107
|
+
)
|
|
108
|
+
).toBe('src/mypkg/src/Shared/MyModule.spec.lua');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('falls back to heuristic when sourcemap has no mapping', () => {
|
|
112
|
+
expect(
|
|
113
|
+
resolveRobloxTestPath(
|
|
114
|
+
'ServerScriptService.unknownpkg.Shared.Foo.spec',
|
|
115
|
+
resolver
|
|
116
|
+
)
|
|
117
|
+
).toBe('src/unknownpkg/src/Shared/Foo.spec.lua');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('strips :LINE suffix with sourcemap', () => {
|
|
121
|
+
expect(
|
|
122
|
+
resolveRobloxTestPath(
|
|
123
|
+
'ServerScriptService.mypkg.Shared.MyModule.MyModule.spec:42',
|
|
124
|
+
resolver
|
|
125
|
+
)
|
|
126
|
+
).toBe('src/mypkg/src/Shared/MyModule.spec.lua');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts Roblox instance paths from Jest-lua output back to
|
|
3
|
+
* repo-relative local filesystem paths.
|
|
4
|
+
*
|
|
5
|
+
* Two strategies:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Sourcemap** (preferred) — Uses the authoritative `sourcemap.json`
|
|
8
|
+
* mapping from `rojo sourcemap --absolute`. Exact and handles all
|
|
9
|
+
* project layouts.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Heuristic** (fallback) — Assumes `src/{slug}/src/` layout and
|
|
12
|
+
* hard-codes `.spec`/`.story` as known dotted suffixes. Works when no
|
|
13
|
+
* sourcemap is available.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { SourcemapResolver } from '../../sourcemap/index.js';
|
|
17
|
+
|
|
18
|
+
const SSS_PREFIX = 'ServerScriptService.';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a Roblox instance path (from Jest-lua or a stack trace) to a
|
|
22
|
+
* repo-relative filesystem path.
|
|
23
|
+
*
|
|
24
|
+
* When a `sourcemapResolver` is provided, it is tried first. Falls back to
|
|
25
|
+
* the heuristic if the resolver doesn't have a mapping.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveRobloxTestPath(
|
|
28
|
+
instancePath: string,
|
|
29
|
+
sourcemapResolver?: SourcemapResolver
|
|
30
|
+
): string {
|
|
31
|
+
if (sourcemapResolver) {
|
|
32
|
+
const resolved = sourcemapResolver.resolve(instancePath);
|
|
33
|
+
if (resolved) return resolved;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return _resolveHeuristic(instancePath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Heuristic fallback: assumes `src/{slug}/src/` layout.
|
|
41
|
+
*
|
|
42
|
+
* Handles optional `:LINE` suffixes and missing `ServerScriptService.` prefix.
|
|
43
|
+
*/
|
|
44
|
+
function _resolveHeuristic(instancePath: string): string {
|
|
45
|
+
// Strip :LINE suffix if present
|
|
46
|
+
let path = instancePath.replace(/:\d+$/, '');
|
|
47
|
+
|
|
48
|
+
// Strip ServerScriptService. prefix
|
|
49
|
+
if (path.startsWith(SSS_PREFIX)) {
|
|
50
|
+
path = path.slice(SSS_PREFIX.length);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// First dot-separated segment is the package slug
|
|
54
|
+
const dotIndex = path.indexOf('.');
|
|
55
|
+
if (dotIndex === -1) {
|
|
56
|
+
// Just a package name with no sub-path — return the src dir
|
|
57
|
+
return `src/${path}/src`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const packageSlug = path.slice(0, dotIndex);
|
|
61
|
+
const remaining = path.slice(dotIndex + 1);
|
|
62
|
+
|
|
63
|
+
// Split into segments. In Rojo, dots in filenames (e.g. "Maid.spec.lua"
|
|
64
|
+
// becomes instance "Maid.spec") are ambiguous with the hierarchy separator.
|
|
65
|
+
// Rejoin known filename suffixes like ".spec" and ".story" with a dot
|
|
66
|
+
// instead of a slash.
|
|
67
|
+
const segments = remaining.split('.');
|
|
68
|
+
const rejoined: string[] = [];
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < segments.length; i++) {
|
|
71
|
+
const seg = segments[i];
|
|
72
|
+
// If this segment is a known file suffix, merge it with the previous
|
|
73
|
+
if (
|
|
74
|
+
i > 0 &&
|
|
75
|
+
(seg === 'spec' || seg === 'story')
|
|
76
|
+
) {
|
|
77
|
+
rejoined[rejoined.length - 1] += `.${seg}`;
|
|
78
|
+
} else {
|
|
79
|
+
rejoined.push(seg);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const subPath = rejoined.join('/');
|
|
84
|
+
return `src/${packageSlug}/src/${subPath}.lua`;
|
|
85
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
2
|
+
|
|
1
3
|
export interface ParsedTestLogs {
|
|
2
4
|
success: boolean;
|
|
3
5
|
logs: string;
|
|
@@ -8,7 +10,7 @@ export interface ParsedTestLogs {
|
|
|
8
10
|
* Shared by both Open Cloud log fetching and local run-in-roblox output.
|
|
9
11
|
*/
|
|
10
12
|
export function parseTestLogs(rawOutput: string): ParsedTestLogs {
|
|
11
|
-
const cleanLogs =
|
|
13
|
+
const cleanLogs = OutputHelper.stripAnsi(rawOutput);
|
|
12
14
|
|
|
13
15
|
// Check for Jest-style test failures
|
|
14
16
|
const failedSuites = cleanLogs.match(/Test Suites:\s*(\d+)\s+failed/);
|