@quenty/nevermore-cli 4.46.0 → 4.47.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 +13 -0
- package/dist/commands/batch-command/batch-deploy-command.d.ts.map +1 -1
- package/dist/commands/batch-command/batch-deploy-command.js +4 -3
- package/dist/commands/batch-command/batch-deploy-command.js.map +1 -1
- package/dist/commands/deploy-command/asset-command.d.ts +44 -0
- package/dist/commands/deploy-command/asset-command.d.ts.map +1 -0
- package/dist/commands/deploy-command/asset-command.js +182 -0
- package/dist/commands/deploy-command/asset-command.js.map +1 -0
- package/dist/commands/deploy-command/index.d.ts.map +1 -1
- package/dist/commands/deploy-command/index.js +62 -0
- package/dist/commands/deploy-command/index.js.map +1 -1
- package/dist/utils/assets/asset-creator.d.ts +32 -0
- package/dist/utils/assets/asset-creator.d.ts.map +1 -0
- package/dist/utils/assets/asset-creator.js +62 -0
- package/dist/utils/assets/asset-creator.js.map +1 -0
- package/dist/utils/assets/asset-creator.test.d.ts +2 -0
- package/dist/utils/assets/asset-creator.test.d.ts.map +1 -0
- package/dist/utils/assets/asset-creator.test.js +71 -0
- package/dist/utils/assets/asset-creator.test.js.map +1 -0
- package/dist/utils/assets/asset-formats.d.ts +28 -0
- package/dist/utils/assets/asset-formats.d.ts.map +1 -0
- package/dist/utils/assets/asset-formats.js +72 -0
- package/dist/utils/assets/asset-formats.js.map +1 -0
- package/dist/utils/assets/asset-formats.test.d.ts +2 -0
- package/dist/utils/assets/asset-formats.test.d.ts.map +1 -0
- package/dist/utils/assets/asset-formats.test.js +43 -0
- package/dist/utils/assets/asset-formats.test.js.map +1 -0
- package/dist/utils/assets/asset-upload-plan.d.ts +27 -0
- package/dist/utils/assets/asset-upload-plan.d.ts.map +1 -0
- package/dist/utils/assets/asset-upload-plan.js +43 -0
- package/dist/utils/assets/asset-upload-plan.js.map +1 -0
- package/dist/utils/assets/asset-upload-plan.test.d.ts +2 -0
- package/dist/utils/assets/asset-upload-plan.test.d.ts.map +1 -0
- package/dist/utils/assets/asset-upload-plan.test.js +57 -0
- package/dist/utils/assets/asset-upload-plan.test.js.map +1 -0
- package/dist/utils/build/upload.d.ts.map +1 -1
- package/dist/utils/build/upload.js +4 -1
- package/dist/utils/build/upload.js.map +1 -1
- package/dist/utils/job-context/cloud-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/cloud-job-context.js +5 -3
- package/dist/utils/job-context/cloud-job-context.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.d.ts +81 -4
- package/dist/utils/open-cloud/open-cloud-client.d.ts.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.js +172 -11
- package/dist/utils/open-cloud/open-cloud-client.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.test.js +258 -0
- package/dist/utils/open-cloud/open-cloud-client.test.js.map +1 -1
- package/dist/utils/open-cloud/rate-limiter.d.ts +29 -2
- package/dist/utils/open-cloud/rate-limiter.d.ts.map +1 -1
- package/dist/utils/open-cloud/rate-limiter.js +98 -20
- package/dist/utils/open-cloud/rate-limiter.js.map +1 -1
- package/dist/utils/open-cloud/rate-limiter.test.js +64 -4
- package/dist/utils/open-cloud/rate-limiter.test.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.test.js +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.test.js.map +1 -1
- package/dist/utils/testing/runner/test-runner.d.ts.map +1 -1
- package/dist/utils/testing/runner/test-runner.js +8 -7
- package/dist/utils/testing/runner/test-runner.js.map +1 -1
- package/dist/utils/testing/runner/test-runner.test.js +1 -1
- package/dist/utils/testing/runner/test-runner.test.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/batch-command/batch-deploy-command.ts +11 -3
- package/src/commands/deploy-command/asset-command.ts +279 -0
- package/src/commands/deploy-command/index.ts +85 -0
- package/src/utils/assets/asset-creator.test.ts +106 -0
- package/src/utils/assets/asset-creator.ts +96 -0
- package/src/utils/assets/asset-formats.test.ts +57 -0
- package/src/utils/assets/asset-formats.ts +102 -0
- package/src/utils/assets/asset-upload-plan.test.ts +75 -0
- package/src/utils/assets/asset-upload-plan.ts +79 -0
- package/src/utils/build/upload.ts +5 -1
- package/src/utils/job-context/cloud-job-context.ts +24 -15
- package/src/utils/open-cloud/open-cloud-client.test.ts +361 -0
- package/src/utils/open-cloud/open-cloud-client.ts +341 -24
- package/src/utils/open-cloud/rate-limiter.test.ts +106 -4
- package/src/utils/open-cloud/rate-limiter.ts +150 -21
- package/src/utils/testing/parsers/batch-log-parser.test.ts +3 -3
- package/src/utils/testing/runner/test-runner.test.ts +1 -1
- package/src/utils/testing/runner/test-runner.ts +8 -9
- package/templates/batch-test-runner.lua +176 -172
- package/templates/game-template/src/scripts/Server/ServerMain.server.lua +3 -2
- package/templates/nevermore-library-package-template/test/scripts/Server/ServerMain.server.lua +3 -2
- package/templates/nevermore-service-package-template/test/scripts/Server/ServerMain.server.lua +3 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `assetType` values `nevermore deploy asset upload` can send to the Open
|
|
5
|
+
* Cloud Assets API. Mesh is left out on purpose: the API only accepts mesh data
|
|
6
|
+
* that was itself downloaded from Roblox, which is not something a file on disk
|
|
7
|
+
* can be recognized as.
|
|
8
|
+
*
|
|
9
|
+
* https://create.roblox.com/docs/cloud/guides/usage-assets
|
|
10
|
+
*/
|
|
11
|
+
export const ASSET_TYPES = [
|
|
12
|
+
'Animation',
|
|
13
|
+
'Audio',
|
|
14
|
+
'Decal',
|
|
15
|
+
'Image',
|
|
16
|
+
'Model',
|
|
17
|
+
'Video',
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
export type AssetType = typeof ASSET_TYPES[number];
|
|
21
|
+
|
|
22
|
+
interface AssetFileFormat {
|
|
23
|
+
/** Sent as the `fileContent` part's type; the API picks its importer from it. */
|
|
24
|
+
contentType: string;
|
|
25
|
+
/** Every type this extension can upload as. The first is the default. */
|
|
26
|
+
assetTypes: readonly AssetType[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// An image defaults to Image rather than Decal: an Image id is usable directly
|
|
30
|
+
// as `rbxassetid://`, where a Decal is a wrapper whose id has to be resolved to
|
|
31
|
+
// the image inside it before an ImageLabel can show it.
|
|
32
|
+
const IMAGE_TYPES = ['Image', 'Decal'] as const;
|
|
33
|
+
|
|
34
|
+
// A Roblox model file holds either, and nothing in its name says which.
|
|
35
|
+
const ROBLOX_MODEL_TYPES = ['Model', 'Animation'] as const;
|
|
36
|
+
|
|
37
|
+
const ASSET_FILE_FORMATS: Record<string, AssetFileFormat> = {
|
|
38
|
+
'.mp3': { contentType: 'audio/mpeg', assetTypes: ['Audio'] },
|
|
39
|
+
'.ogg': { contentType: 'audio/ogg', assetTypes: ['Audio'] },
|
|
40
|
+
'.wav': { contentType: 'audio/wav', assetTypes: ['Audio'] },
|
|
41
|
+
'.flac': { contentType: 'audio/flac', assetTypes: ['Audio'] },
|
|
42
|
+
'.png': { contentType: 'image/png', assetTypes: IMAGE_TYPES },
|
|
43
|
+
'.jpg': { contentType: 'image/jpeg', assetTypes: IMAGE_TYPES },
|
|
44
|
+
'.jpeg': { contentType: 'image/jpeg', assetTypes: IMAGE_TYPES },
|
|
45
|
+
'.bmp': { contentType: 'image/bmp', assetTypes: IMAGE_TYPES },
|
|
46
|
+
'.tga': { contentType: 'image/tga', assetTypes: IMAGE_TYPES },
|
|
47
|
+
'.fbx': { contentType: 'model/fbx', assetTypes: ['Model'] },
|
|
48
|
+
'.gltf': { contentType: 'model/gltf+json', assetTypes: ['Model'] },
|
|
49
|
+
'.glb': { contentType: 'model/gltf-binary', assetTypes: ['Model'] },
|
|
50
|
+
'.rbxm': { contentType: 'model/x-rbxm', assetTypes: ROBLOX_MODEL_TYPES },
|
|
51
|
+
'.rbxmx': { contentType: 'model/x-rbxm', assetTypes: ROBLOX_MODEL_TYPES },
|
|
52
|
+
'.mp4': { contentType: 'video/mp4', assetTypes: ['Video'] },
|
|
53
|
+
'.mov': { contentType: 'video/mov', assetTypes: ['Video'] },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export interface ResolvedAssetFormat {
|
|
57
|
+
assetType: AssetType;
|
|
58
|
+
contentType: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Work out how a file uploads: its asset type and the content type to send.
|
|
63
|
+
* The extension decides both unless `explicitType` picks one of the
|
|
64
|
+
* alternatives it allows (`Decal` for an image, `Animation` for an `.rbxm`).
|
|
65
|
+
*/
|
|
66
|
+
export function resolveAssetFormat(
|
|
67
|
+
filePath: string,
|
|
68
|
+
explicitType?: AssetType
|
|
69
|
+
): ResolvedAssetFormat {
|
|
70
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
71
|
+
const format = ASSET_FILE_FORMATS[extension];
|
|
72
|
+
if (!format) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Cannot upload "${filePath}": ${
|
|
75
|
+
extension === '' ? 'a file with no extension' : `"${extension}"`
|
|
76
|
+
} is not an asset format the Open Cloud Assets API accepts.\n` +
|
|
77
|
+
`Supported: ${Object.keys(ASSET_FILE_FORMATS).join(', ')}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (explicitType != null && !format.assetTypes.includes(explicitType)) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Cannot upload "${filePath}" as ${explicitType}: ` +
|
|
84
|
+
`a "${extension}" file uploads as ${format.assetTypes.join(' or ')}.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
assetType: explicitType ?? format.assetTypes[0]!,
|
|
90
|
+
contentType: format.contentType,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Whether an existing asset of this type can have its file replaced in place,
|
|
96
|
+
* keeping its id. The API documents this for models only, and marks images,
|
|
97
|
+
* audio and video "not available for updating" — so a changed image or sound
|
|
98
|
+
* is always a new asset with a new id.
|
|
99
|
+
*/
|
|
100
|
+
export function canUpdateAssetContent(assetType: AssetType): boolean {
|
|
101
|
+
return assetType === 'Model';
|
|
102
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildAssetUploadPlan } from './asset-upload-plan.js';
|
|
3
|
+
|
|
4
|
+
describe('buildAssetUploadPlan', () => {
|
|
5
|
+
it('names each asset after its file, and describes it by its name', () => {
|
|
6
|
+
expect(
|
|
7
|
+
buildAssetUploadPlan({ files: ['art/icons_1.png', 'models/City.fbx'] })
|
|
8
|
+
).toEqual([
|
|
9
|
+
{
|
|
10
|
+
filePath: 'art/icons_1.png',
|
|
11
|
+
assetType: 'Image',
|
|
12
|
+
contentType: 'image/png',
|
|
13
|
+
displayName: 'icons_1',
|
|
14
|
+
description: 'icons_1',
|
|
15
|
+
assetId: undefined,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
filePath: 'models/City.fbx',
|
|
19
|
+
assetType: 'Model',
|
|
20
|
+
contentType: 'model/fbx',
|
|
21
|
+
displayName: 'City',
|
|
22
|
+
description: 'City',
|
|
23
|
+
assetId: undefined,
|
|
24
|
+
},
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('takes an explicit name and description for a single file', () => {
|
|
29
|
+
const [entry] = buildAssetUploadPlan({
|
|
30
|
+
files: ['City.fbx'],
|
|
31
|
+
name: 'Openfront City',
|
|
32
|
+
description: 'Unit model',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(entry?.displayName).toBe('Openfront City');
|
|
36
|
+
expect(entry?.description).toBe('Unit model');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('refuses one name for several files', () => {
|
|
40
|
+
expect(() =>
|
|
41
|
+
buildAssetUploadPlan({ files: ['a.png', 'b.png'], name: 'Icon' })
|
|
42
|
+
).toThrowError(/--name names one asset, but 2 files were given/);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('plans a replacement when given the id of an existing model', () => {
|
|
46
|
+
const [entry] = buildAssetUploadPlan({
|
|
47
|
+
files: ['City.fbx'],
|
|
48
|
+
assetId: 123,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(entry?.assetId).toBe(123);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('refuses to replace anything but a model', () => {
|
|
55
|
+
// The API has no in-place update for these, so letting the request through
|
|
56
|
+
// would only trade a clear message for whatever the server says instead.
|
|
57
|
+
expect(() =>
|
|
58
|
+
buildAssetUploadPlan({ files: ['icon.png'], assetId: 123 })
|
|
59
|
+
).toThrowError(/only replaces the content of a Model/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('refuses one asset id for several files', () => {
|
|
63
|
+
expect(() =>
|
|
64
|
+
buildAssetUploadPlan({ files: ['a.fbx', 'b.fbx'], assetId: 123 })
|
|
65
|
+
).toThrowError(/--asset-id replaces one asset, but 2 files were given/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('rejects the whole batch when one file cannot be uploaded', () => {
|
|
69
|
+
// Nothing is uploaded until every file is known to be uploadable: a batch
|
|
70
|
+
// that fails partway has already created assets that cannot be taken back.
|
|
71
|
+
expect(() =>
|
|
72
|
+
buildAssetUploadPlan({ files: ['a.png', 'notes.txt'] })
|
|
73
|
+
).toThrowError(/"\.txt" is not an asset format/);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import {
|
|
3
|
+
canUpdateAssetContent,
|
|
4
|
+
resolveAssetFormat,
|
|
5
|
+
type AssetType,
|
|
6
|
+
} from './asset-formats.js';
|
|
7
|
+
|
|
8
|
+
export interface AssetUploadPlanOptions {
|
|
9
|
+
files: string[];
|
|
10
|
+
/** Applied to every file; each must be able to upload as it. */
|
|
11
|
+
type?: AssetType;
|
|
12
|
+
/** Display name. Single file only — defaults to the file's base name. */
|
|
13
|
+
name?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
/** Replace this asset's content instead of creating one. Single file only. */
|
|
16
|
+
assetId?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AssetUploadPlanEntry {
|
|
20
|
+
filePath: string;
|
|
21
|
+
assetType: AssetType;
|
|
22
|
+
contentType: string;
|
|
23
|
+
displayName: string;
|
|
24
|
+
description: string;
|
|
25
|
+
/** Set when the upload replaces an existing asset's content in place. */
|
|
26
|
+
assetId?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Turn the command line into one upload per file, rejecting anything that
|
|
31
|
+
* could not work before a single request is sent — a batch that fails on its
|
|
32
|
+
* fifth file has already created four assets.
|
|
33
|
+
*/
|
|
34
|
+
export function buildAssetUploadPlan(
|
|
35
|
+
options: AssetUploadPlanOptions
|
|
36
|
+
): AssetUploadPlanEntry[] {
|
|
37
|
+
const { files, type, name, description, assetId } = options;
|
|
38
|
+
|
|
39
|
+
if (files.length === 0) {
|
|
40
|
+
throw new Error('No files to upload.');
|
|
41
|
+
}
|
|
42
|
+
if (files.length > 1 && name != null) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`--name names one asset, but ${files.length} files were given. ` +
|
|
45
|
+
`Drop it to name each asset after its file.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (files.length > 1 && assetId != null) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`--asset-id replaces one asset, but ${files.length} files were given.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (assetId != null && (!Number.isSafeInteger(assetId) || assetId < 1)) {
|
|
54
|
+
throw new Error('--asset-id must be a positive integer.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return files.map((filePath) => {
|
|
58
|
+
const { assetType, contentType } = resolveAssetFormat(filePath, type);
|
|
59
|
+
if (assetId != null && !canUpdateAssetContent(assetType)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Cannot replace asset ${assetId} with "${filePath}": the Assets API ` +
|
|
62
|
+
`only replaces the content of a Model. Upload the ${assetType} ` +
|
|
63
|
+
`without --asset-id to create a new asset instead.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const displayName = name ?? path.basename(filePath, path.extname(filePath));
|
|
68
|
+
return {
|
|
69
|
+
filePath,
|
|
70
|
+
assetType,
|
|
71
|
+
contentType,
|
|
72
|
+
displayName,
|
|
73
|
+
// The API requires a description on create, so an omitted one falls back
|
|
74
|
+
// to the name rather than to text nobody chose.
|
|
75
|
+
description: description ?? displayName,
|
|
76
|
+
assetId,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from '@quenty/nevermore-cli-helpers';
|
|
5
5
|
import { type DeployTarget } from '@quenty/nevermore-deploy';
|
|
6
6
|
import { OpenCloudClient } from '../open-cloud/open-cloud-client.js';
|
|
7
|
+
import { reportRateLimitWait } from '../open-cloud/rate-limiter.js';
|
|
7
8
|
import { type BuiltPlace } from './build.js';
|
|
8
9
|
import { type Reporter } from '@quenty/cli-output-helpers/reporting';
|
|
9
10
|
|
|
@@ -53,7 +54,10 @@ export async function uploadPlaceAsync(
|
|
|
53
54
|
target.placeId,
|
|
54
55
|
rbxlPath,
|
|
55
56
|
args.publish,
|
|
56
|
-
onProgress
|
|
57
|
+
onProgress,
|
|
58
|
+
reporter && packageName
|
|
59
|
+
? reportRateLimitWait(reporter, packageName)
|
|
60
|
+
: undefined
|
|
57
61
|
);
|
|
58
62
|
|
|
59
63
|
return { client, apiKey, target, version };
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type LuauTask,
|
|
5
5
|
type OpenCloudClient,
|
|
6
6
|
} from '../open-cloud/open-cloud-client.js';
|
|
7
|
+
import { reportRateLimitWait } from '../open-cloud/rate-limiter.js';
|
|
7
8
|
import { tryRenamePlaceAsync } from '@quenty/nevermore-cli-helpers';
|
|
8
9
|
import { buildPlaceNameAsync, timeoutAsync } from '../nevermore-cli-utils.js';
|
|
9
10
|
import {
|
|
@@ -60,7 +61,8 @@ export class CloudJobContext extends BaseJobContext {
|
|
|
60
61
|
transferredBytes: transferred,
|
|
61
62
|
totalBytes: total,
|
|
62
63
|
});
|
|
63
|
-
}
|
|
64
|
+
},
|
|
65
|
+
reportRateLimitWait(this._reporter, packageName)
|
|
64
66
|
);
|
|
65
67
|
|
|
66
68
|
// Eagerly release build artifacts after upload (disposeAsync is safety net)
|
|
@@ -82,13 +84,16 @@ export class CloudJobContext extends BaseJobContext {
|
|
|
82
84
|
const cloudDeployment = deployment as CloudDeployment;
|
|
83
85
|
const { scriptContent, packageName, timeoutMs = 120_000 } = options;
|
|
84
86
|
|
|
87
|
+
const onRateLimitWait = reportRateLimitWait(this._reporter, packageName);
|
|
88
|
+
|
|
85
89
|
this._reporter.onPackagePhaseChange(packageName, 'scheduling');
|
|
86
90
|
const task = await this._openCloudClient!.createExecutionTaskAsync(
|
|
87
91
|
cloudDeployment.universeId,
|
|
88
92
|
cloudDeployment.placeId,
|
|
89
93
|
cloudDeployment.version,
|
|
90
94
|
scriptContent,
|
|
91
|
-
timeoutMs
|
|
95
|
+
timeoutMs,
|
|
96
|
+
onRateLimitWait
|
|
92
97
|
);
|
|
93
98
|
|
|
94
99
|
// Give the server-side timeout a head start so we observe the cancelled
|
|
@@ -97,19 +102,23 @@ export class CloudJobContext extends BaseJobContext {
|
|
|
97
102
|
|
|
98
103
|
let pollCount = 0;
|
|
99
104
|
const completedTask = await Promise.race([
|
|
100
|
-
this._openCloudClient!.pollTaskCompletionAsync(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
this._openCloudClient!.pollTaskCompletionAsync(
|
|
106
|
+
task.path,
|
|
107
|
+
(state) => {
|
|
108
|
+
pollCount++;
|
|
109
|
+
if (state === 'PROCESSING') {
|
|
110
|
+
this._reporter.onPackagePhaseChange(packageName, 'executing');
|
|
111
|
+
}
|
|
112
|
+
const label = state === 'QUEUED' ? 'queued' : `poll #${pollCount}`;
|
|
113
|
+
this._reporter.onPackageProgressUpdate(packageName, {
|
|
114
|
+
kind: 'steps',
|
|
115
|
+
completed: pollCount,
|
|
116
|
+
total: 0,
|
|
117
|
+
label,
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
onRateLimitWait
|
|
121
|
+
),
|
|
113
122
|
timeoutAsync(
|
|
114
123
|
timeoutMs + CLIENT_TIMEOUT_GRACE_MS,
|
|
115
124
|
`Test timed out after ${timeoutMs / 1000}s`
|