@bedrock-rbx/core 0.1.0-beta.13 → 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 +37 -23
- package/dist/cli/run.mjs.map +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/{define-config-Bd0XIiSX.d.mts → define-config-C2cOtDpP.d.mts} +117 -46
- package/dist/{define-config-Bd0XIiSX.d.mts.map → define-config-C2cOtDpP.d.mts.map} +1 -1
- package/dist/index.d.mts +473 -305
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{migrate-mantle-state-CQjWBZwT.mjs → migrate-mantle-state-qejWFAR0.mjs} +2112 -1662
- package/dist/migrate-mantle-state-qejWFAR0.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/migrate-mantle-state-CQjWBZwT.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
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";
|
|
@@ -1329,127 +1329,6 @@ type SelectEnvironmentError = IncompletePassEntryError | IncompletePlaceEntryErr
|
|
|
1329
1329
|
*/
|
|
1330
1330
|
declare function selectEnvironment(config: Config, environment: string): Result$1<ResolvedConfig, SelectEnvironmentError>;
|
|
1331
1331
|
//#endregion
|
|
1332
|
-
//#region src/ports/resource-driver.d.ts
|
|
1333
|
-
/**
|
|
1334
|
-
* Plugin contract for a resource adapter: the interface a third-party author
|
|
1335
|
-
* implements to teach Bedrock how to reconcile one {@link ResourceKind} against
|
|
1336
|
-
* its upstream API.
|
|
1337
|
-
*
|
|
1338
|
-
* `ResourceDriver<K>` is a *driven* (secondary) port in hexagonal terms; the
|
|
1339
|
-
* name "driver" follows Terraform, Pulumi, and Mantle IaC convention for a
|
|
1340
|
-
* component that talks to a specific resource API.
|
|
1341
|
-
*
|
|
1342
|
-
* @template K - The {@link ResourceKind} discriminator this driver handles.
|
|
1343
|
-
*
|
|
1344
|
-
* @example
|
|
1345
|
-
*
|
|
1346
|
-
* ```ts
|
|
1347
|
-
* import {
|
|
1348
|
-
* asResourceKey,
|
|
1349
|
-
* asRobloxAssetId,
|
|
1350
|
-
* asSha256Hex,
|
|
1351
|
-
* type ResourceDriver,
|
|
1352
|
-
* } from "@bedrock-rbx/core";
|
|
1353
|
-
*
|
|
1354
|
-
* const gamePassDriver: ResourceDriver<"gamePass"> = {
|
|
1355
|
-
* async create(desired) {
|
|
1356
|
-
* return {
|
|
1357
|
-
* data: {
|
|
1358
|
-
* ...desired,
|
|
1359
|
-
* outputs: {
|
|
1360
|
-
* assetId: asRobloxAssetId("9876543210"),
|
|
1361
|
-
* iconAssetIds: { "en-us": asRobloxAssetId("1122334455") },
|
|
1362
|
-
* },
|
|
1363
|
-
* },
|
|
1364
|
-
* success: true,
|
|
1365
|
-
* };
|
|
1366
|
-
* },
|
|
1367
|
-
* };
|
|
1368
|
-
*
|
|
1369
|
-
* return gamePassDriver
|
|
1370
|
-
* .create({
|
|
1371
|
-
* description: "Grants VIP perks.",
|
|
1372
|
-
* icon: { "en-us": "assets/vip-icon.png" },
|
|
1373
|
-
* iconFileHashes: {
|
|
1374
|
-
* "en-us": asSha256Hex(
|
|
1375
|
-
* "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
1376
|
-
* ),
|
|
1377
|
-
* },
|
|
1378
|
-
* key: asResourceKey("vip-pass"),
|
|
1379
|
-
* kind: "gamePass",
|
|
1380
|
-
* name: "VIP Pass",
|
|
1381
|
-
* price: undefined,
|
|
1382
|
-
* })
|
|
1383
|
-
* .then((result) => {
|
|
1384
|
-
* expect(result.success).toBeTrue();
|
|
1385
|
-
* if (result.success) {
|
|
1386
|
-
* expect(result.data.outputs.assetId).toBe("9876543210");
|
|
1387
|
-
* }
|
|
1388
|
-
* });
|
|
1389
|
-
* ```
|
|
1390
|
-
*/
|
|
1391
|
-
interface ResourceDriver<K extends ResourceKind> {
|
|
1392
|
-
/**
|
|
1393
|
-
* Create the resource upstream from its desired state and return the
|
|
1394
|
-
* resulting current state (desired fields + Roblox-assigned outputs).
|
|
1395
|
-
*/
|
|
1396
|
-
create(desired: Extract<ResourceDesiredState, {
|
|
1397
|
-
kind: K;
|
|
1398
|
-
}>): Promise<Result$1<ResourceCurrentState<K>, OpenCloudError$1>>;
|
|
1399
|
-
/**
|
|
1400
|
-
* Reconcile an upstream resource whose managed content has drifted from its
|
|
1401
|
-
* desired state. Receives the last-known current state so the driver can
|
|
1402
|
-
* compute a minimal patch (or no-op upstream, for file-backed kinds where
|
|
1403
|
-
* republishing is unconditional).
|
|
1404
|
-
*
|
|
1405
|
-
* Optional. Drivers whose upstream API has no update operation omit this
|
|
1406
|
-
* method; `applyOps` surfaces an `updateUnsupported` error at dispatch time
|
|
1407
|
-
* instead.
|
|
1408
|
-
*/
|
|
1409
|
-
update?(current: ResourceCurrentState<K>, desired: Extract<ResourceDesiredState, {
|
|
1410
|
-
kind: K;
|
|
1411
|
-
}>): Promise<Result$1<ResourceCurrentState<K>, OpenCloudError$1>>;
|
|
1412
|
-
}
|
|
1413
|
-
/**
|
|
1414
|
-
* Polymorphic dispatch table keyed by {@link ResourceKind}, mapping each kind
|
|
1415
|
-
* to the {@link ResourceDriver} that handles it. `applyOps` indexes the
|
|
1416
|
-
* registry by `op.desired.kind` to reach the matching driver with full type
|
|
1417
|
-
* safety: adding a new kind to `ResourceDesiredState` is a compile error until
|
|
1418
|
-
* a matching registry entry is supplied.
|
|
1419
|
-
*
|
|
1420
|
-
* @example
|
|
1421
|
-
*
|
|
1422
|
-
* ```ts
|
|
1423
|
-
* import { OpenCloudError, type DriverRegistry } from "@bedrock-rbx/core";
|
|
1424
|
-
*
|
|
1425
|
-
* const registry: DriverRegistry = {
|
|
1426
|
-
* gamePass: {
|
|
1427
|
-
* async create() {
|
|
1428
|
-
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1429
|
-
* },
|
|
1430
|
-
* },
|
|
1431
|
-
* place: {
|
|
1432
|
-
* async create() {
|
|
1433
|
-
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1434
|
-
* },
|
|
1435
|
-
* },
|
|
1436
|
-
* universe: {
|
|
1437
|
-
* async create() {
|
|
1438
|
-
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1439
|
-
* },
|
|
1440
|
-
* },
|
|
1441
|
-
* developerProduct: {
|
|
1442
|
-
* async create() {
|
|
1443
|
-
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1444
|
-
* },
|
|
1445
|
-
* },
|
|
1446
|
-
* };
|
|
1447
|
-
*
|
|
1448
|
-
* expect(registry.gamePass).toBeObject();
|
|
1449
|
-
* ```
|
|
1450
|
-
*/
|
|
1451
|
-
type DriverRegistry = { [K in ResourceKind]: ResourceDriver<K> };
|
|
1452
|
-
//#endregion
|
|
1453
1332
|
//#region src/core/operations.d.ts
|
|
1454
1333
|
/**
|
|
1455
1334
|
* Fields shared by every operation variant.
|
|
@@ -1561,11 +1440,13 @@ interface CreateOperation extends BaseOperation {
|
|
|
1561
1440
|
* name: "VIP Pass",
|
|
1562
1441
|
* price: 750,
|
|
1563
1442
|
* },
|
|
1443
|
+
* changedFields: ["description", "price"],
|
|
1564
1444
|
* key: asResourceKey("vip-pass"),
|
|
1565
1445
|
* type: "update",
|
|
1566
1446
|
* };
|
|
1567
1447
|
*
|
|
1568
1448
|
* expect(op.type).toBe("update");
|
|
1449
|
+
* expect(op.changedFields).toStrictEqual(["description", "price"]);
|
|
1569
1450
|
* if (op.desired.kind === "gamePass") {
|
|
1570
1451
|
* expect(op.desired.price).toBe(750);
|
|
1571
1452
|
* }
|
|
@@ -1575,6 +1456,13 @@ interface CreateOperation extends BaseOperation {
|
|
|
1575
1456
|
* ```
|
|
1576
1457
|
*/
|
|
1577
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>;
|
|
1578
1466
|
/** Last-known live state; the driver computes a patch against `desired`. */
|
|
1579
1467
|
readonly current: ResourceCurrentState;
|
|
1580
1468
|
/** Declared desired state to converge toward. */
|
|
@@ -1587,9 +1475,9 @@ interface UpdateOperation extends BaseOperation {
|
|
|
1587
1475
|
* entry matches its `current` entry exactly. The driver performs no I/O for
|
|
1588
1476
|
* this variant.
|
|
1589
1477
|
*
|
|
1590
|
-
*
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
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`.
|
|
1593
1481
|
*
|
|
1594
1482
|
* @example
|
|
1595
1483
|
*
|
|
@@ -1598,14 +1486,18 @@ interface UpdateOperation extends BaseOperation {
|
|
|
1598
1486
|
*
|
|
1599
1487
|
* const op: NoopOperation = {
|
|
1600
1488
|
* key: asResourceKey("vip-pass"),
|
|
1489
|
+
* kind: "gamePass",
|
|
1601
1490
|
* type: "noop",
|
|
1602
1491
|
* };
|
|
1603
1492
|
*
|
|
1604
1493
|
* expect(op.type).toBe("noop");
|
|
1494
|
+
* expect(op.kind).toBe("gamePass");
|
|
1605
1495
|
* expect(op.key).toBe("vip-pass");
|
|
1606
1496
|
* ```
|
|
1607
1497
|
*/
|
|
1608
1498
|
interface NoopOperation extends BaseOperation {
|
|
1499
|
+
/** Resource-kind discriminator copied from the matching desired/current entry. */
|
|
1500
|
+
readonly kind: ResourceKind;
|
|
1609
1501
|
/** Discriminator tag for the `Operation` union. */
|
|
1610
1502
|
readonly type: "noop";
|
|
1611
1503
|
}
|
|
@@ -1638,6 +1530,7 @@ interface NoopOperation extends BaseOperation {
|
|
|
1638
1530
|
*
|
|
1639
1531
|
* const op: Operation = {
|
|
1640
1532
|
* key: asResourceKey("vip-pass"),
|
|
1533
|
+
* kind: "gamePass",
|
|
1641
1534
|
* type: "noop",
|
|
1642
1535
|
* };
|
|
1643
1536
|
*
|
|
@@ -1646,15 +1539,145 @@ interface NoopOperation extends BaseOperation {
|
|
|
1646
1539
|
*/
|
|
1647
1540
|
type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
1648
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
|
|
1649
1663
|
//#region src/shell/apply-ops.d.ts
|
|
1650
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
|
+
/**
|
|
1651
1676
|
* Failure surfaced by `applyOps` when an operation cannot be applied.
|
|
1652
1677
|
* Plain-data discriminated union; narrow on `kind`, do not `instanceof` it.
|
|
1653
|
-
*
|
|
1654
|
-
* `
|
|
1655
|
-
*
|
|
1656
|
-
* follow-up reconcile does not duplicate Roblox-side resources that have
|
|
1657
|
-
* 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).
|
|
1658
1681
|
*
|
|
1659
1682
|
* @example
|
|
1660
1683
|
*
|
|
@@ -1666,6 +1689,9 @@ type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
|
1666
1689
|
* case "driverFailure": {
|
|
1667
1690
|
* return `driver failed for ${err.key}: ${err.cause.message}`;
|
|
1668
1691
|
* }
|
|
1692
|
+
* case "unexpectedThrow": {
|
|
1693
|
+
* return `unexpected error for ${err.key}`;
|
|
1694
|
+
* }
|
|
1669
1695
|
* case "updateUnsupported": {
|
|
1670
1696
|
* return `update not supported for ${err.key}`;
|
|
1671
1697
|
* }
|
|
@@ -1674,7 +1700,6 @@ type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
|
1674
1700
|
*
|
|
1675
1701
|
* const err: ApplyError = {
|
|
1676
1702
|
* key: asResourceKey("vip-pass"),
|
|
1677
|
-
* appliedSoFar: [],
|
|
1678
1703
|
* kind: "updateUnsupported",
|
|
1679
1704
|
* };
|
|
1680
1705
|
*
|
|
@@ -1682,126 +1707,290 @@ type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
|
1682
1707
|
* ```
|
|
1683
1708
|
*/
|
|
1684
1709
|
type ApplyError = {
|
|
1685
|
-
readonly appliedSoFar: ReadonlyArray<ResourceCurrentState>;
|
|
1686
1710
|
readonly cause: OpenCloudError$1;
|
|
1687
1711
|
readonly key: ResourceKey;
|
|
1688
1712
|
readonly kind: "driverFailure";
|
|
1689
1713
|
} | {
|
|
1690
|
-
readonly
|
|
1714
|
+
readonly cause: unknown;
|
|
1715
|
+
readonly key: ResourceKey;
|
|
1716
|
+
readonly kind: "unexpectedThrow";
|
|
1717
|
+
} | {
|
|
1691
1718
|
readonly key: ResourceKey;
|
|
1692
1719
|
readonly kind: "updateUnsupported";
|
|
1693
1720
|
};
|
|
1694
1721
|
/**
|
|
1695
|
-
*
|
|
1696
|
-
*
|
|
1697
|
-
* `
|
|
1698
|
-
*
|
|
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.
|
|
1699
1757
|
*
|
|
1700
1758
|
* Behaviour:
|
|
1701
|
-
* - `create` operations
|
|
1702
|
-
* - `update` operations
|
|
1703
|
-
*
|
|
1704
|
-
* `
|
|
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.
|
|
1705
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.
|
|
1706
1968
|
*
|
|
1707
|
-
*
|
|
1708
|
-
* non-noop op, in dispatched order. Noops are not represented; callers
|
|
1709
|
-
* needing a full post-apply snapshot merge with the pre-apply current
|
|
1710
|
-
* state keyed by `ResourceKey`.
|
|
1969
|
+
* `ProgressPort` is a *driven* (secondary) port in hexagonal terms.
|
|
1711
1970
|
*
|
|
1712
|
-
* @param ops - Reconciliation operations produced by `diff`, applied in order.
|
|
1713
|
-
* @param registry - Per-kind driver table; dispatch uses `op.desired.kind` as the index.
|
|
1714
|
-
* @returns `Ok(state)` when every operation succeeds, where `state` holds
|
|
1715
|
-
* driver outputs for each non-noop op in dispatched order; or the first
|
|
1716
|
-
* failure encountered.
|
|
1717
|
-
* @throws Whatever the dispatched driver rejects with outside its `Result`
|
|
1718
|
-
* return. A driver whose injected I/O (file reads, network calls, etc.)
|
|
1719
|
-
* throws will surface that rejection here rather than translating it into
|
|
1720
|
-
* a `Result` failure; wrap the call site in a try/catch when drivers are
|
|
1721
|
-
* not trusted to contain their own rejections.
|
|
1722
1971
|
* @example
|
|
1723
1972
|
*
|
|
1724
1973
|
* ```ts
|
|
1725
|
-
* import {
|
|
1726
|
-
* applyOps,
|
|
1727
|
-
* asResourceKey,
|
|
1728
|
-
* asRobloxAssetId,
|
|
1729
|
-
* asSha256Hex,
|
|
1730
|
-
* type DriverRegistry,
|
|
1731
|
-
* type Operation,
|
|
1732
|
-
* } from "@bedrock-rbx/core";
|
|
1974
|
+
* import type { ProgressEvent, ProgressPort } from "@bedrock-rbx/core";
|
|
1733
1975
|
*
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
* data: {
|
|
1739
|
-
* ...desired,
|
|
1740
|
-
* outputs: {
|
|
1741
|
-
* assetId: asRobloxAssetId("9876543210"),
|
|
1742
|
-
* iconAssetIds: { "en-us": asRobloxAssetId("1122334455") },
|
|
1743
|
-
* },
|
|
1744
|
-
* },
|
|
1745
|
-
* success: true,
|
|
1746
|
-
* };
|
|
1747
|
-
* },
|
|
1748
|
-
* },
|
|
1749
|
-
* place: {
|
|
1750
|
-
* async create(desired) {
|
|
1751
|
-
* return {
|
|
1752
|
-
* data: { ...desired, outputs: { versionNumber: 1 } },
|
|
1753
|
-
* success: true,
|
|
1754
|
-
* };
|
|
1755
|
-
* },
|
|
1756
|
-
* },
|
|
1757
|
-
* universe: {
|
|
1758
|
-
* async create(desired) {
|
|
1759
|
-
* return {
|
|
1760
|
-
* data: { ...desired, outputs: { rootPlaceId: asRobloxAssetId("4711") } },
|
|
1761
|
-
* success: true,
|
|
1762
|
-
* };
|
|
1763
|
-
* },
|
|
1764
|
-
* },
|
|
1765
|
-
* developerProduct: {
|
|
1766
|
-
* async create(desired) {
|
|
1767
|
-
* return {
|
|
1768
|
-
* data: {
|
|
1769
|
-
* ...desired,
|
|
1770
|
-
* outputs: { productId: asRobloxAssetId("8172635495") },
|
|
1771
|
-
* },
|
|
1772
|
-
* success: true,
|
|
1773
|
-
* };
|
|
1774
|
-
* },
|
|
1976
|
+
* let received: ReadonlyArray<ProgressEvent> = [];
|
|
1977
|
+
* const port: ProgressPort = {
|
|
1978
|
+
* emit(event) {
|
|
1979
|
+
* received = [...received, event];
|
|
1775
1980
|
* },
|
|
1776
1981
|
* };
|
|
1777
1982
|
*
|
|
1778
|
-
*
|
|
1779
|
-
* {
|
|
1780
|
-
* key: asResourceKey("vip-pass"),
|
|
1781
|
-
* type: "create",
|
|
1782
|
-
* desired: {
|
|
1783
|
-
* key: asResourceKey("vip-pass"),
|
|
1784
|
-
* name: "VIP Pass",
|
|
1785
|
-
* description: "Grants VIP perks.",
|
|
1786
|
-
* icon: { "en-us": "assets/vip-icon.png" },
|
|
1787
|
-
* iconFileHashes: {
|
|
1788
|
-
* "en-us": asSha256Hex(
|
|
1789
|
-
* "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
1790
|
-
* ),
|
|
1791
|
-
* },
|
|
1792
|
-
* kind: "gamePass",
|
|
1793
|
-
* price: 500,
|
|
1794
|
-
* },
|
|
1795
|
-
* },
|
|
1796
|
-
* ];
|
|
1983
|
+
* port.emit({ environment: "production", kind: "deploySuccess", resourceCount: 3 });
|
|
1797
1984
|
*
|
|
1798
|
-
*
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
* });
|
|
1985
|
+
* expect(received).toEqual([
|
|
1986
|
+
* { environment: "production", kind: "deploySuccess", resourceCount: 3 },
|
|
1987
|
+
* ]);
|
|
1802
1988
|
* ```
|
|
1803
1989
|
*/
|
|
1804
|
-
|
|
1990
|
+
interface ProgressPort {
|
|
1991
|
+
/** Hand a single progress event to the adapter for rendering or logging. */
|
|
1992
|
+
emit(event: ProgressEvent): void;
|
|
1993
|
+
}
|
|
1805
1994
|
//#endregion
|
|
1806
1995
|
//#region src/shell/build-default-registry.d.ts
|
|
1807
1996
|
/**
|
|
@@ -2086,16 +2275,21 @@ declare function flattenConfig(config: ResolvedConfig): ReadonlyArray<ResourceDe
|
|
|
2086
2275
|
//#endregion
|
|
2087
2276
|
//#region src/core/kinds/module.d.ts
|
|
2088
2277
|
/**
|
|
2089
|
-
* Failure surfaced during desired-state preparation.
|
|
2278
|
+
* Failure surfaced during desired-state preparation. Three variants today:
|
|
2090
2279
|
*
|
|
2091
2280
|
* - `fileReadFailed`: a kind module's `normalize` could not read a file
|
|
2092
2281
|
* the input declared (e.g. An icon path that is missing on disk).
|
|
2093
2282
|
* - `iconRemovalRejected`: `validatePlan` saw a kind whose prior current
|
|
2094
2283
|
* state recorded an icon that the desired state no longer declares,
|
|
2095
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`.
|
|
2096
2289
|
*
|
|
2097
|
-
*
|
|
2098
|
-
* 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.
|
|
2099
2293
|
*
|
|
2100
2294
|
* @example
|
|
2101
2295
|
*
|
|
@@ -2121,6 +2315,11 @@ type BuildDesiredError = {
|
|
|
2121
2315
|
/** ResourceKey of the entry whose icon is being removed. */readonly key: ResourceKey; /** Literal discriminator for narrowing. */
|
|
2122
2316
|
readonly kind: "iconRemovalRejected"; /** Human-readable explanation naming the resource and the invariant. */
|
|
2123
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;
|
|
2124
2323
|
};
|
|
2125
2324
|
/**
|
|
2126
2325
|
* I/O surface the shell injects into kind-module `normalize` calls. Carries
|
|
@@ -2184,6 +2383,8 @@ interface KindIo {
|
|
|
2184
2383
|
* },
|
|
2185
2384
|
* success: true,
|
|
2186
2385
|
* }),
|
|
2386
|
+
* changedFieldsBetween: (desired, current) =>
|
|
2387
|
+
* desired.name === current.name ? [] : ["name"],
|
|
2187
2388
|
* fieldsEqual: (desired, current) => desired.name === current.name,
|
|
2188
2389
|
* };
|
|
2189
2390
|
*
|
|
@@ -2200,6 +2401,15 @@ interface ResourceKindModule<K extends ResourceKind> {
|
|
|
2200
2401
|
* invariants omit this hook.
|
|
2201
2402
|
*/
|
|
2202
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>;
|
|
2203
2413
|
/** ArkType schema for the authored entry body of this kind. */
|
|
2204
2414
|
readonly entrySchema: Type$1<ResourceEntryByKind[K]>;
|
|
2205
2415
|
/**
|
|
@@ -2383,6 +2593,13 @@ interface DeployOptions {
|
|
|
2383
2593
|
readonly getEnv?: (name: string) => string | undefined;
|
|
2384
2594
|
/** Loader invoked when `config` is omitted; defaults to `loadConfig` from this package. */
|
|
2385
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;
|
|
2386
2603
|
/** Reads file bytes for resources that have file-backed inputs. Defaults to `node:fs/promises.readFile`. */
|
|
2387
2604
|
readonly readFile?: (path: string) => Promise<Uint8Array>;
|
|
2388
2605
|
/** Per-kind driver table consulted for create / update dispatch. Default-constructed from `BEDROCK_API_KEY` when omitted. */
|
|
@@ -2399,7 +2616,7 @@ interface DeployOptions {
|
|
|
2399
2616
|
* `unsupportedBackend`, `registryConfigMissing`).
|
|
2400
2617
|
*/
|
|
2401
2618
|
type DeployError = IncompletePassEntryError | IncompletePlaceEntryError | IncompleteUniverseEntryError | MissingCredentialError | RegistryConfigError | StateNotConfiguredError | UnknownEnvironmentError | UnsupportedBackendError | {
|
|
2402
|
-
readonly cause:
|
|
2619
|
+
readonly cause: AggregateApplyError;
|
|
2403
2620
|
readonly kind: "applyFailed";
|
|
2404
2621
|
} | {
|
|
2405
2622
|
readonly cause: BuildDesiredError;
|
|
@@ -2521,70 +2738,6 @@ interface ClackPort {
|
|
|
2521
2738
|
outro(message: string): void;
|
|
2522
2739
|
}
|
|
2523
2740
|
//#endregion
|
|
2524
|
-
//#region src/ports/progress-port.d.ts
|
|
2525
|
-
/**
|
|
2526
|
-
* Per-environment outcome event emitted after a deploy completes
|
|
2527
|
-
* successfully. Carries the environment name and the count of resources
|
|
2528
|
-
* present in the persisted state snapshot.
|
|
2529
|
-
*/
|
|
2530
|
-
interface DeploySuccessEvent {
|
|
2531
|
-
/** The environment that finished reconciling. */
|
|
2532
|
-
readonly environment: string;
|
|
2533
|
-
/** Discriminator tag. */
|
|
2534
|
-
readonly kind: "deploySuccess";
|
|
2535
|
-
/** Number of resources in the post-deploy state snapshot. */
|
|
2536
|
-
readonly resourceCount: number;
|
|
2537
|
-
}
|
|
2538
|
-
/**
|
|
2539
|
-
* Per-environment outcome event emitted when a deploy fails. Carries the
|
|
2540
|
-
* environment name and the full {@link DeployError} so a renderer can
|
|
2541
|
-
* delegate to the existing diagnostic helpers.
|
|
2542
|
-
*/
|
|
2543
|
-
interface DeployFailureEvent {
|
|
2544
|
-
/** The environment whose deploy failed. */
|
|
2545
|
-
readonly environment: string;
|
|
2546
|
-
/** Stage-tagged failure reason returned by the shell `deploy` function. */
|
|
2547
|
-
readonly error: DeployError;
|
|
2548
|
-
/** Discriminator tag. */
|
|
2549
|
-
readonly kind: "deployFailure";
|
|
2550
|
-
}
|
|
2551
|
-
/**
|
|
2552
|
-
* Discriminated union of progress events the CLI emits while a deploy
|
|
2553
|
-
* runs. The variant set is additive: future per-stage and per-resource
|
|
2554
|
-
* events land as new `kind` values without breaking existing adapters.
|
|
2555
|
-
*/
|
|
2556
|
-
type ProgressEvent = DeployFailureEvent | DeploySuccessEvent;
|
|
2557
|
-
/**
|
|
2558
|
-
* Plugin contract for receiving deploy outcomes: the interface an adapter
|
|
2559
|
-
* (clack renderer, JSON logger, custom UI) implements to let the CLI hand
|
|
2560
|
-
* off events without re-implementing rendering logic.
|
|
2561
|
-
*
|
|
2562
|
-
* `ProgressPort` is a *driven* (secondary) port in hexagonal terms.
|
|
2563
|
-
*
|
|
2564
|
-
* @example
|
|
2565
|
-
*
|
|
2566
|
-
* ```ts
|
|
2567
|
-
* import type { ProgressEvent, ProgressPort } from "@bedrock-rbx/core";
|
|
2568
|
-
*
|
|
2569
|
-
* let received: ReadonlyArray<ProgressEvent> = [];
|
|
2570
|
-
* const port: ProgressPort = {
|
|
2571
|
-
* emit(event) {
|
|
2572
|
-
* received = [...received, event];
|
|
2573
|
-
* },
|
|
2574
|
-
* };
|
|
2575
|
-
*
|
|
2576
|
-
* port.emit({ environment: "production", kind: "deploySuccess", resourceCount: 3 });
|
|
2577
|
-
*
|
|
2578
|
-
* expect(received).toEqual([
|
|
2579
|
-
* { environment: "production", kind: "deploySuccess", resourceCount: 3 },
|
|
2580
|
-
* ]);
|
|
2581
|
-
* ```
|
|
2582
|
-
*/
|
|
2583
|
-
interface ProgressPort {
|
|
2584
|
-
/** Hand a single progress event to the adapter for rendering or logging. */
|
|
2585
|
-
emit(event: ProgressEvent): void;
|
|
2586
|
-
}
|
|
2587
|
-
//#endregion
|
|
2588
2741
|
//#region src/adapters/clack-progress-adapter.d.ts
|
|
2589
2742
|
/**
|
|
2590
2743
|
* Configuration for {@link createClackProgressAdapter}.
|
|
@@ -2592,12 +2745,18 @@ interface ProgressPort {
|
|
|
2592
2745
|
interface ClackProgressAdapterDeps {
|
|
2593
2746
|
/** Output port the events are rendered through. */
|
|
2594
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;
|
|
2595
2754
|
}
|
|
2596
2755
|
/**
|
|
2597
2756
|
* Build a {@link ProgressPort} that renders events through a `ClackPort`.
|
|
2598
|
-
* Pattern-matches on the event `kind`:
|
|
2599
|
-
*
|
|
2600
|
-
*
|
|
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`.
|
|
2601
2760
|
*
|
|
2602
2761
|
* @example
|
|
2603
2762
|
*
|
|
@@ -2616,12 +2775,12 @@ interface ClackProgressAdapterDeps {
|
|
|
2616
2775
|
*
|
|
2617
2776
|
* const port = createClackProgressAdapter({ clack });
|
|
2618
2777
|
*
|
|
2619
|
-
* port.emit({ environment: "production", kind: "
|
|
2778
|
+
* port.emit({ environment: "production", kind: "stateWritten" });
|
|
2620
2779
|
*
|
|
2621
|
-
* expect(lines).toEqual(["
|
|
2780
|
+
* expect(lines).toEqual(["log: State written to state"]);
|
|
2622
2781
|
* ```
|
|
2623
2782
|
*
|
|
2624
|
-
* @param deps - The clack port the adapter renders through.
|
|
2783
|
+
* @param deps - The clack port and optional config the adapter renders through.
|
|
2625
2784
|
* @returns A `ProgressPort` that renders via clack.
|
|
2626
2785
|
*/
|
|
2627
2786
|
declare function createClackProgressAdapter(deps: ClackProgressAdapterDeps): ProgressPort;
|
|
@@ -3184,8 +3343,12 @@ declare function derivePriceFields(desired: {
|
|
|
3184
3343
|
* `update` op if any declared field differs or a `noop` op if every field
|
|
3185
3344
|
* matches.
|
|
3186
3345
|
*
|
|
3187
|
-
* Ops appear in the order their desired entries appear in the input array
|
|
3188
|
-
*
|
|
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.
|
|
3189
3352
|
*
|
|
3190
3353
|
* @param desired - Declared desired state from user config, already normalized
|
|
3191
3354
|
* (file hashes computed, nullable wire values mapped to `undefined`).
|
|
@@ -3249,6 +3412,11 @@ declare function derivePriceFields(desired: {
|
|
|
3249
3412
|
* const ops = diff([unchanged, drifted, fresh], current);
|
|
3250
3413
|
*
|
|
3251
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
|
+
* }
|
|
3252
3420
|
* ```
|
|
3253
3421
|
*/
|
|
3254
3422
|
declare function diff(desired: ReadonlyArray<ResourceDesiredState>, current: ReadonlyArray<ResourceCurrentState>): ReadonlyArray<Operation>;
|
|
@@ -3632,5 +3800,5 @@ interface MigrateMantleStateDeps {
|
|
|
3632
3800
|
*/
|
|
3633
3801
|
declare function migrateMantleState(deps: MigrateMantleStateDeps): Promise<Result$1<MigrationReport, MigrateError>>;
|
|
3634
3802
|
//#endregion
|
|
3635
|
-
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 RedactedDeveloperProductOverride, 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 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 };
|
|
3636
3804
|
//# sourceMappingURL=index.d.mts.map
|