@bedrock-rbx/core 0.1.0-beta.12 → 0.1.0-beta.14
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/dist/cli/run.mjs +73 -28
- package/dist/cli/run.mjs.map +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/{define-config-87u2jqjM.d.mts → define-config-C2cOtDpP.d.mts} +233 -15
- package/dist/{define-config-87u2jqjM.d.mts.map → define-config-C2cOtDpP.d.mts.map} +1 -1
- package/dist/index.d.mts +496 -307
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{migrate-mantle-state-Dkk5zGHw.mjs → migrate-mantle-state-qejWFAR0.mjs} +1886 -961
- package/dist/migrate-mantle-state-qejWFAR0.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/migrate-mantle-state-Dkk5zGHw.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ConfigError,
|
|
1
|
+
import { C as UniverseOverlayWithId, D as ConfigError, E as validateConfig, O as ConfigValidationIssue, S as UniverseEntry, T as isGistStateConfig, _ as ResolvedConfig, a as ConfigEnvironmentUniverseId, b as ResourceEntryByKind, c as DisplayNamePrefixConfig, d as GistStateConfig, f as PlaceEntry, g as RedactedPlaceOverride, h as RedactedGamePassOverride, i as Config, l as EnvironmentEntry, m as RedactedEnvironmentOverride, n as ConfigInput, o as ConfigRootUniverseId, p as RedactedDeveloperProductOverride, r as defineConfig, s as DeveloperProductEntry, t as ConfigContext, u as GamePassEntry, v as ResolvedPlaceEntry, w as UniverseOverlayWithoutId, x as StateConfig, y as ResolvedUniverseEntry } from "./define-config-C2cOtDpP.mjs";
|
|
2
2
|
import { OpenCloudError, OpenCloudError as OpenCloudError$1, Result, Result as Result$1 } from "@bedrock-rbx/ocale";
|
|
3
3
|
import { Type as Type$1 } from "arktype";
|
|
4
4
|
import { DeveloperProductsClient } from "@bedrock-rbx/ocale/developer-products";
|
|
@@ -1227,8 +1227,29 @@ interface IncompleteUniverseEntryError {
|
|
|
1227
1227
|
/** Field that the merged entry lacks. V1 only surfaces `"universeId"`. */
|
|
1228
1228
|
readonly missingField: "universeId";
|
|
1229
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Failure surfaced when a merged `passes` entry is missing a required
|
|
1232
|
+
* field. The most common path here is an overlay-only pass declared
|
|
1233
|
+
* under `environments.X.passes` with no matching root entry: the overlay
|
|
1234
|
+
* shape is `Partial<GamePassEntry>`, so a typo on the ResourceKey
|
|
1235
|
+
* silently produces an incomplete entry that would otherwise be filled
|
|
1236
|
+
* in by `applyRedaction` (when `redacted: true` is set) and pushed as a
|
|
1237
|
+
* phantom placeholder pass. Surfacing the missing field at the
|
|
1238
|
+
* resolution boundary keeps that case attributable instead of letting
|
|
1239
|
+
* normalize fail later with a less specific error.
|
|
1240
|
+
*/
|
|
1241
|
+
interface IncompletePassEntryError {
|
|
1242
|
+
/** ResourceKey of the pass entry that is missing a required field. */
|
|
1243
|
+
readonly key: string;
|
|
1244
|
+
/** Environment whose overlay was projected onto the config. */
|
|
1245
|
+
readonly environment: string;
|
|
1246
|
+
/** Literal discriminator for narrowing. */
|
|
1247
|
+
readonly kind: "incompletePassEntry";
|
|
1248
|
+
/** Field that the merged entry lacks. */
|
|
1249
|
+
readonly missingField: "description" | "icon" | "name";
|
|
1250
|
+
}
|
|
1230
1251
|
/** Failure modes returned by {@link selectEnvironment}. */
|
|
1231
|
-
type SelectEnvironmentError = IncompletePlaceEntryError | IncompleteUniverseEntryError | UnknownEnvironmentError;
|
|
1252
|
+
type SelectEnvironmentError = IncompletePassEntryError | IncompletePlaceEntryError | IncompleteUniverseEntryError | UnknownEnvironmentError;
|
|
1232
1253
|
/**
|
|
1233
1254
|
* Project a validated `Config` onto a single environment. Looks up the
|
|
1234
1255
|
* matching `environments[environment]` entry, deep-merges its resource
|
|
@@ -1308,127 +1329,6 @@ type SelectEnvironmentError = IncompletePlaceEntryError | IncompleteUniverseEntr
|
|
|
1308
1329
|
*/
|
|
1309
1330
|
declare function selectEnvironment(config: Config, environment: string): Result$1<ResolvedConfig, SelectEnvironmentError>;
|
|
1310
1331
|
//#endregion
|
|
1311
|
-
//#region src/ports/resource-driver.d.ts
|
|
1312
|
-
/**
|
|
1313
|
-
* Plugin contract for a resource adapter: the interface a third-party author
|
|
1314
|
-
* implements to teach Bedrock how to reconcile one {@link ResourceKind} against
|
|
1315
|
-
* its upstream API.
|
|
1316
|
-
*
|
|
1317
|
-
* `ResourceDriver<K>` is a *driven* (secondary) port in hexagonal terms; the
|
|
1318
|
-
* name "driver" follows Terraform, Pulumi, and Mantle IaC convention for a
|
|
1319
|
-
* component that talks to a specific resource API.
|
|
1320
|
-
*
|
|
1321
|
-
* @template K - The {@link ResourceKind} discriminator this driver handles.
|
|
1322
|
-
*
|
|
1323
|
-
* @example
|
|
1324
|
-
*
|
|
1325
|
-
* ```ts
|
|
1326
|
-
* import {
|
|
1327
|
-
* asResourceKey,
|
|
1328
|
-
* asRobloxAssetId,
|
|
1329
|
-
* asSha256Hex,
|
|
1330
|
-
* type ResourceDriver,
|
|
1331
|
-
* } from "@bedrock-rbx/core";
|
|
1332
|
-
*
|
|
1333
|
-
* const gamePassDriver: ResourceDriver<"gamePass"> = {
|
|
1334
|
-
* async create(desired) {
|
|
1335
|
-
* return {
|
|
1336
|
-
* data: {
|
|
1337
|
-
* ...desired,
|
|
1338
|
-
* outputs: {
|
|
1339
|
-
* assetId: asRobloxAssetId("9876543210"),
|
|
1340
|
-
* iconAssetIds: { "en-us": asRobloxAssetId("1122334455") },
|
|
1341
|
-
* },
|
|
1342
|
-
* },
|
|
1343
|
-
* success: true,
|
|
1344
|
-
* };
|
|
1345
|
-
* },
|
|
1346
|
-
* };
|
|
1347
|
-
*
|
|
1348
|
-
* return gamePassDriver
|
|
1349
|
-
* .create({
|
|
1350
|
-
* description: "Grants VIP perks.",
|
|
1351
|
-
* icon: { "en-us": "assets/vip-icon.png" },
|
|
1352
|
-
* iconFileHashes: {
|
|
1353
|
-
* "en-us": asSha256Hex(
|
|
1354
|
-
* "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
1355
|
-
* ),
|
|
1356
|
-
* },
|
|
1357
|
-
* key: asResourceKey("vip-pass"),
|
|
1358
|
-
* kind: "gamePass",
|
|
1359
|
-
* name: "VIP Pass",
|
|
1360
|
-
* price: undefined,
|
|
1361
|
-
* })
|
|
1362
|
-
* .then((result) => {
|
|
1363
|
-
* expect(result.success).toBeTrue();
|
|
1364
|
-
* if (result.success) {
|
|
1365
|
-
* expect(result.data.outputs.assetId).toBe("9876543210");
|
|
1366
|
-
* }
|
|
1367
|
-
* });
|
|
1368
|
-
* ```
|
|
1369
|
-
*/
|
|
1370
|
-
interface ResourceDriver<K extends ResourceKind> {
|
|
1371
|
-
/**
|
|
1372
|
-
* Create the resource upstream from its desired state and return the
|
|
1373
|
-
* resulting current state (desired fields + Roblox-assigned outputs).
|
|
1374
|
-
*/
|
|
1375
|
-
create(desired: Extract<ResourceDesiredState, {
|
|
1376
|
-
kind: K;
|
|
1377
|
-
}>): Promise<Result$1<ResourceCurrentState<K>, OpenCloudError$1>>;
|
|
1378
|
-
/**
|
|
1379
|
-
* Reconcile an upstream resource whose managed content has drifted from its
|
|
1380
|
-
* desired state. Receives the last-known current state so the driver can
|
|
1381
|
-
* compute a minimal patch (or no-op upstream, for file-backed kinds where
|
|
1382
|
-
* republishing is unconditional).
|
|
1383
|
-
*
|
|
1384
|
-
* Optional. Drivers whose upstream API has no update operation omit this
|
|
1385
|
-
* method; `applyOps` surfaces an `updateUnsupported` error at dispatch time
|
|
1386
|
-
* instead.
|
|
1387
|
-
*/
|
|
1388
|
-
update?(current: ResourceCurrentState<K>, desired: Extract<ResourceDesiredState, {
|
|
1389
|
-
kind: K;
|
|
1390
|
-
}>): Promise<Result$1<ResourceCurrentState<K>, OpenCloudError$1>>;
|
|
1391
|
-
}
|
|
1392
|
-
/**
|
|
1393
|
-
* Polymorphic dispatch table keyed by {@link ResourceKind}, mapping each kind
|
|
1394
|
-
* to the {@link ResourceDriver} that handles it. `applyOps` indexes the
|
|
1395
|
-
* registry by `op.desired.kind` to reach the matching driver with full type
|
|
1396
|
-
* safety: adding a new kind to `ResourceDesiredState` is a compile error until
|
|
1397
|
-
* a matching registry entry is supplied.
|
|
1398
|
-
*
|
|
1399
|
-
* @example
|
|
1400
|
-
*
|
|
1401
|
-
* ```ts
|
|
1402
|
-
* import { OpenCloudError, type DriverRegistry } from "@bedrock-rbx/core";
|
|
1403
|
-
*
|
|
1404
|
-
* const registry: DriverRegistry = {
|
|
1405
|
-
* gamePass: {
|
|
1406
|
-
* async create() {
|
|
1407
|
-
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1408
|
-
* },
|
|
1409
|
-
* },
|
|
1410
|
-
* place: {
|
|
1411
|
-
* async create() {
|
|
1412
|
-
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1413
|
-
* },
|
|
1414
|
-
* },
|
|
1415
|
-
* universe: {
|
|
1416
|
-
* async create() {
|
|
1417
|
-
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1418
|
-
* },
|
|
1419
|
-
* },
|
|
1420
|
-
* developerProduct: {
|
|
1421
|
-
* async create() {
|
|
1422
|
-
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1423
|
-
* },
|
|
1424
|
-
* },
|
|
1425
|
-
* };
|
|
1426
|
-
*
|
|
1427
|
-
* expect(registry.gamePass).toBeObject();
|
|
1428
|
-
* ```
|
|
1429
|
-
*/
|
|
1430
|
-
type DriverRegistry = { [K in ResourceKind]: ResourceDriver<K> };
|
|
1431
|
-
//#endregion
|
|
1432
1332
|
//#region src/core/operations.d.ts
|
|
1433
1333
|
/**
|
|
1434
1334
|
* Fields shared by every operation variant.
|
|
@@ -1540,11 +1440,13 @@ interface CreateOperation extends BaseOperation {
|
|
|
1540
1440
|
* name: "VIP Pass",
|
|
1541
1441
|
* price: 750,
|
|
1542
1442
|
* },
|
|
1443
|
+
* changedFields: ["description", "price"],
|
|
1543
1444
|
* key: asResourceKey("vip-pass"),
|
|
1544
1445
|
* type: "update",
|
|
1545
1446
|
* };
|
|
1546
1447
|
*
|
|
1547
1448
|
* expect(op.type).toBe("update");
|
|
1449
|
+
* expect(op.changedFields).toStrictEqual(["description", "price"]);
|
|
1548
1450
|
* if (op.desired.kind === "gamePass") {
|
|
1549
1451
|
* expect(op.desired.price).toBe(750);
|
|
1550
1452
|
* }
|
|
@@ -1554,6 +1456,13 @@ interface CreateOperation extends BaseOperation {
|
|
|
1554
1456
|
* ```
|
|
1555
1457
|
*/
|
|
1556
1458
|
interface UpdateOperation extends BaseOperation {
|
|
1459
|
+
/**
|
|
1460
|
+
* Top-level field names that differ between `current` and `desired`,
|
|
1461
|
+
* populated by `diff` from the kind module's `changedFieldsBetween`.
|
|
1462
|
+
* Plan and apply renderers consume this as the single source of truth
|
|
1463
|
+
* for "what changed" on this op; never empty for an `update` variant.
|
|
1464
|
+
*/
|
|
1465
|
+
readonly changedFields: ReadonlyArray<string>;
|
|
1557
1466
|
/** Last-known live state; the driver computes a patch against `desired`. */
|
|
1558
1467
|
readonly current: ResourceCurrentState;
|
|
1559
1468
|
/** Declared desired state to converge toward. */
|
|
@@ -1566,9 +1475,9 @@ interface UpdateOperation extends BaseOperation {
|
|
|
1566
1475
|
* entry matches its `current` entry exactly. The driver performs no I/O for
|
|
1567
1476
|
* this variant.
|
|
1568
1477
|
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1478
|
+
* Carries `key` and `kind` so progress renderers and adapters can describe the
|
|
1479
|
+
* unchanged resource without re-looking it up in the desired or current
|
|
1480
|
+
* snapshots passed to `diff`.
|
|
1572
1481
|
*
|
|
1573
1482
|
* @example
|
|
1574
1483
|
*
|
|
@@ -1577,14 +1486,18 @@ interface UpdateOperation extends BaseOperation {
|
|
|
1577
1486
|
*
|
|
1578
1487
|
* const op: NoopOperation = {
|
|
1579
1488
|
* key: asResourceKey("vip-pass"),
|
|
1489
|
+
* kind: "gamePass",
|
|
1580
1490
|
* type: "noop",
|
|
1581
1491
|
* };
|
|
1582
1492
|
*
|
|
1583
1493
|
* expect(op.type).toBe("noop");
|
|
1494
|
+
* expect(op.kind).toBe("gamePass");
|
|
1584
1495
|
* expect(op.key).toBe("vip-pass");
|
|
1585
1496
|
* ```
|
|
1586
1497
|
*/
|
|
1587
1498
|
interface NoopOperation extends BaseOperation {
|
|
1499
|
+
/** Resource-kind discriminator copied from the matching desired/current entry. */
|
|
1500
|
+
readonly kind: ResourceKind;
|
|
1588
1501
|
/** Discriminator tag for the `Operation` union. */
|
|
1589
1502
|
readonly type: "noop";
|
|
1590
1503
|
}
|
|
@@ -1617,6 +1530,7 @@ interface NoopOperation extends BaseOperation {
|
|
|
1617
1530
|
*
|
|
1618
1531
|
* const op: Operation = {
|
|
1619
1532
|
* key: asResourceKey("vip-pass"),
|
|
1533
|
+
* kind: "gamePass",
|
|
1620
1534
|
* type: "noop",
|
|
1621
1535
|
* };
|
|
1622
1536
|
*
|
|
@@ -1625,15 +1539,145 @@ interface NoopOperation extends BaseOperation {
|
|
|
1625
1539
|
*/
|
|
1626
1540
|
type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
1627
1541
|
//#endregion
|
|
1542
|
+
//#region src/ports/resource-driver.d.ts
|
|
1543
|
+
/**
|
|
1544
|
+
* Plugin contract for a resource adapter: the interface a third-party author
|
|
1545
|
+
* implements to teach Bedrock how to reconcile one {@link ResourceKind} against
|
|
1546
|
+
* its upstream API.
|
|
1547
|
+
*
|
|
1548
|
+
* `ResourceDriver<K>` is a *driven* (secondary) port in hexagonal terms; the
|
|
1549
|
+
* name "driver" follows Terraform, Pulumi, and Mantle IaC convention for a
|
|
1550
|
+
* component that talks to a specific resource API.
|
|
1551
|
+
*
|
|
1552
|
+
* @template K - The {@link ResourceKind} discriminator this driver handles.
|
|
1553
|
+
*
|
|
1554
|
+
* @example
|
|
1555
|
+
*
|
|
1556
|
+
* ```ts
|
|
1557
|
+
* import {
|
|
1558
|
+
* asResourceKey,
|
|
1559
|
+
* asRobloxAssetId,
|
|
1560
|
+
* asSha256Hex,
|
|
1561
|
+
* type ResourceDriver,
|
|
1562
|
+
* } from "@bedrock-rbx/core";
|
|
1563
|
+
*
|
|
1564
|
+
* const gamePassDriver: ResourceDriver<"gamePass"> = {
|
|
1565
|
+
* async create(desired) {
|
|
1566
|
+
* return {
|
|
1567
|
+
* data: {
|
|
1568
|
+
* ...desired,
|
|
1569
|
+
* outputs: {
|
|
1570
|
+
* assetId: asRobloxAssetId("9876543210"),
|
|
1571
|
+
* iconAssetIds: { "en-us": asRobloxAssetId("1122334455") },
|
|
1572
|
+
* },
|
|
1573
|
+
* },
|
|
1574
|
+
* success: true,
|
|
1575
|
+
* };
|
|
1576
|
+
* },
|
|
1577
|
+
* };
|
|
1578
|
+
*
|
|
1579
|
+
* return gamePassDriver
|
|
1580
|
+
* .create({
|
|
1581
|
+
* description: "Grants VIP perks.",
|
|
1582
|
+
* icon: { "en-us": "assets/vip-icon.png" },
|
|
1583
|
+
* iconFileHashes: {
|
|
1584
|
+
* "en-us": asSha256Hex(
|
|
1585
|
+
* "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
1586
|
+
* ),
|
|
1587
|
+
* },
|
|
1588
|
+
* key: asResourceKey("vip-pass"),
|
|
1589
|
+
* kind: "gamePass",
|
|
1590
|
+
* name: "VIP Pass",
|
|
1591
|
+
* price: undefined,
|
|
1592
|
+
* })
|
|
1593
|
+
* .then((result) => {
|
|
1594
|
+
* expect(result.success).toBeTrue();
|
|
1595
|
+
* if (result.success) {
|
|
1596
|
+
* expect(result.data.outputs.assetId).toBe("9876543210");
|
|
1597
|
+
* }
|
|
1598
|
+
* });
|
|
1599
|
+
* ```
|
|
1600
|
+
*/
|
|
1601
|
+
interface ResourceDriver<K extends ResourceKind> {
|
|
1602
|
+
/**
|
|
1603
|
+
* Create the resource upstream from its desired state and return the
|
|
1604
|
+
* resulting current state (desired fields + Roblox-assigned outputs).
|
|
1605
|
+
*/
|
|
1606
|
+
create(desired: Extract<ResourceDesiredState, {
|
|
1607
|
+
kind: K;
|
|
1608
|
+
}>): Promise<Result$1<ResourceCurrentState<K>, OpenCloudError$1>>;
|
|
1609
|
+
/**
|
|
1610
|
+
* Reconcile an upstream resource whose managed content has drifted from its
|
|
1611
|
+
* desired state. Receives the last-known current state so the driver can
|
|
1612
|
+
* compute a minimal patch (or no-op upstream, for file-backed kinds where
|
|
1613
|
+
* republishing is unconditional).
|
|
1614
|
+
*
|
|
1615
|
+
* Optional. Drivers whose upstream API has no update operation omit this
|
|
1616
|
+
* method; `applyOps` surfaces an `updateUnsupported` error at dispatch time
|
|
1617
|
+
* instead.
|
|
1618
|
+
*/
|
|
1619
|
+
update?(current: ResourceCurrentState<K>, desired: Extract<ResourceDesiredState, {
|
|
1620
|
+
kind: K;
|
|
1621
|
+
}>): Promise<Result$1<ResourceCurrentState<K>, OpenCloudError$1>>;
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Polymorphic dispatch table keyed by {@link ResourceKind}, mapping each kind
|
|
1625
|
+
* to the {@link ResourceDriver} that handles it. `applyOps` indexes the
|
|
1626
|
+
* registry by `op.desired.kind` to reach the matching driver with full type
|
|
1627
|
+
* safety: adding a new kind to `ResourceDesiredState` is a compile error until
|
|
1628
|
+
* a matching registry entry is supplied.
|
|
1629
|
+
*
|
|
1630
|
+
* @example
|
|
1631
|
+
*
|
|
1632
|
+
* ```ts
|
|
1633
|
+
* import { OpenCloudError, type DriverRegistry } from "@bedrock-rbx/core";
|
|
1634
|
+
*
|
|
1635
|
+
* const registry: DriverRegistry = {
|
|
1636
|
+
* gamePass: {
|
|
1637
|
+
* async create() {
|
|
1638
|
+
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1639
|
+
* },
|
|
1640
|
+
* },
|
|
1641
|
+
* place: {
|
|
1642
|
+
* async create() {
|
|
1643
|
+
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1644
|
+
* },
|
|
1645
|
+
* },
|
|
1646
|
+
* universe: {
|
|
1647
|
+
* async create() {
|
|
1648
|
+
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1649
|
+
* },
|
|
1650
|
+
* },
|
|
1651
|
+
* developerProduct: {
|
|
1652
|
+
* async create() {
|
|
1653
|
+
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1654
|
+
* },
|
|
1655
|
+
* },
|
|
1656
|
+
* };
|
|
1657
|
+
*
|
|
1658
|
+
* expect(registry.gamePass).toBeObject();
|
|
1659
|
+
* ```
|
|
1660
|
+
*/
|
|
1661
|
+
type DriverRegistry = { [K in ResourceKind]: ResourceDriver<K> };
|
|
1662
|
+
//#endregion
|
|
1628
1663
|
//#region src/shell/apply-ops.d.ts
|
|
1629
1664
|
/**
|
|
1665
|
+
* Optional wiring `applyOps` uses to emit per-resource and aggregate progress
|
|
1666
|
+
* events. When omitted, `applyOps` runs silently (backward-compatible with
|
|
1667
|
+
* pre-progress callers).
|
|
1668
|
+
*/
|
|
1669
|
+
interface ApplyOpsReporting {
|
|
1670
|
+
/** Environment name stamped on every emitted event. */
|
|
1671
|
+
readonly environment: string;
|
|
1672
|
+
/** Sink the apply pipeline pushes events into. */
|
|
1673
|
+
readonly progress: ProgressPort;
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1630
1676
|
* Failure surfaced by `applyOps` when an operation cannot be applied.
|
|
1631
1677
|
* Plain-data discriminated union; narrow on `kind`, do not `instanceof` it.
|
|
1632
|
-
*
|
|
1633
|
-
* `
|
|
1634
|
-
*
|
|
1635
|
-
* follow-up reconcile does not duplicate Roblox-side resources that have
|
|
1636
|
-
* already been created or updated.
|
|
1678
|
+
* One `ApplyError` describes one failing op; the surrounding
|
|
1679
|
+
* `AggregateApplyError` carries the full batch outcome (every survivor and
|
|
1680
|
+
* every failure).
|
|
1637
1681
|
*
|
|
1638
1682
|
* @example
|
|
1639
1683
|
*
|
|
@@ -1645,6 +1689,9 @@ type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
|
1645
1689
|
* case "driverFailure": {
|
|
1646
1690
|
* return `driver failed for ${err.key}: ${err.cause.message}`;
|
|
1647
1691
|
* }
|
|
1692
|
+
* case "unexpectedThrow": {
|
|
1693
|
+
* return `unexpected error for ${err.key}`;
|
|
1694
|
+
* }
|
|
1648
1695
|
* case "updateUnsupported": {
|
|
1649
1696
|
* return `update not supported for ${err.key}`;
|
|
1650
1697
|
* }
|
|
@@ -1653,7 +1700,6 @@ type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
|
1653
1700
|
*
|
|
1654
1701
|
* const err: ApplyError = {
|
|
1655
1702
|
* key: asResourceKey("vip-pass"),
|
|
1656
|
-
* appliedSoFar: [],
|
|
1657
1703
|
* kind: "updateUnsupported",
|
|
1658
1704
|
* };
|
|
1659
1705
|
*
|
|
@@ -1661,126 +1707,290 @@ type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
|
1661
1707
|
* ```
|
|
1662
1708
|
*/
|
|
1663
1709
|
type ApplyError = {
|
|
1664
|
-
readonly appliedSoFar: ReadonlyArray<ResourceCurrentState>;
|
|
1665
1710
|
readonly cause: OpenCloudError$1;
|
|
1666
1711
|
readonly key: ResourceKey;
|
|
1667
1712
|
readonly kind: "driverFailure";
|
|
1668
1713
|
} | {
|
|
1669
|
-
readonly
|
|
1714
|
+
readonly cause: unknown;
|
|
1715
|
+
readonly key: ResourceKey;
|
|
1716
|
+
readonly kind: "unexpectedThrow";
|
|
1717
|
+
} | {
|
|
1670
1718
|
readonly key: ResourceKey;
|
|
1671
1719
|
readonly kind: "updateUnsupported";
|
|
1672
1720
|
};
|
|
1673
1721
|
/**
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
1676
|
-
* `
|
|
1677
|
-
*
|
|
1722
|
+
* Aggregate outcome returned by `applyOps` when one or more ops fail.
|
|
1723
|
+
* `applied` is the survivor set in Phase 1 then Phase 2 input order.
|
|
1724
|
+
* `failures` is the non-empty list of `ApplyError`s, one per failing op,
|
|
1725
|
+
* grouped the same way.
|
|
1726
|
+
*
|
|
1727
|
+
* @example
|
|
1728
|
+
*
|
|
1729
|
+
* ```ts
|
|
1730
|
+
* import { asResourceKey, type AggregateApplyError } from "@bedrock-rbx/core";
|
|
1731
|
+
*
|
|
1732
|
+
* function summarize(err: AggregateApplyError): string {
|
|
1733
|
+
* return `${err.applied.length} survived, ${err.failures.length} failed`;
|
|
1734
|
+
* }
|
|
1735
|
+
*
|
|
1736
|
+
* const err: AggregateApplyError = {
|
|
1737
|
+
* applied: [],
|
|
1738
|
+
* failures: [{ key: asResourceKey("vip-pass"), kind: "updateUnsupported" }],
|
|
1739
|
+
* };
|
|
1740
|
+
*
|
|
1741
|
+
* expect(summarize(err)).toBe("0 survived, 1 failed");
|
|
1742
|
+
* ```
|
|
1743
|
+
*/
|
|
1744
|
+
interface AggregateApplyError {
|
|
1745
|
+
/** Survivors persisted to state, in Phase 1 then Phase 2 input order. */
|
|
1746
|
+
readonly applied: ReadonlyArray<ResourceCurrentState>;
|
|
1747
|
+
/** Per-op failures, at least one, in Phase 1 then Phase 2 input order. */
|
|
1748
|
+
readonly failures: readonly [ApplyError, ...ReadonlyArray<ApplyError>];
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Dispatch reconciliation operations to their matching drivers in two phases
|
|
1752
|
+
* with continue-on-failure semantics. Phase 1 runs universe ops sequentially
|
|
1753
|
+
* (singleton per environment; sequencing it before everything else avoids the
|
|
1754
|
+
* `displayName` race against the root `Place`). Phase 2 dispatches every
|
|
1755
|
+
* remaining non-noop op concurrently via `Promise.all`; every op is
|
|
1756
|
+
* attempted regardless of earlier failures.
|
|
1678
1757
|
*
|
|
1679
1758
|
* Behaviour:
|
|
1680
|
-
* - `create` operations
|
|
1681
|
-
* - `update` operations
|
|
1682
|
-
*
|
|
1683
|
-
* `
|
|
1759
|
+
* - `create` operations route to `registry[op.desired.kind].create`.
|
|
1760
|
+
* - `update` operations route to `registry[op.desired.kind].update` when the
|
|
1761
|
+
* driver exposes it; otherwise they yield an `updateUnsupported`
|
|
1762
|
+
* `ApplyError` without invoking the driver.
|
|
1684
1763
|
* - `noop` operations are skipped entirely (no I/O, no dispatch).
|
|
1764
|
+
* - A driver that throws outside its `Result` contract is caught at the
|
|
1765
|
+
* dispatch boundary and translated to an `unexpectedThrow` `ApplyError`
|
|
1766
|
+
* scoped to that op alone; the rest of the batch keeps running.
|
|
1767
|
+
*
|
|
1768
|
+
* On Ok the returned array carries driver outputs for every non-noop op
|
|
1769
|
+
* in phase order: Phase 1 universe entries first, then Phase 2 entries in
|
|
1770
|
+
* their input order. Noops are not represented; callers needing a full
|
|
1771
|
+
* post-apply snapshot merge with the pre-apply current state keyed by
|
|
1772
|
+
* `ResourceKey`.
|
|
1773
|
+
*
|
|
1774
|
+
* On Err the aggregate carries every survivor in `applied` (Phase 1 first,
|
|
1775
|
+
* then Phase 2 input order) and every failure in `failures` with the same
|
|
1776
|
+
* grouping. Neither array reflects completion order.
|
|
1777
|
+
*
|
|
1778
|
+
* @param ops - Reconciliation operations produced by `diff`, applied in
|
|
1779
|
+
* declaration order.
|
|
1780
|
+
* @param registry - Per-kind driver table; dispatch uses `op.desired.kind`
|
|
1781
|
+
* as the index.
|
|
1782
|
+
* @param reporting - Optional progress wiring. When supplied, `applyOps`
|
|
1783
|
+
* emits one `resourceOpStarted` and one terminal event per non-noop op,
|
|
1784
|
+
* one `resourceOpNoop` per noop op, and a final `applySummary` carrying
|
|
1785
|
+
* the per-type counts and the wall-clock apply duration. When omitted,
|
|
1786
|
+
* no events fire.
|
|
1787
|
+
* @returns `Ok(state)` when every op succeeded; otherwise
|
|
1788
|
+
* `Err(AggregateApplyError)` with the survivors and the non-empty
|
|
1789
|
+
* failures tuple.
|
|
1790
|
+
* @example
|
|
1791
|
+
*
|
|
1792
|
+
* ```ts
|
|
1793
|
+
* import { applyOps, type DriverRegistry } from "@bedrock-rbx/core";
|
|
1794
|
+
*
|
|
1795
|
+
* const noopRegistry: DriverRegistry = {
|
|
1796
|
+
* developerProduct: { create: async () => ({ err: new Error("stub") as never, success: false }) },
|
|
1797
|
+
* gamePass: { create: async () => ({ err: new Error("stub") as never, success: false }) },
|
|
1798
|
+
* place: { create: async () => ({ err: new Error("stub") as never, success: false }) },
|
|
1799
|
+
* universe: { create: async () => ({ err: new Error("stub") as never, success: false }) },
|
|
1800
|
+
* };
|
|
1801
|
+
*
|
|
1802
|
+
* return applyOps([], noopRegistry).then((result) => {
|
|
1803
|
+
* expect(result).toStrictEqual({ data: [], success: true });
|
|
1804
|
+
* });
|
|
1805
|
+
* ```
|
|
1806
|
+
*/
|
|
1807
|
+
declare function applyOps(ops: ReadonlyArray<Operation>, registry: DriverRegistry, reporting?: ApplyOpsReporting): Promise<Result$1<ReadonlyArray<ResourceCurrentState>, AggregateApplyError>>;
|
|
1808
|
+
//#endregion
|
|
1809
|
+
//#region src/ports/progress-port.d.ts
|
|
1810
|
+
/**
|
|
1811
|
+
* Per-environment outcome event emitted after a deploy completes
|
|
1812
|
+
* successfully. Carries the environment name and the count of resources
|
|
1813
|
+
* present in the persisted state snapshot.
|
|
1814
|
+
*/
|
|
1815
|
+
interface DeploySuccessEvent {
|
|
1816
|
+
/** The environment that finished reconciling. */
|
|
1817
|
+
readonly environment: string;
|
|
1818
|
+
/** Discriminator tag. */
|
|
1819
|
+
readonly kind: "deploySuccess";
|
|
1820
|
+
/** Number of resources in the post-deploy state snapshot. */
|
|
1821
|
+
readonly resourceCount: number;
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Per-environment outcome event emitted when a deploy fails. Carries the
|
|
1825
|
+
* environment name and the full {@link DeployError} so a renderer can
|
|
1826
|
+
* delegate to the existing diagnostic helpers.
|
|
1827
|
+
*/
|
|
1828
|
+
interface DeployFailureEvent {
|
|
1829
|
+
/** The environment whose deploy failed. */
|
|
1830
|
+
readonly environment: string;
|
|
1831
|
+
/** Stage-tagged failure reason returned by the shell `deploy` function. */
|
|
1832
|
+
readonly error: DeployError;
|
|
1833
|
+
/** Discriminator tag. */
|
|
1834
|
+
readonly kind: "deployFailure";
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Per-resource event emitted immediately before `applyOps` dispatches a
|
|
1838
|
+
* non-noop op to its driver. Adapters may render a "starting" line or
|
|
1839
|
+
* stay silent; the matching terminal event ({@link ResourceOpSucceededEvent}
|
|
1840
|
+
* or {@link ResourceOpFailedEvent}) fires when the driver settles.
|
|
1841
|
+
*/
|
|
1842
|
+
interface ResourceOpStartedEvent {
|
|
1843
|
+
/** User-supplied resource key. */
|
|
1844
|
+
readonly key: ResourceKey;
|
|
1845
|
+
/** Environment whose reconcile is running. */
|
|
1846
|
+
readonly environment: string;
|
|
1847
|
+
/** Discriminator tag. */
|
|
1848
|
+
readonly kind: "resourceOpStarted";
|
|
1849
|
+
/** Operation type being dispatched. Noops never fire this event. */
|
|
1850
|
+
readonly opType: "create" | "update";
|
|
1851
|
+
/** Resource-kind discriminator (`gamePass`, `place`, ...). */
|
|
1852
|
+
readonly resourceKind: ResourceKind;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Terminal event for a successful create op. The `resourceKind` discriminator
|
|
1856
|
+
* narrows `outputs` to the matching `ResourceOutputs<K>` shape so renderers
|
|
1857
|
+
* can read Roblox-assigned IDs without casts.
|
|
1858
|
+
*/
|
|
1859
|
+
type ResourceOpSucceededCreateEvent = { [K in ResourceKind]: Readonly<{
|
|
1860
|
+
environment: string;
|
|
1861
|
+
key: ResourceKey;
|
|
1862
|
+
kind: "resourceOpSucceeded";
|
|
1863
|
+
opType: "create";
|
|
1864
|
+
outputs: ResourceOutputs<K>;
|
|
1865
|
+
resourceKind: K;
|
|
1866
|
+
}> }[ResourceKind];
|
|
1867
|
+
/**
|
|
1868
|
+
* Terminal event for a successful update op. Carries the list of top-level
|
|
1869
|
+
* fields the diff flagged as changed so renderers can attribute the update.
|
|
1870
|
+
*/
|
|
1871
|
+
interface ResourceOpSucceededUpdateEvent {
|
|
1872
|
+
/** User-supplied resource key. */
|
|
1873
|
+
readonly key: ResourceKey;
|
|
1874
|
+
/** Top-level field names whose values differed between desired and current. */
|
|
1875
|
+
readonly changedFields: ReadonlyArray<string>;
|
|
1876
|
+
/** Environment whose reconcile is running. */
|
|
1877
|
+
readonly environment: string;
|
|
1878
|
+
/** Discriminator tag. */
|
|
1879
|
+
readonly kind: "resourceOpSucceeded";
|
|
1880
|
+
/** Operation type. */
|
|
1881
|
+
readonly opType: "update";
|
|
1882
|
+
/** Resource-kind discriminator. */
|
|
1883
|
+
readonly resourceKind: ResourceKind;
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Terminal event for a successful non-noop op. Sub-discriminated by `opType`
|
|
1887
|
+
* so a renderer can extract `outputs` (creates) or `changedFields` (updates)
|
|
1888
|
+
* without losing type narrowing.
|
|
1889
|
+
*/
|
|
1890
|
+
type ResourceOpSucceededEvent = ResourceOpSucceededCreateEvent | ResourceOpSucceededUpdateEvent;
|
|
1891
|
+
/**
|
|
1892
|
+
* Per-resource event emitted for each op the diff produced as a noop.
|
|
1893
|
+
* Noops never fire a `started`/terminal pair; this single event stands in
|
|
1894
|
+
* for the entire op so adapters can render a "unchanged" line.
|
|
1895
|
+
*/
|
|
1896
|
+
interface ResourceOpNoopEvent {
|
|
1897
|
+
/** User-supplied resource key. */
|
|
1898
|
+
readonly key: ResourceKey;
|
|
1899
|
+
/** Environment whose reconcile is running. */
|
|
1900
|
+
readonly environment: string;
|
|
1901
|
+
/** Discriminator tag. */
|
|
1902
|
+
readonly kind: "resourceOpNoop";
|
|
1903
|
+
/** Resource-kind discriminator. */
|
|
1904
|
+
readonly resourceKind: ResourceKind;
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Terminal event for a failed non-noop op. Carries the {@link ApplyError}
|
|
1908
|
+
* so a renderer can delegate to the existing apply-cause diagnostic helper.
|
|
1909
|
+
*/
|
|
1910
|
+
interface ResourceOpFailedEvent {
|
|
1911
|
+
/** User-supplied resource key. */
|
|
1912
|
+
readonly key: ResourceKey;
|
|
1913
|
+
/** Environment whose reconcile is running. */
|
|
1914
|
+
readonly environment: string;
|
|
1915
|
+
/** Apply error returned by `dispatchOp`. */
|
|
1916
|
+
readonly error: ApplyError;
|
|
1917
|
+
/** Discriminator tag. */
|
|
1918
|
+
readonly kind: "resourceOpFailed";
|
|
1919
|
+
/** Operation type that was being attempted. */
|
|
1920
|
+
readonly opType: "create" | "update";
|
|
1921
|
+
/** Resource-kind discriminator. */
|
|
1922
|
+
readonly resourceKind: ResourceKind;
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Aggregate footer event emitted after `applyOps` finishes (Phase 2 settled).
|
|
1926
|
+
* Fires unconditionally, including on partial failure; `durationMs` measures
|
|
1927
|
+
* apply time only (state-write time excluded).
|
|
1928
|
+
*/
|
|
1929
|
+
interface ApplySummaryEvent {
|
|
1930
|
+
/** Count of successful create ops. */
|
|
1931
|
+
readonly created: number;
|
|
1932
|
+
/** Wall-clock duration between `applyOps` entry and Phase 2 resolution, in milliseconds. */
|
|
1933
|
+
readonly durationMs: number;
|
|
1934
|
+
/** Environment whose reconcile is running. */
|
|
1935
|
+
readonly environment: string;
|
|
1936
|
+
/** Count of failed ops (any opType). */
|
|
1937
|
+
readonly failed: number;
|
|
1938
|
+
/** Discriminator tag. */
|
|
1939
|
+
readonly kind: "applySummary";
|
|
1940
|
+
/** Count of noop ops. */
|
|
1941
|
+
readonly noop: number;
|
|
1942
|
+
/** Count of successful update ops. */
|
|
1943
|
+
readonly updated: number;
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Per-environment event emitted after `statePort.write` returns `Ok`.
|
|
1947
|
+
* Not emitted on write failure: the existing `deployFailure` event with
|
|
1948
|
+
* `kind: "stateWriteFailed"` runs the existing failure flow. The payload
|
|
1949
|
+
* carries no backend identity; renderers read the backend label from the
|
|
1950
|
+
* project config when constructing the rendered line.
|
|
1951
|
+
*/
|
|
1952
|
+
interface StateWrittenEvent {
|
|
1953
|
+
/** Environment whose state snapshot was just persisted. */
|
|
1954
|
+
readonly environment: string;
|
|
1955
|
+
/** Discriminator tag. */
|
|
1956
|
+
readonly kind: "stateWritten";
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Discriminated union of progress events the CLI emits while a deploy
|
|
1960
|
+
* runs. The variant set is additive: future per-stage and per-resource
|
|
1961
|
+
* events land as new `kind` values without breaking existing adapters.
|
|
1962
|
+
*/
|
|
1963
|
+
type ProgressEvent = ApplySummaryEvent | DeployFailureEvent | DeploySuccessEvent | ResourceOpFailedEvent | ResourceOpNoopEvent | ResourceOpStartedEvent | ResourceOpSucceededEvent | StateWrittenEvent;
|
|
1964
|
+
/**
|
|
1965
|
+
* Plugin contract for receiving deploy outcomes: the interface an adapter
|
|
1966
|
+
* (clack renderer, JSON logger, custom UI) implements to let the CLI hand
|
|
1967
|
+
* off events without re-implementing rendering logic.
|
|
1685
1968
|
*
|
|
1686
|
-
*
|
|
1687
|
-
* non-noop op, in dispatched order. Noops are not represented; callers
|
|
1688
|
-
* needing a full post-apply snapshot merge with the pre-apply current
|
|
1689
|
-
* state keyed by `ResourceKey`.
|
|
1969
|
+
* `ProgressPort` is a *driven* (secondary) port in hexagonal terms.
|
|
1690
1970
|
*
|
|
1691
|
-
* @param ops - Reconciliation operations produced by `diff`, applied in order.
|
|
1692
|
-
* @param registry - Per-kind driver table; dispatch uses `op.desired.kind` as the index.
|
|
1693
|
-
* @returns `Ok(state)` when every operation succeeds, where `state` holds
|
|
1694
|
-
* driver outputs for each non-noop op in dispatched order; or the first
|
|
1695
|
-
* failure encountered.
|
|
1696
|
-
* @throws Whatever the dispatched driver rejects with outside its `Result`
|
|
1697
|
-
* return. A driver whose injected I/O (file reads, network calls, etc.)
|
|
1698
|
-
* throws will surface that rejection here rather than translating it into
|
|
1699
|
-
* a `Result` failure; wrap the call site in a try/catch when drivers are
|
|
1700
|
-
* not trusted to contain their own rejections.
|
|
1701
1971
|
* @example
|
|
1702
1972
|
*
|
|
1703
1973
|
* ```ts
|
|
1704
|
-
* import {
|
|
1705
|
-
* applyOps,
|
|
1706
|
-
* asResourceKey,
|
|
1707
|
-
* asRobloxAssetId,
|
|
1708
|
-
* asSha256Hex,
|
|
1709
|
-
* type DriverRegistry,
|
|
1710
|
-
* type Operation,
|
|
1711
|
-
* } from "@bedrock-rbx/core";
|
|
1974
|
+
* import type { ProgressEvent, ProgressPort } from "@bedrock-rbx/core";
|
|
1712
1975
|
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
1715
|
-
*
|
|
1716
|
-
*
|
|
1717
|
-
* data: {
|
|
1718
|
-
* ...desired,
|
|
1719
|
-
* outputs: {
|
|
1720
|
-
* assetId: asRobloxAssetId("9876543210"),
|
|
1721
|
-
* iconAssetIds: { "en-us": asRobloxAssetId("1122334455") },
|
|
1722
|
-
* },
|
|
1723
|
-
* },
|
|
1724
|
-
* success: true,
|
|
1725
|
-
* };
|
|
1726
|
-
* },
|
|
1727
|
-
* },
|
|
1728
|
-
* place: {
|
|
1729
|
-
* async create(desired) {
|
|
1730
|
-
* return {
|
|
1731
|
-
* data: { ...desired, outputs: { versionNumber: 1 } },
|
|
1732
|
-
* success: true,
|
|
1733
|
-
* };
|
|
1734
|
-
* },
|
|
1735
|
-
* },
|
|
1736
|
-
* universe: {
|
|
1737
|
-
* async create(desired) {
|
|
1738
|
-
* return {
|
|
1739
|
-
* data: { ...desired, outputs: { rootPlaceId: asRobloxAssetId("4711") } },
|
|
1740
|
-
* success: true,
|
|
1741
|
-
* };
|
|
1742
|
-
* },
|
|
1743
|
-
* },
|
|
1744
|
-
* developerProduct: {
|
|
1745
|
-
* async create(desired) {
|
|
1746
|
-
* return {
|
|
1747
|
-
* data: {
|
|
1748
|
-
* ...desired,
|
|
1749
|
-
* outputs: { productId: asRobloxAssetId("8172635495") },
|
|
1750
|
-
* },
|
|
1751
|
-
* success: true,
|
|
1752
|
-
* };
|
|
1753
|
-
* },
|
|
1976
|
+
* let received: ReadonlyArray<ProgressEvent> = [];
|
|
1977
|
+
* const port: ProgressPort = {
|
|
1978
|
+
* emit(event) {
|
|
1979
|
+
* received = [...received, event];
|
|
1754
1980
|
* },
|
|
1755
1981
|
* };
|
|
1756
1982
|
*
|
|
1757
|
-
*
|
|
1758
|
-
* {
|
|
1759
|
-
* key: asResourceKey("vip-pass"),
|
|
1760
|
-
* type: "create",
|
|
1761
|
-
* desired: {
|
|
1762
|
-
* key: asResourceKey("vip-pass"),
|
|
1763
|
-
* name: "VIP Pass",
|
|
1764
|
-
* description: "Grants VIP perks.",
|
|
1765
|
-
* icon: { "en-us": "assets/vip-icon.png" },
|
|
1766
|
-
* iconFileHashes: {
|
|
1767
|
-
* "en-us": asSha256Hex(
|
|
1768
|
-
* "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
1769
|
-
* ),
|
|
1770
|
-
* },
|
|
1771
|
-
* kind: "gamePass",
|
|
1772
|
-
* price: 500,
|
|
1773
|
-
* },
|
|
1774
|
-
* },
|
|
1775
|
-
* ];
|
|
1983
|
+
* port.emit({ environment: "production", kind: "deploySuccess", resourceCount: 3 });
|
|
1776
1984
|
*
|
|
1777
|
-
*
|
|
1778
|
-
*
|
|
1779
|
-
*
|
|
1780
|
-
* });
|
|
1985
|
+
* expect(received).toEqual([
|
|
1986
|
+
* { environment: "production", kind: "deploySuccess", resourceCount: 3 },
|
|
1987
|
+
* ]);
|
|
1781
1988
|
* ```
|
|
1782
1989
|
*/
|
|
1783
|
-
|
|
1990
|
+
interface ProgressPort {
|
|
1991
|
+
/** Hand a single progress event to the adapter for rendering or logging. */
|
|
1992
|
+
emit(event: ProgressEvent): void;
|
|
1993
|
+
}
|
|
1784
1994
|
//#endregion
|
|
1785
1995
|
//#region src/shell/build-default-registry.d.ts
|
|
1786
1996
|
/**
|
|
@@ -2065,16 +2275,21 @@ declare function flattenConfig(config: ResolvedConfig): ReadonlyArray<ResourceDe
|
|
|
2065
2275
|
//#endregion
|
|
2066
2276
|
//#region src/core/kinds/module.d.ts
|
|
2067
2277
|
/**
|
|
2068
|
-
* Failure surfaced during desired-state preparation.
|
|
2278
|
+
* Failure surfaced during desired-state preparation. Three variants today:
|
|
2069
2279
|
*
|
|
2070
2280
|
* - `fileReadFailed`: a kind module's `normalize` could not read a file
|
|
2071
2281
|
* the input declared (e.g. An icon path that is missing on disk).
|
|
2072
2282
|
* - `iconRemovalRejected`: `validatePlan` saw a kind whose prior current
|
|
2073
2283
|
* state recorded an icon that the desired state no longer declares,
|
|
2074
2284
|
* and the kind has no documented unset path on the upstream API.
|
|
2285
|
+
* - `redactedNameCollision`: `validatePlan` saw two developer-products in
|
|
2286
|
+
* the same plan that resolve to the same wire `name`. The upstream
|
|
2287
|
+
* Roblox API enforces per-universe uniqueness on developer-product
|
|
2288
|
+
* names and would reject the second PATCH with `DuplicateProductName`.
|
|
2075
2289
|
*
|
|
2076
|
-
*
|
|
2077
|
-
* failure to
|
|
2290
|
+
* The single-resource variants carry the offending `key` so the CLI can
|
|
2291
|
+
* attribute the failure to one entry; `redactedNameCollision` carries
|
|
2292
|
+
* both colliding keys and the resolved name.
|
|
2078
2293
|
*
|
|
2079
2294
|
* @example
|
|
2080
2295
|
*
|
|
@@ -2100,6 +2315,11 @@ type BuildDesiredError = {
|
|
|
2100
2315
|
/** ResourceKey of the entry whose icon is being removed. */readonly key: ResourceKey; /** Literal discriminator for narrowing. */
|
|
2101
2316
|
readonly kind: "iconRemovalRejected"; /** Human-readable explanation naming the resource and the invariant. */
|
|
2102
2317
|
readonly message: string;
|
|
2318
|
+
} | {
|
|
2319
|
+
/** The two developer-product keys whose desired `name` resolves to the same string. */readonly keys: readonly [ResourceKey, ResourceKey]; /** Literal discriminator for narrowing. */
|
|
2320
|
+
readonly kind: "redactedNameCollision"; /** Human-readable explanation naming both keys and the override remedy. */
|
|
2321
|
+
readonly message: string; /** The wire `name` value both products resolve to. */
|
|
2322
|
+
readonly resolvedName: string;
|
|
2103
2323
|
};
|
|
2104
2324
|
/**
|
|
2105
2325
|
* I/O surface the shell injects into kind-module `normalize` calls. Carries
|
|
@@ -2163,6 +2383,8 @@ interface KindIo {
|
|
|
2163
2383
|
* },
|
|
2164
2384
|
* success: true,
|
|
2165
2385
|
* }),
|
|
2386
|
+
* changedFieldsBetween: (desired, current) =>
|
|
2387
|
+
* desired.name === current.name ? [] : ["name"],
|
|
2166
2388
|
* fieldsEqual: (desired, current) => desired.name === current.name,
|
|
2167
2389
|
* };
|
|
2168
2390
|
*
|
|
@@ -2179,6 +2401,15 @@ interface ResourceKindModule<K extends ResourceKind> {
|
|
|
2179
2401
|
* invariants omit this hook.
|
|
2180
2402
|
*/
|
|
2181
2403
|
readonly assertReconcilable?: (current: ResourceCurrentState<K>, desired: DesiredFor<K>) => Result$1<undefined, BuildDesiredError>;
|
|
2404
|
+
/**
|
|
2405
|
+
* Top-level field names that differ between `desired` and `current`,
|
|
2406
|
+
* in a kind-specific deterministic order. Mirrors `fieldsEqual`
|
|
2407
|
+
* semantics, so `fieldsEqual(d, c)` is `true` iff this returns `[]`.
|
|
2408
|
+
* Granularity is top-level: `discordSocialLink`, not
|
|
2409
|
+
* `discordSocialLink.uri`. Plan and apply renderers consume this as
|
|
2410
|
+
* the single source of truth for "what changed" on an update op.
|
|
2411
|
+
*/
|
|
2412
|
+
changedFieldsBetween(desired: DesiredFor<K>, current: ResourceCurrentState<K>): ReadonlyArray<string>;
|
|
2182
2413
|
/** ArkType schema for the authored entry body of this kind. */
|
|
2183
2414
|
readonly entrySchema: Type$1<ResourceEntryByKind[K]>;
|
|
2184
2415
|
/**
|
|
@@ -2362,6 +2593,13 @@ interface DeployOptions {
|
|
|
2362
2593
|
readonly getEnv?: (name: string) => string | undefined;
|
|
2363
2594
|
/** Loader invoked when `config` is omitted; defaults to `loadConfig` from this package. */
|
|
2364
2595
|
readonly loadConfig?: (options?: LoadConfigOptions) => Promise<Result$1<Config, ConfigError>>;
|
|
2596
|
+
/**
|
|
2597
|
+
* Optional sink for per-resource and aggregate progress events. When
|
|
2598
|
+
* supplied, `applyOps` emits one started/terminal pair per non-noop op
|
|
2599
|
+
* (plus per-noop and summary events), and `deploy` emits `stateWritten`
|
|
2600
|
+
* after a successful state-write. Omit to run silently.
|
|
2601
|
+
*/
|
|
2602
|
+
readonly progress?: ProgressPort;
|
|
2365
2603
|
/** Reads file bytes for resources that have file-backed inputs. Defaults to `node:fs/promises.readFile`. */
|
|
2366
2604
|
readonly readFile?: (path: string) => Promise<Uint8Array>;
|
|
2367
2605
|
/** Per-kind driver table consulted for create / update dispatch. Default-constructed from `BEDROCK_API_KEY` when omitted. */
|
|
@@ -2377,8 +2615,8 @@ interface DeployOptions {
|
|
|
2377
2615
|
* `incompletePlaceEntry`, `incompleteUniverseEntry`, `missingCredential`,
|
|
2378
2616
|
* `unsupportedBackend`, `registryConfigMissing`).
|
|
2379
2617
|
*/
|
|
2380
|
-
type DeployError = IncompletePlaceEntryError | IncompleteUniverseEntryError | MissingCredentialError | RegistryConfigError | StateNotConfiguredError | UnknownEnvironmentError | UnsupportedBackendError | {
|
|
2381
|
-
readonly cause:
|
|
2618
|
+
type DeployError = IncompletePassEntryError | IncompletePlaceEntryError | IncompleteUniverseEntryError | MissingCredentialError | RegistryConfigError | StateNotConfiguredError | UnknownEnvironmentError | UnsupportedBackendError | {
|
|
2619
|
+
readonly cause: AggregateApplyError;
|
|
2382
2620
|
readonly kind: "applyFailed";
|
|
2383
2621
|
} | {
|
|
2384
2622
|
readonly cause: BuildDesiredError;
|
|
@@ -2500,70 +2738,6 @@ interface ClackPort {
|
|
|
2500
2738
|
outro(message: string): void;
|
|
2501
2739
|
}
|
|
2502
2740
|
//#endregion
|
|
2503
|
-
//#region src/ports/progress-port.d.ts
|
|
2504
|
-
/**
|
|
2505
|
-
* Per-environment outcome event emitted after a deploy completes
|
|
2506
|
-
* successfully. Carries the environment name and the count of resources
|
|
2507
|
-
* present in the persisted state snapshot.
|
|
2508
|
-
*/
|
|
2509
|
-
interface DeploySuccessEvent {
|
|
2510
|
-
/** The environment that finished reconciling. */
|
|
2511
|
-
readonly environment: string;
|
|
2512
|
-
/** Discriminator tag. */
|
|
2513
|
-
readonly kind: "deploySuccess";
|
|
2514
|
-
/** Number of resources in the post-deploy state snapshot. */
|
|
2515
|
-
readonly resourceCount: number;
|
|
2516
|
-
}
|
|
2517
|
-
/**
|
|
2518
|
-
* Per-environment outcome event emitted when a deploy fails. Carries the
|
|
2519
|
-
* environment name and the full {@link DeployError} so a renderer can
|
|
2520
|
-
* delegate to the existing diagnostic helpers.
|
|
2521
|
-
*/
|
|
2522
|
-
interface DeployFailureEvent {
|
|
2523
|
-
/** The environment whose deploy failed. */
|
|
2524
|
-
readonly environment: string;
|
|
2525
|
-
/** Stage-tagged failure reason returned by the shell `deploy` function. */
|
|
2526
|
-
readonly error: DeployError;
|
|
2527
|
-
/** Discriminator tag. */
|
|
2528
|
-
readonly kind: "deployFailure";
|
|
2529
|
-
}
|
|
2530
|
-
/**
|
|
2531
|
-
* Discriminated union of progress events the CLI emits while a deploy
|
|
2532
|
-
* runs. The variant set is additive: future per-stage and per-resource
|
|
2533
|
-
* events land as new `kind` values without breaking existing adapters.
|
|
2534
|
-
*/
|
|
2535
|
-
type ProgressEvent = DeployFailureEvent | DeploySuccessEvent;
|
|
2536
|
-
/**
|
|
2537
|
-
* Plugin contract for receiving deploy outcomes: the interface an adapter
|
|
2538
|
-
* (clack renderer, JSON logger, custom UI) implements to let the CLI hand
|
|
2539
|
-
* off events without re-implementing rendering logic.
|
|
2540
|
-
*
|
|
2541
|
-
* `ProgressPort` is a *driven* (secondary) port in hexagonal terms.
|
|
2542
|
-
*
|
|
2543
|
-
* @example
|
|
2544
|
-
*
|
|
2545
|
-
* ```ts
|
|
2546
|
-
* import type { ProgressEvent, ProgressPort } from "@bedrock-rbx/core";
|
|
2547
|
-
*
|
|
2548
|
-
* let received: ReadonlyArray<ProgressEvent> = [];
|
|
2549
|
-
* const port: ProgressPort = {
|
|
2550
|
-
* emit(event) {
|
|
2551
|
-
* received = [...received, event];
|
|
2552
|
-
* },
|
|
2553
|
-
* };
|
|
2554
|
-
*
|
|
2555
|
-
* port.emit({ environment: "production", kind: "deploySuccess", resourceCount: 3 });
|
|
2556
|
-
*
|
|
2557
|
-
* expect(received).toEqual([
|
|
2558
|
-
* { environment: "production", kind: "deploySuccess", resourceCount: 3 },
|
|
2559
|
-
* ]);
|
|
2560
|
-
* ```
|
|
2561
|
-
*/
|
|
2562
|
-
interface ProgressPort {
|
|
2563
|
-
/** Hand a single progress event to the adapter for rendering or logging. */
|
|
2564
|
-
emit(event: ProgressEvent): void;
|
|
2565
|
-
}
|
|
2566
|
-
//#endregion
|
|
2567
2741
|
//#region src/adapters/clack-progress-adapter.d.ts
|
|
2568
2742
|
/**
|
|
2569
2743
|
* Configuration for {@link createClackProgressAdapter}.
|
|
@@ -2571,12 +2745,18 @@ interface ProgressPort {
|
|
|
2571
2745
|
interface ClackProgressAdapterDeps {
|
|
2572
2746
|
/** Output port the events are rendered through. */
|
|
2573
2747
|
readonly clack: ClackPort;
|
|
2748
|
+
/**
|
|
2749
|
+
* Loaded project config; the `stateWritten` case resolves the per-environment
|
|
2750
|
+
* `StateConfig` against this to format the backend label. When omitted,
|
|
2751
|
+
* `stateWritten` renders the generic `"state"` placeholder.
|
|
2752
|
+
*/
|
|
2753
|
+
readonly config?: Config;
|
|
2574
2754
|
}
|
|
2575
2755
|
/**
|
|
2576
2756
|
* Build a {@link ProgressPort} that renders events through a `ClackPort`.
|
|
2577
|
-
* Pattern-matches on the event `kind`:
|
|
2578
|
-
*
|
|
2579
|
-
*
|
|
2757
|
+
* Pattern-matches on the event `kind`: per-resource events render one line each,
|
|
2758
|
+
* the aggregate `applySummary` becomes the deploy footer, and `stateWritten`
|
|
2759
|
+
* names the persistence backend resolved from the loaded `Config`.
|
|
2580
2760
|
*
|
|
2581
2761
|
* @example
|
|
2582
2762
|
*
|
|
@@ -2595,12 +2775,12 @@ interface ClackProgressAdapterDeps {
|
|
|
2595
2775
|
*
|
|
2596
2776
|
* const port = createClackProgressAdapter({ clack });
|
|
2597
2777
|
*
|
|
2598
|
-
* port.emit({ environment: "production", kind: "
|
|
2778
|
+
* port.emit({ environment: "production", kind: "stateWritten" });
|
|
2599
2779
|
*
|
|
2600
|
-
* expect(lines).toEqual(["
|
|
2780
|
+
* expect(lines).toEqual(["log: State written to state"]);
|
|
2601
2781
|
* ```
|
|
2602
2782
|
*
|
|
2603
|
-
* @param deps - The clack port the adapter renders through.
|
|
2783
|
+
* @param deps - The clack port and optional config the adapter renders through.
|
|
2604
2784
|
* @returns A `ProgressPort` that renders via clack.
|
|
2605
2785
|
*/
|
|
2606
2786
|
declare function createClackProgressAdapter(deps: ClackProgressAdapterDeps): ProgressPort;
|
|
@@ -3163,8 +3343,12 @@ declare function derivePriceFields(desired: {
|
|
|
3163
3343
|
* `update` op if any declared field differs or a `noop` op if every field
|
|
3164
3344
|
* matches.
|
|
3165
3345
|
*
|
|
3166
|
-
* Ops appear in the order their desired entries appear in the input array
|
|
3167
|
-
*
|
|
3346
|
+
* Ops appear in the order their desired entries appear in the input array.
|
|
3347
|
+
* `applyOps` regroups them into Phase 1 (universe) and Phase 2 (everything
|
|
3348
|
+
* else) when dispatching; the execution order within Phase 2 is not
|
|
3349
|
+
* guaranteed because Phase 2 dispatches concurrently. Persisted state-file
|
|
3350
|
+
* order is determined by the merge in `deploy.runReconcile` (which retains
|
|
3351
|
+
* prior-snapshot positions for unchanged keys), not by this diff output.
|
|
3168
3352
|
*
|
|
3169
3353
|
* @param desired - Declared desired state from user config, already normalized
|
|
3170
3354
|
* (file hashes computed, nullable wire values mapped to `undefined`).
|
|
@@ -3228,6 +3412,11 @@ declare function derivePriceFields(desired: {
|
|
|
3228
3412
|
* const ops = diff([unchanged, drifted, fresh], current);
|
|
3229
3413
|
*
|
|
3230
3414
|
* expect(ops.map((op) => op.type)).toEqual(["noop", "update", "create"]);
|
|
3415
|
+
*
|
|
3416
|
+
* const updateOp = ops[1]!;
|
|
3417
|
+
* if (updateOp.type === "update") {
|
|
3418
|
+
* expect(updateOp.changedFields).toStrictEqual(["name"]);
|
|
3419
|
+
* }
|
|
3231
3420
|
* ```
|
|
3232
3421
|
*/
|
|
3233
3422
|
declare function diff(desired: ReadonlyArray<ResourceDesiredState>, current: ReadonlyArray<ResourceCurrentState>): ReadonlyArray<Operation>;
|
|
@@ -3611,5 +3800,5 @@ interface MigrateMantleStateDeps {
|
|
|
3611
3800
|
*/
|
|
3612
3801
|
declare function migrateMantleState(deps: MigrateMantleStateDeps): Promise<Result$1<MigrationReport, MigrateError>>;
|
|
3613
3802
|
//#endregion
|
|
3614
|
-
export { type ApplyError, type BaseOperation, type BedrockState, type BuildDesiredError, type ClackPort, type ClackProgressAdapterDeps, type Config, type ConfigContext, type ConfigEnvironmentUniverseId, type ConfigError, type ConfigInput, type ConfigRootUniverseId, type ConfigValidationIssue, type CreateOperation, DEFAULT_PREFIX_FORMAT, type DeployError, type DeployFailureEvent, type DeployOptions, type DeploySuccessEvent, type DeveloperProductDesiredInput, type DeveloperProductDesiredState, type DeveloperProductDriverDeps, type DeveloperProductEntry, type DeveloperProductOutputs, type DisplayNamePrefixConfig, type DriverRegistry, type EnvironmentEntry, type GamePassDesiredInput, type GamePassDesiredState, type GamePassDriverDeps, type GamePassEntry, type GamePassOutputs, type GetEnvironmentError, type GistStateAdapterDeps, type GistStateConfig, type IncompletePlaceEntryError, type IncompleteUniverseEntryError, type KindIo, type KindRegistry, type LoadConfigOptions, type MigrateError, type MigrateMantleStateDeps, type MigrationReport, type MigrationSummary, type MigrationWarning, type MissingCredentialError, type NoopOperation, OpenCloudError, type Operation, type PlaceDesiredInput, type PlaceDesiredState, type PlaceDriverDeps, type PlaceEntry, type PlaceOutputs, type PriceFields, type ProgressEvent, type ProgressPort, type RegistryConfigError, type ResolvedConfig, type ResolvedPlaceEntry, type ResolvedUniverseEntry, type ResourceCurrentState, type ResourceDesiredInput, type ResourceDesiredState, type ResourceDriver, type ResourceEntryByKind, type ResourceKey, type ResourceKind, type ResourceKindModule, type ResourceOutputs, type ResourceOutputsByKind, type Result, type RobloxAssetId, SOCIAL_LINK_FIELDS, type SelectEnvironmentError, type Sha256Hex, type SocialLink, type SocialLinkField, type StateConfig, type StateError, type StateNotConfiguredError, type StatePort, type StatesByEnvironment, UNIVERSE_SINGLETON_KEY, type UniverseDesiredInput, type UniverseDesiredState, type UniverseDriverDeps, type UniverseEntry, type UniverseOutputs, type UniverseOverlayWithId, type UniverseOverlayWithoutId, type UnknownEnvironmentError, type UnsupportedBackendError, type UpdateOperation, applyOps, asResourceKey, asRobloxAssetId, asSha256Hex, buildDefaultRegistry, buildDesired, buildStatePort, createClackPort, createClackProgressAdapter, createDeveloperProductDriver, createGamePassDriver, createGistStateAdapter, createNoOpProgressAdapter, createPlaceDriver, createUniverseDriver, defaultKindRegistry, defineConfig, deploy, derivePriceFields, diff, flattenConfig, getEnvironment, isGistStateConfig, isResourceKey, isRobloxAssetId, isSha256Hex, loadConfig, migrateMantleState, parseStateFile, renderDisplayNamePrefix, resolveStateConfig, selectEnvironment, serializeStateFile, shouldReuploadIcon, validateConfig, validateEnvironmentName, validatePlan };
|
|
3803
|
+
export { type AggregateApplyError, type ApplyError, type ApplyOpsReporting, type ApplySummaryEvent, type BaseOperation, type BedrockState, type BuildDesiredError, type ClackPort, type ClackProgressAdapterDeps, type Config, type ConfigContext, type ConfigEnvironmentUniverseId, type ConfigError, type ConfigInput, type ConfigRootUniverseId, type ConfigValidationIssue, type CreateOperation, DEFAULT_PREFIX_FORMAT, type DeployError, type DeployFailureEvent, type DeployOptions, type DeploySuccessEvent, type DeveloperProductDesiredInput, type DeveloperProductDesiredState, type DeveloperProductDriverDeps, type DeveloperProductEntry, type DeveloperProductOutputs, type DisplayNamePrefixConfig, type DriverRegistry, type EnvironmentEntry, type GamePassDesiredInput, type GamePassDesiredState, type GamePassDriverDeps, type GamePassEntry, type GamePassOutputs, type GetEnvironmentError, type GistStateAdapterDeps, type GistStateConfig, type IncompletePlaceEntryError, type IncompleteUniverseEntryError, type KindIo, type KindRegistry, type LoadConfigOptions, type MigrateError, type MigrateMantleStateDeps, type MigrationReport, type MigrationSummary, type MigrationWarning, type MissingCredentialError, type NoopOperation, OpenCloudError, type Operation, type PlaceDesiredInput, type PlaceDesiredState, type PlaceDriverDeps, type PlaceEntry, type PlaceOutputs, type PriceFields, type ProgressEvent, type ProgressPort, type RedactedDeveloperProductOverride, type RedactedEnvironmentOverride, type RedactedGamePassOverride, type RedactedPlaceOverride, type RegistryConfigError, type ResolvedConfig, type ResolvedPlaceEntry, type ResolvedUniverseEntry, type ResourceCurrentState, type ResourceDesiredInput, type ResourceDesiredState, type ResourceDriver, type ResourceEntryByKind, type ResourceKey, type ResourceKind, type ResourceKindModule, type ResourceOpFailedEvent, type ResourceOpNoopEvent, type ResourceOpStartedEvent, type ResourceOpSucceededCreateEvent, type ResourceOpSucceededEvent, type ResourceOpSucceededUpdateEvent, type ResourceOutputs, type ResourceOutputsByKind, type Result, type RobloxAssetId, SOCIAL_LINK_FIELDS, type SelectEnvironmentError, type Sha256Hex, type SocialLink, type SocialLinkField, type StateConfig, type StateError, type StateNotConfiguredError, type StatePort, type StateWrittenEvent, type StatesByEnvironment, UNIVERSE_SINGLETON_KEY, type UniverseDesiredInput, type UniverseDesiredState, type UniverseDriverDeps, type UniverseEntry, type UniverseOutputs, type UniverseOverlayWithId, type UniverseOverlayWithoutId, type UnknownEnvironmentError, type UnsupportedBackendError, type UpdateOperation, applyOps, asResourceKey, asRobloxAssetId, asSha256Hex, buildDefaultRegistry, buildDesired, buildStatePort, createClackPort, createClackProgressAdapter, createDeveloperProductDriver, createGamePassDriver, createGistStateAdapter, createNoOpProgressAdapter, createPlaceDriver, createUniverseDriver, defaultKindRegistry, defineConfig, deploy, derivePriceFields, diff, flattenConfig, getEnvironment, isGistStateConfig, isResourceKey, isRobloxAssetId, isSha256Hex, loadConfig, migrateMantleState, parseStateFile, renderDisplayNamePrefix, resolveStateConfig, selectEnvironment, serializeStateFile, shouldReuploadIcon, validateConfig, validateEnvironmentName, validatePlan };
|
|
3615
3804
|
//# sourceMappingURL=index.d.mts.map
|