@quenty/nevermore-cli 4.50.0 → 4.50.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.
- package/CHANGELOG.md +6 -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 +7 -3
- package/dist/commands/tools-command/ci-post-test-results.js.map +1 -1
- package/dist/utils/batch/changed-packages-utils.d.ts +8 -0
- package/dist/utils/batch/changed-packages-utils.d.ts.map +1 -1
- package/dist/utils/batch/changed-packages-utils.js +9 -11
- package/dist/utils/batch/changed-packages-utils.js.map +1 -1
- package/dist/utils/testing/parsers/annotation-workspace.d.ts +17 -0
- package/dist/utils/testing/parsers/annotation-workspace.d.ts.map +1 -0
- package/dist/utils/testing/parsers/annotation-workspace.js +47 -0
- package/dist/utils/testing/parsers/annotation-workspace.js.map +1 -0
- package/dist/utils/testing/parsers/index.d.ts +0 -1
- package/dist/utils/testing/parsers/index.d.ts.map +1 -1
- package/dist/utils/testing/parsers/index.js +0 -1
- package/dist/utils/testing/parsers/index.js.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.d.ts +14 -29
- package/dist/utils/testing/parsers/jest-lua-parser.d.ts.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.js +85 -100
- package/dist/utils/testing/parsers/jest-lua-parser.js.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.test.js +359 -56
- package/dist/utils/testing/parsers/jest-lua-parser.test.js.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.d.ts +23 -17
- package/dist/utils/testing/parsers/roblox-path-resolver.d.ts.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.js +89 -54
- package/dist/utils/testing/parsers/roblox-path-resolver.js.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.test.js +76 -36
- package/dist/utils/testing/parsers/roblox-path-resolver.test.js.map +1 -1
- package/dist/utils/testing/runner/combined-project-generator.d.ts.map +1 -1
- package/dist/utils/testing/runner/combined-project-generator.js +5 -6
- package/dist/utils/testing/runner/combined-project-generator.js.map +1 -1
- package/dist/utils/testing/runner/test-project-slug.d.ts +11 -0
- package/dist/utils/testing/runner/test-project-slug.d.ts.map +1 -0
- package/dist/utils/testing/runner/test-project-slug.js +11 -0
- package/dist/utils/testing/runner/test-project-slug.js.map +1 -0
- package/package.json +6 -6
- package/src/commands/tools-command/ci-post-test-results.ts +7 -2
- package/src/utils/batch/changed-packages-utils.ts +21 -12
- package/src/utils/testing/parsers/annotation-workspace.ts +74 -0
- package/src/utils/testing/parsers/index.ts +0 -1
- package/src/utils/testing/parsers/jest-lua-parser.test.ts +433 -65
- package/src/utils/testing/parsers/jest-lua-parser.ts +131 -109
- package/src/utils/testing/parsers/roblox-path-resolver.test.ts +144 -61
- package/src/utils/testing/parsers/roblox-path-resolver.ts +117 -56
- package/src/utils/testing/runner/combined-project-generator.ts +9 -13
- package/src/utils/testing/runner/test-project-slug.ts +18 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,82 +1,143 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Maps Roblox instance paths from Jest-lua output to existing repo-relative
|
|
3
|
+
* source files, through `sourcemap.json` or else the `<package dir>/src/`
|
|
4
|
+
* layout.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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.
|
|
6
|
+
* A package's npm name (`@quenty/playermock`), directory (`src/player-mock`),
|
|
7
|
+
* Rojo project name (`playermock`) and test-place slug (`gameproduct` for
|
|
8
|
+
* `src/gameproductservice`) can all differ; see `AnnotationWorkspace`.
|
|
14
9
|
*/
|
|
15
10
|
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
16
13
|
import type { SourcemapResolver } from '../../sourcemap/index.js';
|
|
14
|
+
import type {
|
|
15
|
+
AnnotationWorkspace,
|
|
16
|
+
AnnotationWorkspacePackage,
|
|
17
|
+
} from './annotation-workspace.js';
|
|
17
18
|
|
|
18
19
|
const SERVER_SCRIPT_SERVICE_PREFIX = 'ServerScriptService.';
|
|
20
|
+
const NODE_MODULES_SEGMENT = '.node_modules.';
|
|
21
|
+
const QUENTY_SCOPE = '@quenty/';
|
|
22
|
+
const QUENTY_SCOPE_SEGMENT = '@quenty.';
|
|
23
|
+
const CHUNK_NAME_PATTERN = /^\[string "(.*)"\]$/;
|
|
24
|
+
// Rojo strips `.server`/`.client` from script instance names.
|
|
25
|
+
const SOURCE_EXTENSIONS = ['', '.server', '.client'].flatMap((kind) => [
|
|
26
|
+
`${kind}.lua`,
|
|
27
|
+
`${kind}.luau`,
|
|
28
|
+
`/init${kind}.lua`,
|
|
29
|
+
`/init${kind}.luau`,
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
export interface RobloxSourceResolveOptions {
|
|
33
|
+
sourcemapResolver?: SourcemapResolver;
|
|
34
|
+
workspace?: AnnotationWorkspace;
|
|
35
|
+
/** npm name of the package under test, e.g. `@quenty/gameproductservice` */
|
|
36
|
+
packageName?: string;
|
|
37
|
+
/** Repo root that holds the workspace. Defaults to cwd. */
|
|
38
|
+
repoRoot?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ResolvedSourceFile {
|
|
42
|
+
file: string;
|
|
43
|
+
/** False for dependency copies and sibling packages. */
|
|
44
|
+
isPackageUnderTest: boolean;
|
|
45
|
+
}
|
|
19
46
|
|
|
20
47
|
/**
|
|
21
|
-
* Resolve
|
|
22
|
-
*
|
|
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.
|
|
48
|
+
* Resolve an instance path (no `:LINE` suffix) to an existing repo-relative
|
|
49
|
+
* source file, or `undefined` when none matches.
|
|
26
50
|
*/
|
|
27
|
-
export function
|
|
51
|
+
export function tryResolveRobloxSourceFile(
|
|
28
52
|
instancePath: string,
|
|
29
|
-
|
|
30
|
-
):
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
options: RobloxSourceResolveOptions = {}
|
|
54
|
+
): ResolvedSourceFile | undefined {
|
|
55
|
+
let unwrapped = _unwrapChunkName(instancePath);
|
|
56
|
+
|
|
57
|
+
// Roblox truncates long chunk names: `...@quenty.servic..."]`
|
|
58
|
+
if (unwrapped.endsWith('...')) return undefined;
|
|
59
|
+
|
|
60
|
+
if (unwrapped.startsWith(SERVER_SCRIPT_SERVICE_PREFIX)) {
|
|
61
|
+
unwrapped = unwrapped.slice(SERVER_SCRIPT_SERVICE_PREFIX.length);
|
|
34
62
|
}
|
|
35
63
|
|
|
36
|
-
|
|
37
|
-
}
|
|
64
|
+
const packagesByName = options.workspace?.packagesByName;
|
|
38
65
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
function _resolveHeuristic(instancePath: string): string {
|
|
45
|
-
// Strip :LINE suffix if present
|
|
46
|
-
let path = instancePath.replace(/:\d+$/, '');
|
|
66
|
+
let workspacePackage: AnnotationWorkspacePackage | undefined;
|
|
67
|
+
let packageInstanceName: string;
|
|
68
|
+
let rest: string;
|
|
69
|
+
let isPackageUnderTest = false;
|
|
47
70
|
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
71
|
+
const nodeModulesIndex = unwrapped.lastIndexOf(NODE_MODULES_SEGMENT);
|
|
72
|
+
if (nodeModulesIndex !== -1) {
|
|
73
|
+
const dependencyPath = unwrapped.slice(
|
|
74
|
+
nodeModulesIndex + NODE_MODULES_SEGMENT.length
|
|
75
|
+
);
|
|
76
|
+
if (!dependencyPath.startsWith(QUENTY_SCOPE_SEGMENT)) return undefined;
|
|
77
|
+
[packageInstanceName, rest] = _splitFirstSegment(
|
|
78
|
+
dependencyPath.slice(QUENTY_SCOPE_SEGMENT.length)
|
|
79
|
+
);
|
|
80
|
+
workspacePackage = packagesByName?.get(QUENTY_SCOPE + packageInstanceName);
|
|
81
|
+
} else {
|
|
82
|
+
[packageInstanceName, rest] = _splitFirstSegment(unwrapped);
|
|
83
|
+
const packageUnderTest = options.packageName
|
|
84
|
+
? packagesByName?.get(options.packageName)
|
|
85
|
+
: undefined;
|
|
86
|
+
if (packageUnderTest?.testSlugs.includes(packageInstanceName)) {
|
|
87
|
+
workspacePackage = packageUnderTest;
|
|
88
|
+
isPackageUnderTest = true;
|
|
89
|
+
}
|
|
51
90
|
}
|
|
52
91
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
92
|
+
const dir = workspacePackage?.dir ?? `src/${packageInstanceName}`;
|
|
93
|
+
// The sourcemap names packages by Rojo project name, not folder.
|
|
94
|
+
const sourcemapNames = new Set([
|
|
95
|
+
workspacePackage?.projectName ?? packageInstanceName,
|
|
96
|
+
path.posix.basename(dir),
|
|
97
|
+
]);
|
|
98
|
+
for (const name of sourcemapNames) {
|
|
99
|
+
const mapped = options.sourcemapResolver?.resolve(
|
|
100
|
+
SERVER_SCRIPT_SERVICE_PREFIX + name + (rest ? `.${rest}` : '')
|
|
101
|
+
);
|
|
102
|
+
if (mapped) return { file: mapped, isPackageUnderTest };
|
|
58
103
|
}
|
|
59
104
|
|
|
60
|
-
const
|
|
61
|
-
const
|
|
105
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
106
|
+
const base = _resolveHeuristic(dir, rest);
|
|
107
|
+
for (const extension of SOURCE_EXTENSIONS) {
|
|
108
|
+
const file = `${base}${extension}`;
|
|
109
|
+
if (fs.existsSync(path.join(repoRoot, file))) {
|
|
110
|
+
return { file, isPackageUnderTest };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
62
113
|
|
|
63
|
-
|
|
64
|
-
|
|
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[] = [];
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
69
116
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
function _unwrapChunkName(name: string): string {
|
|
118
|
+
const match = name.trim().match(CHUNK_NAME_PATTERN);
|
|
119
|
+
return match ? match[1] : name.trim();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function _splitFirstSegment(dottedPath: string): [string, string] {
|
|
123
|
+
const dotIndex = dottedPath.indexOf('.');
|
|
124
|
+
return dotIndex === -1
|
|
125
|
+
? [dottedPath, '']
|
|
126
|
+
: [dottedPath.slice(0, dotIndex), dottedPath.slice(dotIndex + 1)];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function _resolveHeuristic(dir: string, rest: string): string {
|
|
130
|
+
if (!rest) return `${dir}/src`;
|
|
131
|
+
|
|
132
|
+
// Rojo turns `Maid.spec.lua` into instance `Maid.spec`, so rejoin suffixes.
|
|
133
|
+
const rejoined: string[] = [];
|
|
134
|
+
for (const segment of rest.split('.')) {
|
|
135
|
+
if (rejoined.length > 0 && (segment === 'spec' || segment === 'story')) {
|
|
136
|
+
rejoined[rejoined.length - 1] += `.${segment}`;
|
|
75
137
|
} else {
|
|
76
|
-
rejoined.push(
|
|
138
|
+
rejoined.push(segment);
|
|
77
139
|
}
|
|
78
140
|
}
|
|
79
141
|
|
|
80
|
-
|
|
81
|
-
return `src/${packageSlug}/src/${subPath}.lua`;
|
|
142
|
+
return `${dir}/src/${rejoined.join('/')}`;
|
|
82
143
|
}
|
|
@@ -8,6 +8,10 @@ import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
|
8
8
|
import { type DeployTarget } from '@quenty/nevermore-deploy';
|
|
9
9
|
import { type BatchTarget } from '../../batch/changed-packages-utils.js';
|
|
10
10
|
import { recompressPlace } from '../../build/place-recompression.js';
|
|
11
|
+
import {
|
|
12
|
+
type TestRojoProject,
|
|
13
|
+
findTestProjectSlug,
|
|
14
|
+
} from './test-project-slug.js';
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Budget for one combined place, as uploaded (recompressed). Under the upload
|
|
@@ -40,10 +44,6 @@ export interface CombinedProjectResult {
|
|
|
40
44
|
buildContext: BuildContext;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
interface RojoNode {
|
|
44
|
-
[key: string]: unknown;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
47
|
import { type StepProgress } from '@quenty/cli-output-helpers/reporting';
|
|
48
48
|
|
|
49
49
|
/** Progress callbacks for granular phase reporting during combined builds. */
|
|
@@ -345,20 +345,16 @@ async function _extractSlugAsync(
|
|
|
345
345
|
);
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
const project = JSON.parse(content) as
|
|
349
|
-
|
|
350
|
-
};
|
|
351
|
-
const serverScriptService = project.tree?.ServerScriptService;
|
|
352
|
-
if (!serverScriptService) {
|
|
348
|
+
const project = JSON.parse(content) as TestRojoProject;
|
|
349
|
+
if (!project.tree?.ServerScriptService) {
|
|
353
350
|
throw new Error(
|
|
354
351
|
`Test project for ${batchTargetName} is missing ServerScriptService in tree`
|
|
355
352
|
);
|
|
356
353
|
}
|
|
357
354
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
355
|
+
const slug = findTestProjectSlug(project);
|
|
356
|
+
if (slug) {
|
|
357
|
+
return slug;
|
|
362
358
|
}
|
|
363
359
|
|
|
364
360
|
throw new Error(
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface TestRojoProject {
|
|
2
|
+
tree?: { ServerScriptService?: Record<string, unknown> };
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A package's instance name in its test place: the first non-`$`,
|
|
7
|
+
* non-`Script` key under ServerScriptService.
|
|
8
|
+
*/
|
|
9
|
+
export function findTestProjectSlug(
|
|
10
|
+
project: TestRojoProject
|
|
11
|
+
): string | undefined {
|
|
12
|
+
const serverScriptService = project.tree?.ServerScriptService;
|
|
13
|
+
if (!serverScriptService) return undefined;
|
|
14
|
+
|
|
15
|
+
return Object.keys(serverScriptService).find(
|
|
16
|
+
(key) => !key.startsWith('$') && key !== 'Script'
|
|
17
|
+
);
|
|
18
|
+
}
|