@quenty/nevermore-cli 4.37.0 → 4.38.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 +6 -0
- package/dist/commands/deploy-command/index.d.ts.map +1 -1
- package/dist/commands/deploy-command/index.js +43 -0
- package/dist/commands/deploy-command/index.js.map +1 -1
- package/dist/commands/deploy-command/version-command.d.ts +21 -0
- package/dist/commands/deploy-command/version-command.d.ts.map +1 -0
- package/dist/commands/deploy-command/version-command.js +206 -0
- package/dist/commands/deploy-command/version-command.js.map +1 -0
- package/dist/utils/build/deploy-config.d.ts +8 -0
- package/dist/utils/build/deploy-config.d.ts.map +1 -1
- package/dist/utils/build/deploy-config.js +5 -0
- package/dist/utils/build/deploy-config.js.map +1 -1
- package/dist/utils/build/deploy-config.test.js +86 -1
- package/dist/utils/build/deploy-config.test.js.map +1 -1
- package/dist/utils/job-context/base-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/base-job-context.js +4 -2
- package/dist/utils/job-context/base-job-context.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.d.ts +12 -1
- package/dist/utils/open-cloud/open-cloud-client.d.ts.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.js +49 -4
- package/dist/utils/open-cloud/open-cloud-client.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.test.js +89 -0
- package/dist/utils/open-cloud/open-cloud-client.test.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/deploy-command/index.ts +71 -0
- package/src/commands/deploy-command/version-command.ts +311 -0
- package/src/utils/build/deploy-config.test.ts +98 -0
- package/src/utils/build/deploy-config.ts +17 -0
- package/src/utils/job-context/base-job-context.ts +7 -2
- package/src/utils/open-cloud/open-cloud-client.test.ts +132 -0
- package/src/utils/open-cloud/open-cloud-client.ts +84 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -390,17 +390,26 @@ export class OpenCloudClient {
|
|
|
390
390
|
* Requires the `legacy-asset:manage` scope on the API key.
|
|
391
391
|
*
|
|
392
392
|
* Two-step process: fetch a temporary CDN URL, then download the binary.
|
|
393
|
+
*
|
|
394
|
+
* Pass `version` to fetch a specific published version instead of the latest.
|
|
395
|
+
* This is how deploys pin a `basePlace` so a broken Studio edit can't leak in.
|
|
393
396
|
*/
|
|
394
397
|
async downloadPlaceAsync(
|
|
395
398
|
universeId: number,
|
|
396
399
|
placeId: number,
|
|
397
|
-
onProgress?: (transferredBytes: number, totalBytes: number) => void
|
|
400
|
+
onProgress?: (transferredBytes: number, totalBytes: number) => void,
|
|
401
|
+
version?: number
|
|
398
402
|
): Promise<Buffer> {
|
|
399
403
|
const apiKey = await this._resolveApiKeyAsync();
|
|
400
|
-
const url =
|
|
404
|
+
const url =
|
|
405
|
+
version != null
|
|
406
|
+
? `https://apis.roblox.com/asset-delivery-api/v1/assetId/${placeId}/version/${version}`
|
|
407
|
+
: `https://apis.roblox.com/asset-delivery-api/v1/assetId/${placeId}`;
|
|
401
408
|
|
|
402
409
|
OutputHelper.verbose(
|
|
403
|
-
`Downloading base place
|
|
410
|
+
`Downloading base place${
|
|
411
|
+
version != null ? ` v${version}` : ''
|
|
412
|
+
} from https://www.roblox.com/games/${placeId}/place ...`
|
|
404
413
|
);
|
|
405
414
|
|
|
406
415
|
const response = await this._rateLimiter.fetchAsync(url, {
|
|
@@ -432,14 +441,84 @@ export class OpenCloudClient {
|
|
|
432
441
|
);
|
|
433
442
|
}
|
|
434
443
|
|
|
435
|
-
|
|
444
|
+
// The Asset Delivery API reports a missing asset/version as HTTP 200 with an
|
|
445
|
+
// `errors` array rather than an error status, so a bad `version` pin lands
|
|
446
|
+
// here instead of the block above.
|
|
447
|
+
const data = (await response.json()) as {
|
|
448
|
+
location?: string;
|
|
449
|
+
errors?: Array<{ code?: number; message?: string }>;
|
|
450
|
+
};
|
|
436
451
|
if (!data.location) {
|
|
437
|
-
|
|
452
|
+
const apiMessage = data.errors?.[0]?.message;
|
|
453
|
+
if (version != null) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`Base place ${placeId} has no version ${version}` +
|
|
456
|
+
(apiMessage ? ` (${apiMessage})` : '') +
|
|
457
|
+
`. Run "nevermore deploy version upgrade" to re-pin, or check the pinned version in deploy.nevermore.json.`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
throw new Error(
|
|
461
|
+
`Download place failed: no CDN location in response` +
|
|
462
|
+
(apiMessage ? `: ${apiMessage}` : '')
|
|
463
|
+
);
|
|
438
464
|
}
|
|
439
465
|
|
|
440
466
|
OutputHelper.verbose('Fetching place binary from CDN...');
|
|
441
467
|
return _fetchCdnBinaryAsync(data.location, onProgress);
|
|
442
468
|
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Resolve the current latest published version number of a place, via the
|
|
472
|
+
* Open Cloud Assets API (`asset:read` scope; `legacy-asset:manage` also
|
|
473
|
+
* grants it). The returned number is the same value the Asset Delivery API's
|
|
474
|
+
* `/version/{n}` route expects, so it can be written straight into a
|
|
475
|
+
* `basePlace.version` pin.
|
|
476
|
+
*/
|
|
477
|
+
async getLatestPlaceVersionAsync(
|
|
478
|
+
universeId: number,
|
|
479
|
+
placeId: number
|
|
480
|
+
): Promise<number> {
|
|
481
|
+
const apiKey = await this._resolveApiKeyAsync();
|
|
482
|
+
const url = `https://apis.roblox.com/assets/v1/assets/${placeId}`;
|
|
483
|
+
|
|
484
|
+
const response = await this._rateLimiter.fetchAsync(url, {
|
|
485
|
+
method: 'GET',
|
|
486
|
+
headers: {
|
|
487
|
+
'x-api-key': apiKey,
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
if (!response.ok) {
|
|
492
|
+
const text = await response.text();
|
|
493
|
+
if (response.status === 401 || response.status === 403) {
|
|
494
|
+
throw new Error(
|
|
495
|
+
formatAuthError(
|
|
496
|
+
'Read place version',
|
|
497
|
+
'asset:read',
|
|
498
|
+
universeId,
|
|
499
|
+
placeId,
|
|
500
|
+
response.status,
|
|
501
|
+
response.statusText,
|
|
502
|
+
text,
|
|
503
|
+
'GET',
|
|
504
|
+
url
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
throw new Error(
|
|
509
|
+
`Read place version failed: ${response.status} ${response.statusText}: ${text}`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const data = (await response.json()) as { revisionId?: string };
|
|
514
|
+
const version = Number(data.revisionId);
|
|
515
|
+
if (!data.revisionId || !Number.isFinite(version)) {
|
|
516
|
+
throw new Error(
|
|
517
|
+
`Read place version failed: no revisionId for place ${placeId}`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
return version;
|
|
521
|
+
}
|
|
443
522
|
}
|
|
444
523
|
|
|
445
524
|
const CDN_MAX_ATTEMPTS = 4;
|