@quenty/nevermore-cli 4.43.3 → 4.44.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 +10 -0
- package/build-scripts/{combine-test-places.luau → combine-test-places.lua} +3 -3
- package/build-scripts/{transform-inject-deploy-metadata.luau → transform-inject-deploy-metadata.lua} +3 -3
- package/dist/commands/batch-command/batch-deploy-command.js +4 -4
- package/dist/commands/batch-command/batch-deploy-command.js.map +1 -1
- package/dist/commands/batch-command/batch-test-command.d.ts.map +1 -1
- package/dist/commands/batch-command/batch-test-command.js +76 -19
- package/dist/commands/batch-command/batch-test-command.js.map +1 -1
- package/dist/commands/deploy-command/deploy-init-utils.d.ts.map +1 -1
- package/dist/commands/deploy-command/deploy-init-utils.js +7 -1
- package/dist/commands/deploy-command/deploy-init-utils.js.map +1 -1
- package/dist/commands/deploy-command/index.js +1 -1
- package/dist/commands/deploy-command/index.js.map +1 -1
- package/dist/commands/init-command/init-game-command.js +2 -2
- package/dist/commands/init-command/init-game-command.js.map +1 -1
- package/dist/commands/install-package-command.d.ts +14 -0
- package/dist/commands/install-package-command.d.ts.map +1 -1
- package/dist/commands/install-package-command.js +65 -12
- package/dist/commands/install-package-command.js.map +1 -1
- package/dist/utils/deploy/deploy-github-columns.d.ts +12 -2
- package/dist/utils/deploy/deploy-github-columns.d.ts.map +1 -1
- package/dist/utils/deploy/deploy-github-columns.js +11 -4
- package/dist/utils/deploy/deploy-github-columns.js.map +1 -1
- package/dist/utils/deploy/deploy-metadata.js +1 -1
- package/dist/utils/deploy/deploy-metadata.js.map +1 -1
- package/dist/utils/job-context/base-job-context.d.ts +2 -2
- package/dist/utils/job-context/base-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/base-job-context.js +1 -1
- package/dist/utils/job-context/base-job-context.js.map +1 -1
- package/dist/utils/job-context/batch-script-job-context.d.ts +37 -2
- package/dist/utils/job-context/batch-script-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/batch-script-job-context.js +59 -22
- package/dist/utils/job-context/batch-script-job-context.js.map +1 -1
- package/dist/utils/job-context/cloud-job-context.d.ts +2 -2
- package/dist/utils/job-context/cloud-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/cloud-job-context.js +10 -5
- package/dist/utils/job-context/cloud-job-context.js.map +1 -1
- package/dist/utils/job-context/cloud-job-context.test.d.ts +7 -0
- package/dist/utils/job-context/cloud-job-context.test.d.ts.map +1 -0
- package/dist/utils/job-context/cloud-job-context.test.js +91 -0
- package/dist/utils/job-context/cloud-job-context.test.js.map +1 -0
- package/dist/utils/job-context/job-context.d.ts +70 -2
- package/dist/utils/job-context/job-context.d.ts.map +1 -1
- package/dist/utils/job-context/local-job-context.d.ts +2 -2
- package/dist/utils/job-context/local-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/local-job-context.js +4 -2
- package/dist/utils/job-context/local-job-context.js.map +1 -1
- package/dist/utils/job-context/local-job-context.test.d.ts +7 -0
- package/dist/utils/job-context/local-job-context.test.d.ts.map +1 -0
- package/dist/utils/job-context/local-job-context.test.js +68 -0
- package/dist/utils/job-context/local-job-context.test.js.map +1 -0
- package/dist/utils/open-cloud/open-cloud-client.d.ts +50 -7
- package/dist/utils/open-cloud/open-cloud-client.d.ts.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.js +49 -13
- package/dist/utils/open-cloud/open-cloud-client.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.test.js +125 -1
- package/dist/utils/open-cloud/open-cloud-client.test.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-logs.characterization.test.d.ts +22 -0
- package/dist/utils/open-cloud/open-cloud-logs.characterization.test.d.ts.map +1 -0
- package/dist/utils/open-cloud/open-cloud-logs.characterization.test.js +202 -0
- package/dist/utils/open-cloud/open-cloud-logs.characterization.test.js.map +1 -0
- package/dist/utils/package-manager-utils.d.ts +29 -0
- package/dist/utils/package-manager-utils.d.ts.map +1 -0
- package/dist/utils/package-manager-utils.js +120 -0
- package/dist/utils/package-manager-utils.js.map +1 -0
- package/dist/utils/package-manager-utils.test.d.ts +2 -0
- package/dist/utils/package-manager-utils.test.d.ts.map +1 -0
- package/dist/utils/package-manager-utils.test.js +121 -0
- package/dist/utils/package-manager-utils.test.js.map +1 -0
- package/dist/utils/testing/batch-run.e2e.test.d.ts +17 -0
- package/dist/utils/testing/batch-run.e2e.test.d.ts.map +1 -0
- package/dist/utils/testing/batch-run.e2e.test.js +343 -0
- package/dist/utils/testing/batch-run.e2e.test.js.map +1 -0
- package/dist/utils/testing/batch-test-runner-template.test.d.ts +11 -0
- package/dist/utils/testing/batch-test-runner-template.test.d.ts.map +1 -0
- package/dist/utils/testing/batch-test-runner-template.test.js +38 -0
- package/dist/utils/testing/batch-test-runner-template.test.js.map +1 -0
- package/dist/utils/testing/log-fetch-stats.d.ts +40 -0
- package/dist/utils/testing/log-fetch-stats.d.ts.map +1 -0
- package/dist/utils/testing/log-fetch-stats.js +18 -0
- package/dist/utils/testing/log-fetch-stats.js.map +1 -0
- package/dist/utils/testing/log-fetch-stats.test.d.ts +2 -0
- package/dist/utils/testing/log-fetch-stats.test.d.ts.map +1 -0
- package/dist/utils/testing/log-fetch-stats.test.js +41 -0
- package/dist/utils/testing/log-fetch-stats.test.js.map +1 -0
- package/dist/utils/testing/parsers/batch-log-parser.d.ts +126 -1
- package/dist/utils/testing/parsers/batch-log-parser.d.ts.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.js +440 -103
- package/dist/utils/testing/parsers/batch-log-parser.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.test.js +494 -1
- package/dist/utils/testing/parsers/batch-log-parser.test.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-renderer.d.ts +79 -0
- package/dist/utils/testing/parsers/batch-log-renderer.d.ts.map +1 -0
- package/dist/utils/testing/parsers/batch-log-renderer.js +241 -0
- package/dist/utils/testing/parsers/batch-log-renderer.js.map +1 -0
- package/dist/utils/testing/parsers/batch-log-renderer.test.d.ts +2 -0
- package/dist/utils/testing/parsers/batch-log-renderer.test.d.ts.map +1 -0
- package/dist/utils/testing/parsers/batch-log-renderer.test.js +202 -0
- package/dist/utils/testing/parsers/batch-log-renderer.test.js.map +1 -0
- package/dist/utils/testing/runner/combined-project-generator.js +1 -1
- package/dist/utils/testing/runner/combined-project-generator.js.map +1 -1
- package/dist/utils/testing/runner/test-runner.d.ts +24 -0
- package/dist/utils/testing/runner/test-runner.d.ts.map +1 -1
- package/dist/utils/testing/runner/test-runner.js +166 -6
- package/dist/utils/testing/runner/test-runner.js.map +1 -1
- package/dist/utils/testing/runner/test-runner.test.js +228 -2
- package/dist/utils/testing/runner/test-runner.test.js.map +1 -1
- package/dist/utils/testing/structured-test-results.d.ts +77 -0
- package/dist/utils/testing/structured-test-results.d.ts.map +1 -0
- package/dist/utils/testing/structured-test-results.js +140 -0
- package/dist/utils/testing/structured-test-results.js.map +1 -0
- package/dist/utils/testing/structured-test-results.test.d.ts +7 -0
- package/dist/utils/testing/structured-test-results.test.d.ts.map +1 -0
- package/dist/utils/testing/structured-test-results.test.js +179 -0
- package/dist/utils/testing/structured-test-results.test.js.map +1 -0
- package/package.json +6 -6
- package/src/commands/batch-command/batch-deploy-command.ts +4 -4
- package/src/commands/batch-command/batch-test-command.ts +94 -19
- package/src/commands/deploy-command/deploy-init-utils.ts +7 -1
- package/src/commands/deploy-command/index.ts +1 -1
- package/src/commands/init-command/init-game-command.ts +2 -2
- package/src/commands/install-package-command.ts +107 -14
- package/src/utils/deploy/deploy-github-columns.ts +14 -4
- package/src/utils/deploy/deploy-metadata.ts +1 -1
- package/src/utils/job-context/base-job-context.ts +3 -2
- package/src/utils/job-context/batch-script-job-context.ts +98 -17
- package/src/utils/job-context/cloud-job-context.test.ts +112 -0
- package/src/utils/job-context/cloud-job-context.ts +12 -6
- package/src/utils/job-context/job-context.ts +71 -2
- package/src/utils/job-context/local-job-context.test.ts +89 -0
- package/src/utils/job-context/local-job-context.ts +7 -3
- package/src/utils/open-cloud/open-cloud-client.test.ts +155 -1
- package/src/utils/open-cloud/open-cloud-client.ts +94 -18
- package/src/utils/open-cloud/open-cloud-logs.characterization.test.ts +277 -0
- package/src/utils/package-manager-utils.test.ts +174 -0
- package/src/utils/package-manager-utils.ts +148 -0
- package/src/utils/testing/batch-run.e2e.test.ts +425 -0
- package/src/utils/testing/batch-test-runner-template.test.ts +62 -0
- package/src/utils/testing/log-fetch-stats.test.ts +48 -0
- package/src/utils/testing/log-fetch-stats.ts +55 -0
- package/src/utils/testing/parsers/batch-log-parser.test.ts +660 -1
- package/src/utils/testing/parsers/batch-log-parser.ts +721 -116
- package/src/utils/testing/parsers/batch-log-renderer.test.ts +330 -0
- package/src/utils/testing/parsers/batch-log-renderer.ts +329 -0
- package/src/utils/testing/runner/combined-project-generator.ts +1 -1
- package/src/utils/testing/runner/test-runner.test.ts +332 -2
- package/src/utils/testing/runner/test-runner.ts +231 -9
- package/src/utils/testing/structured-test-results.test.ts +267 -0
- package/src/utils/testing/structured-test-results.ts +199 -0
- package/templates/batch-test-runner.lua +242 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/templates/batch-test-runner.luau +0 -137
- /package/build-scripts/{smoke-test-server.luau → smoke-test-server.lua} +0 -0
- /package/build-scripts/{transform-rojo-merge-place.luau → transform-rojo-merge-place.lua} +0 -0
|
@@ -6,9 +6,17 @@ import { Argv, CommandModule } from 'yargs';
|
|
|
6
6
|
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
7
7
|
import { NevermoreGlobalArgs } from '../args/global-args.js';
|
|
8
8
|
import { runCommandAsync } from '../utils/nevermore-cli-utils.js';
|
|
9
|
+
import {
|
|
10
|
+
buildAddPackagesCommand,
|
|
11
|
+
detectPackageManagerAsync,
|
|
12
|
+
isPackageManager,
|
|
13
|
+
PACKAGE_MANAGERS,
|
|
14
|
+
PackageManager,
|
|
15
|
+
} from '../utils/package-manager-utils.js';
|
|
9
16
|
|
|
10
17
|
export interface InstallPackageArgs extends NevermoreGlobalArgs {
|
|
11
18
|
packages: string[];
|
|
19
|
+
packageManager?: string;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
/**
|
|
@@ -27,19 +35,65 @@ export class InstallPackageCommand<T>
|
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Lists the published `@quenty/*` package names, without the scope.
|
|
40
|
+
*
|
|
41
|
+
* The registry caps search results at 250 per request, well under the number
|
|
42
|
+
* of published packages, so this pages through until the reported total is
|
|
43
|
+
* covered.
|
|
44
|
+
*/
|
|
30
45
|
private static async _getPackages(): Promise<string[]> {
|
|
46
|
+
const pageSize = 250;
|
|
47
|
+
const names = new Set<string>();
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
let from = 0;
|
|
51
|
+
for (;;) {
|
|
52
|
+
const response = await fetch(
|
|
53
|
+
`https://registry.npmjs.org/-/v1/search?text=@quenty/&size=${pageSize}&from=${from}`
|
|
54
|
+
);
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
const objects: any[] = data.objects ?? [];
|
|
57
|
+
|
|
58
|
+
for (const entry of objects) {
|
|
59
|
+
const name: string = entry.package?.name ?? '';
|
|
60
|
+
if (name.startsWith('@quenty/')) {
|
|
61
|
+
names.add(name.slice('@quenty/'.length));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
from += objects.length;
|
|
66
|
+
if (objects.length < pageSize || from >= (data.total ?? 0)) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [...names].sort();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Asks the registry about one package directly. Returns undefined when the
|
|
79
|
+
* registry can't be reached, so an offline run doesn't look like a typo.
|
|
80
|
+
*/
|
|
81
|
+
private static async _packageExistsAsync(
|
|
82
|
+
scopedName: string
|
|
83
|
+
): Promise<boolean | undefined> {
|
|
31
84
|
try {
|
|
32
85
|
const response = await fetch(
|
|
33
|
-
|
|
86
|
+
`https://registry.npmjs.org/${scopedName.replace('/', '%2f')}`,
|
|
87
|
+
{ method: 'HEAD' }
|
|
34
88
|
);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
89
|
+
|
|
90
|
+
if (response.status === 404) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return response.ok ? true : undefined;
|
|
41
95
|
} catch {
|
|
42
|
-
return
|
|
96
|
+
return undefined;
|
|
43
97
|
}
|
|
44
98
|
}
|
|
45
99
|
|
|
@@ -53,9 +107,34 @@ export class InstallPackageCommand<T>
|
|
|
53
107
|
return packages.filter((name) => !current || name.startsWith(current));
|
|
54
108
|
},
|
|
55
109
|
});
|
|
110
|
+
args.option('package-manager', {
|
|
111
|
+
describe:
|
|
112
|
+
'Package manager to install with. Detected from the project by default.',
|
|
113
|
+
type: 'string',
|
|
114
|
+
choices: PACKAGE_MANAGERS as unknown as string[],
|
|
115
|
+
});
|
|
56
116
|
return args as Argv<InstallPackageArgs>;
|
|
57
117
|
}
|
|
58
118
|
|
|
119
|
+
private static async _resolvePackageManagerAsync(
|
|
120
|
+
args: InstallPackageArgs,
|
|
121
|
+
srcRoot: string
|
|
122
|
+
): Promise<PackageManager> {
|
|
123
|
+
const requested = args.packageManager;
|
|
124
|
+
if (requested) {
|
|
125
|
+
if (!isPackageManager(requested)) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Unsupported package manager '${requested}'. Expected one of: ${PACKAGE_MANAGERS.join(
|
|
128
|
+
', '
|
|
129
|
+
)}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return requested;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return detectPackageManagerAsync(srcRoot);
|
|
136
|
+
}
|
|
137
|
+
|
|
59
138
|
public async handler(args: InstallPackageArgs) {
|
|
60
139
|
const srcRoot = process.cwd();
|
|
61
140
|
|
|
@@ -67,22 +146,36 @@ export class InstallPackageCommand<T>
|
|
|
67
146
|
InstallPackageCommand._validatePackageName(packageName)
|
|
68
147
|
);
|
|
69
148
|
|
|
70
|
-
const
|
|
149
|
+
const prefixedPackages = args.packages.map(
|
|
150
|
+
(packageName) => `@quenty/${packageName}`
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const existence = await Promise.all(
|
|
154
|
+
prefixedPackages.map((scopedName) =>
|
|
155
|
+
InstallPackageCommand._packageExistsAsync(scopedName)
|
|
156
|
+
)
|
|
157
|
+
);
|
|
71
158
|
const invalidPackages = args.packages.filter(
|
|
72
|
-
(
|
|
159
|
+
(_, index) => existence[index] === false
|
|
73
160
|
);
|
|
74
161
|
|
|
75
162
|
if (invalidPackages.length > 0) {
|
|
76
163
|
throw new Error(`Invalid packages: ${invalidPackages.join(', ')}`);
|
|
77
164
|
}
|
|
78
165
|
|
|
79
|
-
const
|
|
80
|
-
(
|
|
166
|
+
const packageManager =
|
|
167
|
+
await InstallPackageCommand._resolvePackageManagerAsync(args, srcRoot);
|
|
168
|
+
|
|
169
|
+
OutputHelper.info(
|
|
170
|
+
`Installing packages with ${packageManager}: ${args.packages.join(', ')}`
|
|
81
171
|
);
|
|
82
172
|
|
|
83
|
-
|
|
173
|
+
const { command, args: commandArgs } = buildAddPackagesCommand(
|
|
174
|
+
packageManager,
|
|
175
|
+
prefixedPackages
|
|
176
|
+
);
|
|
84
177
|
|
|
85
|
-
await runCommandAsync(args,
|
|
178
|
+
await runCommandAsync(args, command, commandArgs, {
|
|
86
179
|
cwd: srcRoot,
|
|
87
180
|
});
|
|
88
181
|
}
|
|
@@ -11,16 +11,26 @@ export interface BatchDeployResult extends PackageResult {
|
|
|
11
11
|
placeId: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Shared config for the deploy results GitHub comment reporter.
|
|
16
|
+
*
|
|
17
|
+
* Takes the labels rather than fixing them, because a publish and a deploy are
|
|
18
|
+
* different words and the command already knows which it is doing. Without
|
|
19
|
+
* this the comment said "Failed" for a run whose terminal output said "DEPLOY
|
|
20
|
+
* FAILED" — the same run, described two ways.
|
|
21
|
+
*/
|
|
22
|
+
export function createDeployCommentConfig(labels?: {
|
|
23
|
+
successLabel?: string;
|
|
24
|
+
failureLabel?: string;
|
|
25
|
+
}): GithubCommentTableConfig {
|
|
16
26
|
return {
|
|
17
27
|
heading: 'Deploy Results',
|
|
18
28
|
commentMarker: '<!-- nevermore-deploy-results -->',
|
|
19
29
|
sectionId: 'deploy',
|
|
20
30
|
extraColumns: [createErrorColumn(), createTryItColumn()],
|
|
21
31
|
errorHeading: 'Deploy Results',
|
|
22
|
-
successLabel: 'Deployed',
|
|
23
|
-
failureLabel: '
|
|
32
|
+
successLabel: labels?.successLabel ?? 'Deployed',
|
|
33
|
+
failureLabel: labels?.failureLabel ?? 'DEPLOY FAILED',
|
|
24
34
|
summaryVerb: 'deployed',
|
|
25
35
|
};
|
|
26
36
|
}
|
|
@@ -25,7 +25,7 @@ export type DeployMetadataAttributes = Record<
|
|
|
25
25
|
const INJECT_SCRIPT_PATH = resolvePackagePath(
|
|
26
26
|
import.meta.url,
|
|
27
27
|
'build-scripts',
|
|
28
|
-
'transform-inject-deploy-metadata.
|
|
28
|
+
'transform-inject-deploy-metadata.lua'
|
|
29
29
|
);
|
|
30
30
|
|
|
31
31
|
function _git(args: string[]): string | undefined {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type DeployPlaceOptions,
|
|
18
18
|
type RunScriptOptions,
|
|
19
19
|
type ScriptRunResult,
|
|
20
|
+
type JobLogs,
|
|
20
21
|
} from './job-context.js';
|
|
21
22
|
import { type OpenCloudClient } from '../open-cloud/open-cloud-client.js';
|
|
22
23
|
import { type BasePlaceResolver } from '@quenty/nevermore-deploy';
|
|
@@ -24,7 +25,7 @@ import { type BasePlaceResolver } from '@quenty/nevermore-deploy';
|
|
|
24
25
|
const MERGE_SCRIPT_PATH = resolvePackagePath(
|
|
25
26
|
import.meta.url,
|
|
26
27
|
'build-scripts',
|
|
27
|
-
'transform-rojo-merge-place.
|
|
28
|
+
'transform-rojo-merge-place.lua'
|
|
28
29
|
);
|
|
29
30
|
|
|
30
31
|
interface SharedRojoBuild {
|
|
@@ -300,7 +301,7 @@ export abstract class BaseJobContext implements JobContext {
|
|
|
300
301
|
deployment: Deployment,
|
|
301
302
|
options: RunScriptOptions
|
|
302
303
|
): Promise<ScriptRunResult>;
|
|
303
|
-
abstract getLogsAsync(deployment: Deployment): Promise<
|
|
304
|
+
abstract getLogsAsync(deployment: Deployment): Promise<JobLogs>;
|
|
304
305
|
abstract releaseAsync(deployment: Deployment): Promise<void>;
|
|
305
306
|
|
|
306
307
|
async disposeAsync(): Promise<void> {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type JobContext,
|
|
13
13
|
type RunScriptOptions,
|
|
14
14
|
type ScriptRunResult,
|
|
15
|
+
type JobLogs,
|
|
15
16
|
} from './job-context.js';
|
|
16
17
|
import { type BuildPlaceOptions } from '../build/build.js';
|
|
17
18
|
import { type BatchTarget } from '../batch/changed-packages-utils.js';
|
|
@@ -21,9 +22,15 @@ import {
|
|
|
21
22
|
generateCombinedProjectAsync,
|
|
22
23
|
} from '../testing/runner/combined-project-generator.js';
|
|
23
24
|
import {
|
|
25
|
+
type BatchDiagnosticLevel,
|
|
24
26
|
type BatchPackageResult,
|
|
25
27
|
parseBatchTestLogs,
|
|
26
28
|
} from '../testing/parsers/batch-log-parser.js';
|
|
29
|
+
import { type LogFetchStats } from '../testing/log-fetch-stats.js';
|
|
30
|
+
import {
|
|
31
|
+
toRenderableLines,
|
|
32
|
+
type RenderableLogLine,
|
|
33
|
+
} from '../testing/parsers/batch-log-renderer.js';
|
|
27
34
|
|
|
28
35
|
/** Per-package deployment handle. Wraps a shared inner deployment. */
|
|
29
36
|
class BatchDeployment implements Deployment {
|
|
@@ -50,6 +57,28 @@ interface CombinedBuildState {
|
|
|
50
57
|
* package configured with one — those belong in `nevermore batch test
|
|
51
58
|
* --no-aggregated`, or in an integration deploy.
|
|
52
59
|
*/
|
|
60
|
+
/**
|
|
61
|
+
* Everything one aggregated batch produced, as data.
|
|
62
|
+
*
|
|
63
|
+
* The execution used to parse, render and print from in here. Printing from a
|
|
64
|
+
* job context put presentation in the transport, bypassed whatever reporter the
|
|
65
|
+
* command had chosen — output written straight to the console is invisible to a
|
|
66
|
+
* SpinnerReporter's capture — and left the whole path untestable without spying
|
|
67
|
+
* on `console.log`. The context returns this instead, and the command layer
|
|
68
|
+
* decides what to show.
|
|
69
|
+
*/
|
|
70
|
+
export interface BatchExecutionOutcome {
|
|
71
|
+
results: Map<string, BatchPackageResult>;
|
|
72
|
+
/** The run's output, typed by severity and not yet rendered. */
|
|
73
|
+
lines: RenderableLogLine[];
|
|
74
|
+
/** What the fetch had to do to collect it; absent for a transport that cannot say. */
|
|
75
|
+
stats?: LogFetchStats;
|
|
76
|
+
/** What reading the log turned up, to report after the log itself. */
|
|
77
|
+
diagnostics: Array<{ level: BatchDiagnosticLevel; message: string }>;
|
|
78
|
+
/** Section slug to package name, for whoever titles the sections. */
|
|
79
|
+
slugToPackage: Map<string, string>;
|
|
80
|
+
}
|
|
81
|
+
|
|
53
82
|
export class BatchScriptJobContext implements JobContext {
|
|
54
83
|
private _inner: JobContext;
|
|
55
84
|
private _batchTargets: BatchTarget[];
|
|
@@ -62,7 +91,7 @@ export class BatchScriptJobContext implements JobContext {
|
|
|
62
91
|
// Lazy-promise state
|
|
63
92
|
private _combinedBuildPromise?: Promise<CombinedBuildState>;
|
|
64
93
|
private _deployPromise?: Promise<Deployment>;
|
|
65
|
-
private _executionPromise?: Promise<
|
|
94
|
+
private _executionPromise?: Promise<BatchExecutionOutcome>;
|
|
66
95
|
|
|
67
96
|
// State for cleanup
|
|
68
97
|
private _combinedBuildContext?: BuildContext;
|
|
@@ -124,8 +153,8 @@ export class BatchScriptJobContext implements JobContext {
|
|
|
124
153
|
options: RunScriptOptions
|
|
125
154
|
): Promise<ScriptRunResult> {
|
|
126
155
|
const batchDeployment = deployment as BatchDeployment;
|
|
127
|
-
const
|
|
128
|
-
const result =
|
|
156
|
+
const { results } = await this._getBatchExecutionAsync();
|
|
157
|
+
const result = results.get(batchDeployment.packageName);
|
|
129
158
|
|
|
130
159
|
if (!result) {
|
|
131
160
|
return { success: false };
|
|
@@ -135,15 +164,39 @@ export class BatchScriptJobContext implements JobContext {
|
|
|
135
164
|
success: result.success,
|
|
136
165
|
durationMs: result.durationMs,
|
|
137
166
|
errorMessage: result.error,
|
|
167
|
+
// returnValues stays absent: one execution covers every package, so
|
|
168
|
+
// nothing it returns belongs to any single one of them. The batch runner
|
|
169
|
+
// folds each package's own results into the batch summary, which the log
|
|
170
|
+
// parser has already split back out — so they are handed over decoded
|
|
171
|
+
// rather than left for the caller to find in a return value that is not
|
|
172
|
+
// this package's.
|
|
173
|
+
testResults: result.testResults,
|
|
174
|
+
logsLost: result.logsLost,
|
|
138
175
|
};
|
|
139
176
|
}
|
|
140
177
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Run the batch if it has not run, and hand back everything it produced.
|
|
180
|
+
*
|
|
181
|
+
* Public so the command layer can render the run once, before the
|
|
182
|
+
* per-package loop asks for results — which is also what keeps the shared
|
|
183
|
+
* build and upload out of any one package's async context.
|
|
184
|
+
*/
|
|
185
|
+
async getExecutionOutcomeAsync(): Promise<BatchExecutionOutcome> {
|
|
186
|
+
return this._getBatchExecutionAsync();
|
|
187
|
+
}
|
|
145
188
|
|
|
146
|
-
|
|
189
|
+
async getLogsAsync(deployment: Deployment): Promise<JobLogs> {
|
|
190
|
+
const batchDeployment = deployment as BatchDeployment;
|
|
191
|
+
const { results, stats } = await this._getBatchExecutionAsync();
|
|
192
|
+
const result = results.get(batchDeployment.packageName);
|
|
193
|
+
|
|
194
|
+
// The text is this package's section; the stats describe the one fetch that
|
|
195
|
+
// pulled every section, which is what happened to any package whose own
|
|
196
|
+
// section went missing from it. Messages stay empty: severity is per
|
|
197
|
+
// message across the whole window, and slicing it per package would need
|
|
198
|
+
// the section boundaries the parser already resolved.
|
|
199
|
+
return { text: result?.logs ?? '', messages: [], stats };
|
|
147
200
|
}
|
|
148
201
|
|
|
149
202
|
async releaseAsync(_deployment: Deployment): Promise<void> {
|
|
@@ -257,7 +310,7 @@ export class BatchScriptJobContext implements JobContext {
|
|
|
257
310
|
return deployment;
|
|
258
311
|
}
|
|
259
312
|
|
|
260
|
-
private _getBatchExecutionAsync(): Promise<
|
|
313
|
+
private _getBatchExecutionAsync(): Promise<BatchExecutionOutcome> {
|
|
261
314
|
if (!this._executionPromise) {
|
|
262
315
|
this._executionPromise = this._doBatchExecutionAsync();
|
|
263
316
|
// On rejection, clear the promise so that a retry re-triggers the batch
|
|
@@ -268,9 +321,7 @@ export class BatchScriptJobContext implements JobContext {
|
|
|
268
321
|
return this._executionPromise;
|
|
269
322
|
}
|
|
270
323
|
|
|
271
|
-
private async _doBatchExecutionAsync(): Promise<
|
|
272
|
-
Map<string, BatchPackageResult>
|
|
273
|
-
> {
|
|
324
|
+
private async _doBatchExecutionAsync(): Promise<BatchExecutionOutcome> {
|
|
274
325
|
const buildState = await this._getCombinedBuildAsync();
|
|
275
326
|
const deployment = await this._getSharedDeploymentAsync();
|
|
276
327
|
const { slugMap } = buildState.combinedResult;
|
|
@@ -278,7 +329,7 @@ export class BatchScriptJobContext implements JobContext {
|
|
|
278
329
|
// Build the batch Luau script from the template
|
|
279
330
|
const templatePath = resolveTemplatePath(
|
|
280
331
|
import.meta.url,
|
|
281
|
-
'batch-test-runner.
|
|
332
|
+
'batch-test-runner.lua'
|
|
282
333
|
);
|
|
283
334
|
const template = await fs.readFile(templatePath, 'utf-8');
|
|
284
335
|
|
|
@@ -300,8 +351,9 @@ export class BatchScriptJobContext implements JobContext {
|
|
|
300
351
|
timeoutMs: this._batchTimeoutMs,
|
|
301
352
|
});
|
|
302
353
|
|
|
303
|
-
// Fetch the combined logs
|
|
304
|
-
const
|
|
354
|
+
// Fetch the combined logs, with the severity and fetch that produced them.
|
|
355
|
+
const logs = await this._inner.getLogsAsync(deployment);
|
|
356
|
+
const rawLogs = logs.text;
|
|
305
357
|
|
|
306
358
|
if (!result.success) {
|
|
307
359
|
const stateInfo = result.taskState ? ` (state: ${result.taskState})` : '';
|
|
@@ -319,7 +371,36 @@ export class BatchScriptJobContext implements JobContext {
|
|
|
319
371
|
OutputHelper.verbose(`Raw batch logs:\n${rawLogs || '(empty)'}`);
|
|
320
372
|
}
|
|
321
373
|
|
|
322
|
-
// Parse into per-package results
|
|
323
|
-
|
|
374
|
+
// Parse into per-package results. Diagnostics are collected rather than
|
|
375
|
+
// printed: they describe the log window, so they belong after it, and that
|
|
376
|
+
// is the caller's ordering to make.
|
|
377
|
+
const diagnostics: Array<{ level: BatchDiagnosticLevel; message: string }> =
|
|
378
|
+
[];
|
|
379
|
+
const results = parseBatchTestLogs(rawLogs, slugMap, {
|
|
380
|
+
logFetchStats: logs.stats,
|
|
381
|
+
// The batch script returns the same summary it prints. Handing both over
|
|
382
|
+
// lets the parser read the copy that cannot arrive truncated, and fall
|
|
383
|
+
// back to the printed one on a transport that carries no return channel.
|
|
384
|
+
returnValues: result.returnValues,
|
|
385
|
+
onDiagnostic: (level, message) => diagnostics.push({ level, message }),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const slugToPackage = new Map<string, string>();
|
|
389
|
+
for (const [packageName, slug] of slugMap) {
|
|
390
|
+
slugToPackage.set(slug, packageName);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Typed messages when the transport carries severity; otherwise the joined
|
|
394
|
+
// text, which renders uncolored rather than guessed at.
|
|
395
|
+
const messages =
|
|
396
|
+
logs.messages.length > 0 ? logs.messages : [{ message: rawLogs }];
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
results,
|
|
400
|
+
lines: toRenderableLines(messages),
|
|
401
|
+
stats: logs.stats,
|
|
402
|
+
diagnostics,
|
|
403
|
+
slugToPackage,
|
|
404
|
+
};
|
|
324
405
|
}
|
|
325
406
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for CloudJobContext.runScriptAsync — validates what a finished
|
|
3
|
+
* Open Cloud task reports back as a ScriptRunResult, in particular that a task
|
|
4
|
+
* which produced no output stays distinguishable from one that returned nothing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
8
|
+
import { type Reporter } from '@quenty/cli-output-helpers/reporting';
|
|
9
|
+
import { CloudJobContext } from './cloud-job-context.js';
|
|
10
|
+
import {
|
|
11
|
+
type LuauTask,
|
|
12
|
+
type OpenCloudClient,
|
|
13
|
+
} from '../open-cloud/open-cloud-client.js';
|
|
14
|
+
import { type Deployment } from './job-context.js';
|
|
15
|
+
|
|
16
|
+
function createReporter(): Reporter {
|
|
17
|
+
return {
|
|
18
|
+
onPackagePhaseChange: vi.fn(),
|
|
19
|
+
onPackageProgressUpdate: vi.fn(),
|
|
20
|
+
onPackageStart: vi.fn(),
|
|
21
|
+
onPackageResult: vi.fn(),
|
|
22
|
+
} as unknown as Reporter;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A client whose task completes as `completedTask`. The real deployment handle
|
|
27
|
+
* is private to the context, so the test passes the three fields runScriptAsync
|
|
28
|
+
* reads off it.
|
|
29
|
+
*/
|
|
30
|
+
function createContext(completedTask: Partial<LuauTask>) {
|
|
31
|
+
const task = {
|
|
32
|
+
path: 'universes/1/places/2/versions/3/luau-execution-session-tasks/4',
|
|
33
|
+
createTime: '2026-01-01T00:00:00Z',
|
|
34
|
+
updateTime: '2026-01-01T00:01:00Z',
|
|
35
|
+
user: 'users/1',
|
|
36
|
+
state: 'COMPLETE',
|
|
37
|
+
script: 'return 1',
|
|
38
|
+
...completedTask,
|
|
39
|
+
} as LuauTask;
|
|
40
|
+
|
|
41
|
+
const client = {
|
|
42
|
+
createExecutionTaskAsync: vi.fn(async () => task),
|
|
43
|
+
pollTaskCompletionAsync: vi.fn(async () => task),
|
|
44
|
+
} as unknown as OpenCloudClient;
|
|
45
|
+
|
|
46
|
+
const context = new CloudJobContext(createReporter(), client);
|
|
47
|
+
const deployment = {
|
|
48
|
+
universeId: 1,
|
|
49
|
+
placeId: 2,
|
|
50
|
+
version: 3,
|
|
51
|
+
} as unknown as Deployment;
|
|
52
|
+
|
|
53
|
+
return { context, deployment };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('CloudJobContext.runScriptAsync', () => {
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
vi.useRealTimers();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('reports the values the script returned', async () => {
|
|
62
|
+
// Fake timers keep the client-side timeout race from leaving a live timer
|
|
63
|
+
// behind; nothing in the run itself waits on one.
|
|
64
|
+
vi.useFakeTimers();
|
|
65
|
+
const { context, deployment } = createContext({
|
|
66
|
+
state: 'COMPLETE',
|
|
67
|
+
output: { results: [{ slug: 'maid', counts: { passed: 1014 } }] },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const result = await context.runScriptAsync(deployment, {
|
|
71
|
+
scriptContent: 'return results',
|
|
72
|
+
packageName: 'maid',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(result.success).toBe(true);
|
|
76
|
+
expect(result.returnValues).toEqual([
|
|
77
|
+
{ slug: 'maid', counts: { passed: 1014 } },
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('reports an empty result when the task returned nothing', async () => {
|
|
82
|
+
vi.useFakeTimers();
|
|
83
|
+
const { context, deployment } = createContext({
|
|
84
|
+
state: 'COMPLETE',
|
|
85
|
+
output: {},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const result = await context.runScriptAsync(deployment, {
|
|
89
|
+
scriptContent: 'print("hi")',
|
|
90
|
+
packageName: 'maid',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(result.returnValues).toEqual([]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('leaves returnValues absent when a failed task carried no output', async () => {
|
|
97
|
+
vi.useFakeTimers();
|
|
98
|
+
const { context, deployment } = createContext({
|
|
99
|
+
state: 'FAILED',
|
|
100
|
+
output: undefined,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const result = await context.runScriptAsync(deployment, {
|
|
104
|
+
scriptContent: 'return huge',
|
|
105
|
+
packageName: 'maid',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(result.success).toBe(false);
|
|
109
|
+
expect(result.taskState).toBe('FAILED');
|
|
110
|
+
expect(result.returnValues).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Reporter } from '@quenty/cli-output-helpers/reporting';
|
|
2
2
|
import {
|
|
3
|
+
getTaskReturnValues,
|
|
3
4
|
type LuauTask,
|
|
4
5
|
type OpenCloudClient,
|
|
5
6
|
} from '../open-cloud/open-cloud-client.js';
|
|
@@ -8,6 +9,7 @@ import { buildPlaceNameAsync, timeoutAsync } from '../nevermore-cli-utils.js';
|
|
|
8
9
|
import {
|
|
9
10
|
type Deployment,
|
|
10
11
|
type DeployPlaceOptions,
|
|
12
|
+
type JobLogs,
|
|
11
13
|
type RunScriptOptions,
|
|
12
14
|
type ScriptRunResult,
|
|
13
15
|
} from './job-context.js';
|
|
@@ -134,27 +136,31 @@ export class CloudJobContext extends BaseJobContext {
|
|
|
134
136
|
success: completedTask.state === 'COMPLETE',
|
|
135
137
|
taskState: completedTask.state,
|
|
136
138
|
errorMessage,
|
|
139
|
+
returnValues: getTaskReturnValues(completedTask),
|
|
137
140
|
};
|
|
138
141
|
}
|
|
139
142
|
|
|
140
|
-
async getLogsAsync(deployment: Deployment): Promise<
|
|
143
|
+
async getLogsAsync(deployment: Deployment): Promise<JobLogs> {
|
|
141
144
|
const cloudDeployment = deployment as CloudDeployment;
|
|
142
145
|
|
|
143
146
|
if (!cloudDeployment.taskPath) {
|
|
144
147
|
throw new Error('No task has been run yet');
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
const
|
|
150
|
+
const fetched = await this._openCloudClient!.getRawTaskLogsAsync(
|
|
148
151
|
cloudDeployment.taskPath
|
|
149
152
|
);
|
|
150
153
|
|
|
151
154
|
if (cloudDeployment.taskState && cloudDeployment.taskState !== 'COMPLETE') {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.join('\n')
|
|
155
|
+
const note = `Task ended with state: ${cloudDeployment.taskState}`;
|
|
156
|
+
return {
|
|
157
|
+
text: [fetched.text, note].filter(Boolean).join('\n'),
|
|
158
|
+
messages: [...fetched.messages, { message: note }],
|
|
159
|
+
stats: fetched.stats,
|
|
160
|
+
};
|
|
155
161
|
}
|
|
156
162
|
|
|
157
|
-
return
|
|
163
|
+
return fetched;
|
|
158
164
|
}
|
|
159
165
|
|
|
160
166
|
async releaseAsync(_deployment: Deployment): Promise<void> {
|