@bedrock-rbx/core 0.1.0-beta.13 → 0.1.0-beta.15
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/README.md +180 -0
- package/dist/cli/run.mjs +164 -52
- package/dist/cli/run.mjs.map +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/{define-config-Bd0XIiSX.d.mts → define-config-B4GZRPj-.d.mts} +119 -48
- package/dist/{define-config-Bd0XIiSX.d.mts.map → define-config-B4GZRPj-.d.mts.map} +1 -1
- package/dist/index.d.mts +712 -393
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -26
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-mantle-state-CQjWBZwT.mjs → migrate-mantle-state-ClQ40EFD.mjs} +2035 -1291
- package/dist/migrate-mantle-state-ClQ40EFD.mjs.map +1 -0
- package/package.json +4 -4
- 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-B4GZRPj-.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";
|
|
@@ -1017,6 +1017,13 @@ interface GistStateAdapterDeps {
|
|
|
1017
1017
|
/** ID of an existing GitHub Gist that holds this project's state files. */
|
|
1018
1018
|
readonly gistId: string;
|
|
1019
1019
|
/**
|
|
1020
|
+
* Injection seam for retry jitter; defaults to `Math.random`. Tests pass a
|
|
1021
|
+
* deterministic source so jittered sleep durations stay stable across runs.
|
|
1022
|
+
* Jitter prevents concurrent callers (parallel CI jobs writing to the same
|
|
1023
|
+
* gist) from retrying in lockstep and re-colliding on each backoff.
|
|
1024
|
+
*/
|
|
1025
|
+
readonly random?: (() => number) | undefined;
|
|
1026
|
+
/**
|
|
1020
1027
|
* Injection seam for retry backoff timing; defaults to a `setTimeout`-based
|
|
1021
1028
|
* promise. Tests pass a fake to keep retry assertions deterministic.
|
|
1022
1029
|
*/
|
|
@@ -1107,7 +1114,7 @@ interface BuildStatePortDeps {
|
|
|
1107
1114
|
* const port = buildStatePort({
|
|
1108
1115
|
* fetch: async () =>
|
|
1109
1116
|
* new Response(JSON.stringify({ files: {} }), { status: 200 }),
|
|
1110
|
-
* getEnv: (name) => (name === "
|
|
1117
|
+
* getEnv: (name) => (name === "BEDROCK_GITHUB_TOKEN" ? "ghp_example" : undefined),
|
|
1111
1118
|
* stateConfig: { backend: "gist", gistId: "abc123" },
|
|
1112
1119
|
* });
|
|
1113
1120
|
*
|
|
@@ -1329,127 +1336,6 @@ type SelectEnvironmentError = IncompletePassEntryError | IncompletePlaceEntryErr
|
|
|
1329
1336
|
*/
|
|
1330
1337
|
declare function selectEnvironment(config: Config, environment: string): Result$1<ResolvedConfig, SelectEnvironmentError>;
|
|
1331
1338
|
//#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
1339
|
//#region src/core/operations.d.ts
|
|
1454
1340
|
/**
|
|
1455
1341
|
* Fields shared by every operation variant.
|
|
@@ -1561,11 +1447,13 @@ interface CreateOperation extends BaseOperation {
|
|
|
1561
1447
|
* name: "VIP Pass",
|
|
1562
1448
|
* price: 750,
|
|
1563
1449
|
* },
|
|
1450
|
+
* changedFields: ["description", "price"],
|
|
1564
1451
|
* key: asResourceKey("vip-pass"),
|
|
1565
1452
|
* type: "update",
|
|
1566
1453
|
* };
|
|
1567
1454
|
*
|
|
1568
1455
|
* expect(op.type).toBe("update");
|
|
1456
|
+
* expect(op.changedFields).toStrictEqual(["description", "price"]);
|
|
1569
1457
|
* if (op.desired.kind === "gamePass") {
|
|
1570
1458
|
* expect(op.desired.price).toBe(750);
|
|
1571
1459
|
* }
|
|
@@ -1575,7 +1463,14 @@ interface CreateOperation extends BaseOperation {
|
|
|
1575
1463
|
* ```
|
|
1576
1464
|
*/
|
|
1577
1465
|
interface UpdateOperation extends BaseOperation {
|
|
1578
|
-
/**
|
|
1466
|
+
/**
|
|
1467
|
+
* Top-level field names that differ between `current` and `desired`,
|
|
1468
|
+
* populated by `diff` from the kind module's `changedFieldsBetween`.
|
|
1469
|
+
* Preview and apply renderers consume this as the single source of truth
|
|
1470
|
+
* for "what changed" on this op; never empty for an `update` variant.
|
|
1471
|
+
*/
|
|
1472
|
+
readonly changedFields: ReadonlyArray<string>;
|
|
1473
|
+
/** Last-known current state; the driver computes a patch against `desired`. */
|
|
1579
1474
|
readonly current: ResourceCurrentState;
|
|
1580
1475
|
/** Declared desired state to converge toward. */
|
|
1581
1476
|
readonly desired: ResourceDesiredState;
|
|
@@ -1587,9 +1482,9 @@ interface UpdateOperation extends BaseOperation {
|
|
|
1587
1482
|
* entry matches its `current` entry exactly. The driver performs no I/O for
|
|
1588
1483
|
* this variant.
|
|
1589
1484
|
*
|
|
1590
|
-
*
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
1485
|
+
* Carries `key` and `kind` so progress renderers and adapters can describe the
|
|
1486
|
+
* unchanged resource without re-looking it up in the desired or current
|
|
1487
|
+
* snapshots passed to `diff`.
|
|
1593
1488
|
*
|
|
1594
1489
|
* @example
|
|
1595
1490
|
*
|
|
@@ -1598,14 +1493,18 @@ interface UpdateOperation extends BaseOperation {
|
|
|
1598
1493
|
*
|
|
1599
1494
|
* const op: NoopOperation = {
|
|
1600
1495
|
* key: asResourceKey("vip-pass"),
|
|
1496
|
+
* kind: "gamePass",
|
|
1601
1497
|
* type: "noop",
|
|
1602
1498
|
* };
|
|
1603
1499
|
*
|
|
1604
1500
|
* expect(op.type).toBe("noop");
|
|
1501
|
+
* expect(op.kind).toBe("gamePass");
|
|
1605
1502
|
* expect(op.key).toBe("vip-pass");
|
|
1606
1503
|
* ```
|
|
1607
1504
|
*/
|
|
1608
1505
|
interface NoopOperation extends BaseOperation {
|
|
1506
|
+
/** Resource-kind discriminator copied from the matching desired/current entry. */
|
|
1507
|
+
readonly kind: ResourceKind;
|
|
1609
1508
|
/** Discriminator tag for the `Operation` union. */
|
|
1610
1509
|
readonly type: "noop";
|
|
1611
1510
|
}
|
|
@@ -1638,6 +1537,7 @@ interface NoopOperation extends BaseOperation {
|
|
|
1638
1537
|
*
|
|
1639
1538
|
* const op: Operation = {
|
|
1640
1539
|
* key: asResourceKey("vip-pass"),
|
|
1540
|
+
* kind: "gamePass",
|
|
1641
1541
|
* type: "noop",
|
|
1642
1542
|
* };
|
|
1643
1543
|
*
|
|
@@ -1646,15 +1546,145 @@ interface NoopOperation extends BaseOperation {
|
|
|
1646
1546
|
*/
|
|
1647
1547
|
type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
1648
1548
|
//#endregion
|
|
1549
|
+
//#region src/ports/resource-driver.d.ts
|
|
1550
|
+
/**
|
|
1551
|
+
* Plugin contract for a resource adapter: the interface a third-party author
|
|
1552
|
+
* implements to teach Bedrock how to reconcile one {@link ResourceKind} against
|
|
1553
|
+
* its upstream API.
|
|
1554
|
+
*
|
|
1555
|
+
* `ResourceDriver<K>` is a *driven* (secondary) port in hexagonal terms; the
|
|
1556
|
+
* name "driver" follows Terraform, Pulumi, and Mantle IaC convention for a
|
|
1557
|
+
* component that talks to a specific resource API.
|
|
1558
|
+
*
|
|
1559
|
+
* @template K - The {@link ResourceKind} discriminator this driver handles.
|
|
1560
|
+
*
|
|
1561
|
+
* @example
|
|
1562
|
+
*
|
|
1563
|
+
* ```ts
|
|
1564
|
+
* import {
|
|
1565
|
+
* asResourceKey,
|
|
1566
|
+
* asRobloxAssetId,
|
|
1567
|
+
* asSha256Hex,
|
|
1568
|
+
* type ResourceDriver,
|
|
1569
|
+
* } from "@bedrock-rbx/core";
|
|
1570
|
+
*
|
|
1571
|
+
* const gamePassDriver: ResourceDriver<"gamePass"> = {
|
|
1572
|
+
* async create(desired) {
|
|
1573
|
+
* return {
|
|
1574
|
+
* data: {
|
|
1575
|
+
* ...desired,
|
|
1576
|
+
* outputs: {
|
|
1577
|
+
* assetId: asRobloxAssetId("9876543210"),
|
|
1578
|
+
* iconAssetIds: { "en-us": asRobloxAssetId("1122334455") },
|
|
1579
|
+
* },
|
|
1580
|
+
* },
|
|
1581
|
+
* success: true,
|
|
1582
|
+
* };
|
|
1583
|
+
* },
|
|
1584
|
+
* };
|
|
1585
|
+
*
|
|
1586
|
+
* return gamePassDriver
|
|
1587
|
+
* .create({
|
|
1588
|
+
* description: "Grants VIP perks.",
|
|
1589
|
+
* icon: { "en-us": "assets/vip-icon.png" },
|
|
1590
|
+
* iconFileHashes: {
|
|
1591
|
+
* "en-us": asSha256Hex(
|
|
1592
|
+
* "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
1593
|
+
* ),
|
|
1594
|
+
* },
|
|
1595
|
+
* key: asResourceKey("vip-pass"),
|
|
1596
|
+
* kind: "gamePass",
|
|
1597
|
+
* name: "VIP Pass",
|
|
1598
|
+
* price: undefined,
|
|
1599
|
+
* })
|
|
1600
|
+
* .then((result) => {
|
|
1601
|
+
* expect(result.success).toBeTrue();
|
|
1602
|
+
* if (result.success) {
|
|
1603
|
+
* expect(result.data.outputs.assetId).toBe("9876543210");
|
|
1604
|
+
* }
|
|
1605
|
+
* });
|
|
1606
|
+
* ```
|
|
1607
|
+
*/
|
|
1608
|
+
interface ResourceDriver<K extends ResourceKind> {
|
|
1609
|
+
/**
|
|
1610
|
+
* Create the resource upstream from its desired state and return the
|
|
1611
|
+
* resulting current state (desired fields + Roblox-assigned outputs).
|
|
1612
|
+
*/
|
|
1613
|
+
create(desired: Extract<ResourceDesiredState, {
|
|
1614
|
+
kind: K;
|
|
1615
|
+
}>): Promise<Result$1<ResourceCurrentState<K>, OpenCloudError$1>>;
|
|
1616
|
+
/**
|
|
1617
|
+
* Reconcile an upstream resource whose managed content has drifted from its
|
|
1618
|
+
* desired state. Receives the last-known current state so the driver can
|
|
1619
|
+
* compute a minimal patch (or no-op upstream, for file-backed kinds where
|
|
1620
|
+
* republishing is unconditional).
|
|
1621
|
+
*
|
|
1622
|
+
* Optional. Drivers whose upstream API has no update operation omit this
|
|
1623
|
+
* method; `applyOps` surfaces an `updateUnsupported` error at dispatch time
|
|
1624
|
+
* instead.
|
|
1625
|
+
*/
|
|
1626
|
+
update?(current: ResourceCurrentState<K>, desired: Extract<ResourceDesiredState, {
|
|
1627
|
+
kind: K;
|
|
1628
|
+
}>): Promise<Result$1<ResourceCurrentState<K>, OpenCloudError$1>>;
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Polymorphic dispatch table keyed by {@link ResourceKind}, mapping each kind
|
|
1632
|
+
* to the {@link ResourceDriver} that handles it. `applyOps` indexes the
|
|
1633
|
+
* registry by `op.desired.kind` to reach the matching driver with full type
|
|
1634
|
+
* safety: adding a new kind to `ResourceDesiredState` is a compile error until
|
|
1635
|
+
* a matching registry entry is supplied.
|
|
1636
|
+
*
|
|
1637
|
+
* @example
|
|
1638
|
+
*
|
|
1639
|
+
* ```ts
|
|
1640
|
+
* import { OpenCloudError, type DriverRegistry } from "@bedrock-rbx/core";
|
|
1641
|
+
*
|
|
1642
|
+
* const registry: DriverRegistry = {
|
|
1643
|
+
* gamePass: {
|
|
1644
|
+
* async create() {
|
|
1645
|
+
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1646
|
+
* },
|
|
1647
|
+
* },
|
|
1648
|
+
* place: {
|
|
1649
|
+
* async create() {
|
|
1650
|
+
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1651
|
+
* },
|
|
1652
|
+
* },
|
|
1653
|
+
* universe: {
|
|
1654
|
+
* async create() {
|
|
1655
|
+
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1656
|
+
* },
|
|
1657
|
+
* },
|
|
1658
|
+
* developerProduct: {
|
|
1659
|
+
* async create() {
|
|
1660
|
+
* return { err: new OpenCloudError("not implemented"), success: false };
|
|
1661
|
+
* },
|
|
1662
|
+
* },
|
|
1663
|
+
* };
|
|
1664
|
+
*
|
|
1665
|
+
* expect(registry.gamePass).toBeObject();
|
|
1666
|
+
* ```
|
|
1667
|
+
*/
|
|
1668
|
+
type DriverRegistry = { [K in ResourceKind]: ResourceDriver<K> };
|
|
1669
|
+
//#endregion
|
|
1649
1670
|
//#region src/shell/apply-ops.d.ts
|
|
1650
1671
|
/**
|
|
1672
|
+
* Optional wiring `applyOps` uses to emit per-resource and aggregate progress
|
|
1673
|
+
* events. When omitted, `applyOps` runs silently (backward-compatible with
|
|
1674
|
+
* pre-progress callers).
|
|
1675
|
+
*/
|
|
1676
|
+
interface ApplyOpsReporting {
|
|
1677
|
+
/** Environment name stamped on every emitted event. */
|
|
1678
|
+
readonly environment: string;
|
|
1679
|
+
/** Sink the apply pipeline pushes events into. */
|
|
1680
|
+
readonly progress: ProgressPort;
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1651
1683
|
* Failure surfaced by `applyOps` when an operation cannot be applied.
|
|
1652
1684
|
* 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.
|
|
1685
|
+
* One `ApplyError` describes one failing op; the surrounding
|
|
1686
|
+
* `AggregateApplyError` carries the full batch outcome (every survivor and
|
|
1687
|
+
* every failure).
|
|
1658
1688
|
*
|
|
1659
1689
|
* @example
|
|
1660
1690
|
*
|
|
@@ -1666,6 +1696,9 @@ type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
|
1666
1696
|
* case "driverFailure": {
|
|
1667
1697
|
* return `driver failed for ${err.key}: ${err.cause.message}`;
|
|
1668
1698
|
* }
|
|
1699
|
+
* case "unexpectedThrow": {
|
|
1700
|
+
* return `unexpected error for ${err.key}`;
|
|
1701
|
+
* }
|
|
1669
1702
|
* case "updateUnsupported": {
|
|
1670
1703
|
* return `update not supported for ${err.key}`;
|
|
1671
1704
|
* }
|
|
@@ -1674,134 +1707,297 @@ type Operation = CreateOperation | NoopOperation | UpdateOperation;
|
|
|
1674
1707
|
*
|
|
1675
1708
|
* const err: ApplyError = {
|
|
1676
1709
|
* key: asResourceKey("vip-pass"),
|
|
1677
|
-
* appliedSoFar: [],
|
|
1678
1710
|
* kind: "updateUnsupported",
|
|
1679
1711
|
* };
|
|
1680
1712
|
*
|
|
1681
1713
|
* expect(describe(err)).toBe("update not supported for vip-pass");
|
|
1682
1714
|
* ```
|
|
1683
1715
|
*/
|
|
1684
|
-
type ApplyError = {
|
|
1685
|
-
readonly
|
|
1686
|
-
readonly
|
|
1687
|
-
readonly
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
readonly
|
|
1716
|
+
type ApplyError = {
|
|
1717
|
+
readonly cause: OpenCloudError$1;
|
|
1718
|
+
readonly key: ResourceKey;
|
|
1719
|
+
readonly kind: "driverFailure";
|
|
1720
|
+
} | {
|
|
1721
|
+
readonly cause: unknown;
|
|
1722
|
+
readonly key: ResourceKey;
|
|
1723
|
+
readonly kind: "unexpectedThrow";
|
|
1724
|
+
} | {
|
|
1725
|
+
readonly key: ResourceKey;
|
|
1726
|
+
readonly kind: "updateUnsupported";
|
|
1727
|
+
};
|
|
1728
|
+
/**
|
|
1729
|
+
* Aggregate outcome returned by `applyOps` when one or more ops fail.
|
|
1730
|
+
* `applied` is the survivor set in Phase 1 then Phase 2 input order.
|
|
1731
|
+
* `failures` is the non-empty list of `ApplyError`s, one per failing op,
|
|
1732
|
+
* grouped the same way.
|
|
1733
|
+
*
|
|
1734
|
+
* @example
|
|
1735
|
+
*
|
|
1736
|
+
* ```ts
|
|
1737
|
+
* import { asResourceKey, type AggregateApplyError } from "@bedrock-rbx/core";
|
|
1738
|
+
*
|
|
1739
|
+
* function summarize(err: AggregateApplyError): string {
|
|
1740
|
+
* return `${err.applied.length} survived, ${err.failures.length} failed`;
|
|
1741
|
+
* }
|
|
1742
|
+
*
|
|
1743
|
+
* const err: AggregateApplyError = {
|
|
1744
|
+
* applied: [],
|
|
1745
|
+
* failures: [{ key: asResourceKey("vip-pass"), kind: "updateUnsupported" }],
|
|
1746
|
+
* };
|
|
1747
|
+
*
|
|
1748
|
+
* expect(summarize(err)).toBe("0 survived, 1 failed");
|
|
1749
|
+
* ```
|
|
1750
|
+
*/
|
|
1751
|
+
interface AggregateApplyError {
|
|
1752
|
+
/** Survivors persisted to state, in Phase 1 then Phase 2 input order. */
|
|
1753
|
+
readonly applied: ReadonlyArray<ResourceCurrentState>;
|
|
1754
|
+
/** Per-op failures, at least one, in Phase 1 then Phase 2 input order. */
|
|
1755
|
+
readonly failures: readonly [ApplyError, ...ReadonlyArray<ApplyError>];
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Dispatch reconciliation operations to their matching drivers in two phases
|
|
1759
|
+
* with continue-on-failure semantics. Phase 1 runs universe ops sequentially
|
|
1760
|
+
* (singleton per environment; sequencing it before everything else avoids the
|
|
1761
|
+
* `displayName` race against the root `Place`). Phase 2 dispatches every
|
|
1762
|
+
* remaining non-noop op concurrently via `Promise.all`; every op is
|
|
1763
|
+
* attempted regardless of earlier failures.
|
|
1764
|
+
*
|
|
1765
|
+
* Behaviour:
|
|
1766
|
+
* - `create` operations route to `registry[op.desired.kind].create`.
|
|
1767
|
+
* - `update` operations route to `registry[op.desired.kind].update` when the
|
|
1768
|
+
* driver exposes it; otherwise they yield an `updateUnsupported`
|
|
1769
|
+
* `ApplyError` without invoking the driver.
|
|
1770
|
+
* - `noop` operations are skipped entirely (no I/O, no dispatch).
|
|
1771
|
+
* - A driver that throws outside its `Result` contract is caught at the
|
|
1772
|
+
* dispatch boundary and translated to an `unexpectedThrow` `ApplyError`
|
|
1773
|
+
* scoped to that op alone; the rest of the batch keeps running.
|
|
1774
|
+
*
|
|
1775
|
+
* On Ok the returned array carries driver outputs for every non-noop op
|
|
1776
|
+
* in phase order: Phase 1 universe entries first, then Phase 2 entries in
|
|
1777
|
+
* their input order. Noops are not represented; callers needing a full
|
|
1778
|
+
* post-apply snapshot merge with the pre-apply current state keyed by
|
|
1779
|
+
* `ResourceKey`.
|
|
1780
|
+
*
|
|
1781
|
+
* On Err the aggregate carries every survivor in `applied` (Phase 1 first,
|
|
1782
|
+
* then Phase 2 input order) and every failure in `failures` with the same
|
|
1783
|
+
* grouping. Neither array reflects completion order.
|
|
1784
|
+
*
|
|
1785
|
+
* @param ops - Reconciliation operations produced by `diff`, applied in
|
|
1786
|
+
* declaration order.
|
|
1787
|
+
* @param registry - Per-kind driver table; dispatch uses `op.desired.kind`
|
|
1788
|
+
* as the index.
|
|
1789
|
+
* @param reporting - Optional progress wiring. When supplied, `applyOps`
|
|
1790
|
+
* emits one `resourceOpStarted` and one terminal event per non-noop op,
|
|
1791
|
+
* one `resourceOpNoop` per noop op, and a final `applySummary` carrying
|
|
1792
|
+
* the per-type counts and the wall-clock apply duration. When omitted,
|
|
1793
|
+
* no events fire.
|
|
1794
|
+
* @returns `Ok(state)` when every op succeeded; otherwise
|
|
1795
|
+
* `Err(AggregateApplyError)` with the survivors and the non-empty
|
|
1796
|
+
* failures tuple.
|
|
1797
|
+
* @example
|
|
1798
|
+
*
|
|
1799
|
+
* ```ts
|
|
1800
|
+
* import { applyOps, type DriverRegistry } from "@bedrock-rbx/core";
|
|
1801
|
+
*
|
|
1802
|
+
* const noopRegistry: DriverRegistry = {
|
|
1803
|
+
* developerProduct: { create: async () => ({ err: new Error("stub") as never, success: false }) },
|
|
1804
|
+
* gamePass: { create: async () => ({ err: new Error("stub") as never, success: false }) },
|
|
1805
|
+
* place: { create: async () => ({ err: new Error("stub") as never, success: false }) },
|
|
1806
|
+
* universe: { create: async () => ({ err: new Error("stub") as never, success: false }) },
|
|
1807
|
+
* };
|
|
1808
|
+
*
|
|
1809
|
+
* return applyOps([], noopRegistry).then((result) => {
|
|
1810
|
+
* expect(result).toStrictEqual({ data: [], success: true });
|
|
1811
|
+
* });
|
|
1812
|
+
* ```
|
|
1813
|
+
*/
|
|
1814
|
+
declare function applyOps(ops: ReadonlyArray<Operation>, registry: DriverRegistry, reporting?: ApplyOpsReporting): Promise<Result$1<ReadonlyArray<ResourceCurrentState>, AggregateApplyError>>;
|
|
1815
|
+
//#endregion
|
|
1816
|
+
//#region src/ports/progress-port.d.ts
|
|
1817
|
+
/**
|
|
1818
|
+
* Per-environment outcome event emitted after a deploy completes
|
|
1819
|
+
* successfully. Carries the environment name and the count of resources
|
|
1820
|
+
* present in the persisted state snapshot.
|
|
1821
|
+
*/
|
|
1822
|
+
interface DeploySuccessEvent {
|
|
1823
|
+
/** The environment that finished reconciling. */
|
|
1824
|
+
readonly environment: string;
|
|
1825
|
+
/** Discriminator tag. */
|
|
1826
|
+
readonly kind: "deploySuccess";
|
|
1827
|
+
/** Number of resources in the post-deploy state snapshot. */
|
|
1828
|
+
readonly resourceCount: number;
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Per-environment outcome event emitted when a deploy fails. Carries the
|
|
1832
|
+
* environment name and the full {@link DeployError} so a renderer can
|
|
1833
|
+
* delegate to the existing diagnostic helpers.
|
|
1834
|
+
*/
|
|
1835
|
+
interface DeployFailureEvent {
|
|
1836
|
+
/** The environment whose deploy failed. */
|
|
1837
|
+
readonly environment: string;
|
|
1838
|
+
/** Stage-tagged failure reason returned by the shell `deploy` function. */
|
|
1839
|
+
readonly error: DeployError;
|
|
1840
|
+
/** Discriminator tag. */
|
|
1841
|
+
readonly kind: "deployFailure";
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Per-resource event emitted immediately before `applyOps` dispatches a
|
|
1845
|
+
* non-noop op to its driver. Adapters may render a "starting" line or
|
|
1846
|
+
* stay silent; the matching terminal event ({@link ResourceOpSucceededEvent}
|
|
1847
|
+
* or {@link ResourceOpFailedEvent}) fires when the driver settles.
|
|
1848
|
+
*/
|
|
1849
|
+
interface ResourceOpStartedEvent {
|
|
1850
|
+
/** User-supplied resource key. */
|
|
1851
|
+
readonly key: ResourceKey;
|
|
1852
|
+
/** Environment whose reconcile is running. */
|
|
1853
|
+
readonly environment: string;
|
|
1854
|
+
/** Discriminator tag. */
|
|
1855
|
+
readonly kind: "resourceOpStarted";
|
|
1856
|
+
/** Operation type being dispatched. Noops never fire this event. */
|
|
1857
|
+
readonly opType: "create" | "update";
|
|
1858
|
+
/** Resource-kind discriminator (`gamePass`, `place`, ...). */
|
|
1859
|
+
readonly resourceKind: ResourceKind;
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Terminal event for a successful create op. The `resourceKind` discriminator
|
|
1863
|
+
* narrows `outputs` to the matching `ResourceOutputs<K>` shape so renderers
|
|
1864
|
+
* can read Roblox-assigned IDs without casts.
|
|
1865
|
+
*/
|
|
1866
|
+
type ResourceOpSucceededCreateEvent = { [K in ResourceKind]: Readonly<{
|
|
1867
|
+
environment: string;
|
|
1868
|
+
key: ResourceKey;
|
|
1869
|
+
kind: "resourceOpSucceeded";
|
|
1870
|
+
opType: "create";
|
|
1871
|
+
outputs: ResourceOutputs<K>;
|
|
1872
|
+
resourceKind: K;
|
|
1873
|
+
}> }[ResourceKind];
|
|
1874
|
+
/**
|
|
1875
|
+
* Terminal event for a successful update op. Carries the list of top-level
|
|
1876
|
+
* fields the diff flagged as changed so renderers can attribute the update.
|
|
1877
|
+
*/
|
|
1878
|
+
interface ResourceOpSucceededUpdateEvent {
|
|
1879
|
+
/** User-supplied resource key. */
|
|
1880
|
+
readonly key: ResourceKey;
|
|
1881
|
+
/** Top-level field names whose values differed between desired and current. */
|
|
1882
|
+
readonly changedFields: ReadonlyArray<string>;
|
|
1883
|
+
/** Environment whose reconcile is running. */
|
|
1884
|
+
readonly environment: string;
|
|
1885
|
+
/** Discriminator tag. */
|
|
1886
|
+
readonly kind: "resourceOpSucceeded";
|
|
1887
|
+
/** Operation type. */
|
|
1888
|
+
readonly opType: "update";
|
|
1889
|
+
/** Resource-kind discriminator. */
|
|
1890
|
+
readonly resourceKind: ResourceKind;
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Terminal event for a successful non-noop op. Sub-discriminated by `opType`
|
|
1894
|
+
* so a renderer can extract `outputs` (creates) or `changedFields` (updates)
|
|
1895
|
+
* without losing type narrowing.
|
|
1896
|
+
*/
|
|
1897
|
+
type ResourceOpSucceededEvent = ResourceOpSucceededCreateEvent | ResourceOpSucceededUpdateEvent;
|
|
1898
|
+
/**
|
|
1899
|
+
* Per-resource event emitted for each op the diff produced as a noop.
|
|
1900
|
+
* Noops never fire a `started`/terminal pair; this single event stands in
|
|
1901
|
+
* for the entire op so adapters can render a "unchanged" line.
|
|
1902
|
+
*/
|
|
1903
|
+
interface ResourceOpNoopEvent {
|
|
1904
|
+
/** User-supplied resource key. */
|
|
1905
|
+
readonly key: ResourceKey;
|
|
1906
|
+
/** Environment whose reconcile is running. */
|
|
1907
|
+
readonly environment: string;
|
|
1908
|
+
/** Discriminator tag. */
|
|
1909
|
+
readonly kind: "resourceOpNoop";
|
|
1910
|
+
/** Resource-kind discriminator. */
|
|
1911
|
+
readonly resourceKind: ResourceKind;
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Terminal event for a failed non-noop op. Carries the {@link ApplyError}
|
|
1915
|
+
* so a renderer can delegate to the existing apply-cause diagnostic helper.
|
|
1916
|
+
*/
|
|
1917
|
+
interface ResourceOpFailedEvent {
|
|
1918
|
+
/** User-supplied resource key. */
|
|
1691
1919
|
readonly key: ResourceKey;
|
|
1692
|
-
|
|
1693
|
-
|
|
1920
|
+
/** Environment whose reconcile is running. */
|
|
1921
|
+
readonly environment: string;
|
|
1922
|
+
/** Apply error returned by `dispatchOp`. */
|
|
1923
|
+
readonly error: ApplyError;
|
|
1924
|
+
/** Discriminator tag. */
|
|
1925
|
+
readonly kind: "resourceOpFailed";
|
|
1926
|
+
/** Operation type that was being attempted. */
|
|
1927
|
+
readonly opType: "create" | "update";
|
|
1928
|
+
/** Resource-kind discriminator. */
|
|
1929
|
+
readonly resourceKind: ResourceKind;
|
|
1930
|
+
}
|
|
1694
1931
|
/**
|
|
1695
|
-
*
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1932
|
+
* Aggregate footer event emitted after `applyOps` finishes (Phase 2 settled).
|
|
1933
|
+
* Fires unconditionally, including on partial failure; `durationMs` measures
|
|
1934
|
+
* apply time only (state-write time excluded).
|
|
1935
|
+
*/
|
|
1936
|
+
interface ApplySummaryEvent {
|
|
1937
|
+
/** Count of successful create ops. */
|
|
1938
|
+
readonly created: number;
|
|
1939
|
+
/** Wall-clock duration between `applyOps` entry and Phase 2 resolution, in milliseconds. */
|
|
1940
|
+
readonly durationMs: number;
|
|
1941
|
+
/** Environment whose reconcile is running. */
|
|
1942
|
+
readonly environment: string;
|
|
1943
|
+
/** Count of failed ops (any opType). */
|
|
1944
|
+
readonly failed: number;
|
|
1945
|
+
/** Discriminator tag. */
|
|
1946
|
+
readonly kind: "applySummary";
|
|
1947
|
+
/** Count of noop ops. */
|
|
1948
|
+
readonly noop: number;
|
|
1949
|
+
/** Count of successful update ops. */
|
|
1950
|
+
readonly updated: number;
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Per-environment event emitted after `statePort.write` returns `Ok`.
|
|
1954
|
+
* Not emitted on write failure: the existing `deployFailure` event with
|
|
1955
|
+
* `kind: "stateWriteFailed"` runs the existing failure flow. The payload
|
|
1956
|
+
* carries no backend identity; renderers read the backend label from the
|
|
1957
|
+
* project config when constructing the rendered line.
|
|
1958
|
+
*/
|
|
1959
|
+
interface StateWrittenEvent {
|
|
1960
|
+
/** Environment whose state snapshot was just persisted. */
|
|
1961
|
+
readonly environment: string;
|
|
1962
|
+
/** Discriminator tag. */
|
|
1963
|
+
readonly kind: "stateWritten";
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Discriminated union of progress events the CLI emits while a deploy
|
|
1967
|
+
* runs. The variant set is additive: future per-stage and per-resource
|
|
1968
|
+
* events land as new `kind` values without breaking existing adapters.
|
|
1969
|
+
*/
|
|
1970
|
+
type ProgressEvent = ApplySummaryEvent | DeployFailureEvent | DeploySuccessEvent | ResourceOpFailedEvent | ResourceOpNoopEvent | ResourceOpStartedEvent | ResourceOpSucceededEvent | StateWrittenEvent;
|
|
1971
|
+
/**
|
|
1972
|
+
* Plugin contract for receiving deploy outcomes: the interface an adapter
|
|
1973
|
+
* (clack renderer, JSON logger, custom UI) implements to let the CLI hand
|
|
1974
|
+
* off events without re-implementing rendering logic.
|
|
1706
1975
|
*
|
|
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`.
|
|
1976
|
+
* `ProgressPort` is a *driven* (secondary) port in hexagonal terms.
|
|
1711
1977
|
*
|
|
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
1978
|
* @example
|
|
1723
1979
|
*
|
|
1724
1980
|
* ```ts
|
|
1725
|
-
* import {
|
|
1726
|
-
* applyOps,
|
|
1727
|
-
* asResourceKey,
|
|
1728
|
-
* asRobloxAssetId,
|
|
1729
|
-
* asSha256Hex,
|
|
1730
|
-
* type DriverRegistry,
|
|
1731
|
-
* type Operation,
|
|
1732
|
-
* } from "@bedrock-rbx/core";
|
|
1981
|
+
* import type { ProgressEvent, ProgressPort } from "@bedrock-rbx/core";
|
|
1733
1982
|
*
|
|
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
|
-
* },
|
|
1983
|
+
* let received: ReadonlyArray<ProgressEvent> = [];
|
|
1984
|
+
* const port: ProgressPort = {
|
|
1985
|
+
* emit(event) {
|
|
1986
|
+
* received = [...received, event];
|
|
1775
1987
|
* },
|
|
1776
1988
|
* };
|
|
1777
1989
|
*
|
|
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
|
-
* ];
|
|
1990
|
+
* port.emit({ environment: "production", kind: "deploySuccess", resourceCount: 3 });
|
|
1797
1991
|
*
|
|
1798
|
-
*
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
* });
|
|
1992
|
+
* expect(received).toEqual([
|
|
1993
|
+
* { environment: "production", kind: "deploySuccess", resourceCount: 3 },
|
|
1994
|
+
* ]);
|
|
1802
1995
|
* ```
|
|
1803
1996
|
*/
|
|
1804
|
-
|
|
1997
|
+
interface ProgressPort {
|
|
1998
|
+
/** Hand a single progress event to the adapter for rendering or logging. */
|
|
1999
|
+
emit(event: ProgressEvent): void;
|
|
2000
|
+
}
|
|
1805
2001
|
//#endregion
|
|
1806
2002
|
//#region src/shell/build-default-registry.d.ts
|
|
1807
2003
|
/**
|
|
@@ -2086,16 +2282,21 @@ declare function flattenConfig(config: ResolvedConfig): ReadonlyArray<ResourceDe
|
|
|
2086
2282
|
//#endregion
|
|
2087
2283
|
//#region src/core/kinds/module.d.ts
|
|
2088
2284
|
/**
|
|
2089
|
-
* Failure surfaced
|
|
2285
|
+
* Failure surfaced before `diff` runs. Three variants today:
|
|
2090
2286
|
*
|
|
2091
2287
|
* - `fileReadFailed`: a kind module's `normalize` could not read a file
|
|
2092
2288
|
* the input declared (e.g. An icon path that is missing on disk).
|
|
2093
|
-
* - `iconRemovalRejected`:
|
|
2094
|
-
*
|
|
2095
|
-
*
|
|
2289
|
+
* - `iconRemovalRejected`: a kind's prior current state recorded an icon
|
|
2290
|
+
* that the desired state no longer declares, and the kind has no
|
|
2291
|
+
* documented unset path on the upstream API.
|
|
2292
|
+
* - `redactedNameCollision`: two developer-products in the same batch
|
|
2293
|
+
* resolve to the same wire `name`. The upstream Roblox API enforces
|
|
2294
|
+
* per-universe uniqueness on developer-product names and would reject
|
|
2295
|
+
* the second PATCH with `DuplicateProductName`.
|
|
2096
2296
|
*
|
|
2097
|
-
*
|
|
2098
|
-
* failure to
|
|
2297
|
+
* The single-resource variants carry the offending `key` so the CLI can
|
|
2298
|
+
* attribute the failure to one entry; `redactedNameCollision` carries
|
|
2299
|
+
* both colliding keys and the resolved name.
|
|
2099
2300
|
*
|
|
2100
2301
|
* @example
|
|
2101
2302
|
*
|
|
@@ -2121,6 +2322,11 @@ type BuildDesiredError = {
|
|
|
2121
2322
|
/** ResourceKey of the entry whose icon is being removed. */readonly key: ResourceKey; /** Literal discriminator for narrowing. */
|
|
2122
2323
|
readonly kind: "iconRemovalRejected"; /** Human-readable explanation naming the resource and the invariant. */
|
|
2123
2324
|
readonly message: string;
|
|
2325
|
+
} | {
|
|
2326
|
+
/** The two developer-product keys whose desired `name` resolves to the same string. */readonly keys: readonly [ResourceKey, ResourceKey]; /** Literal discriminator for narrowing. */
|
|
2327
|
+
readonly kind: "redactedNameCollision"; /** Human-readable explanation naming both keys and the override remedy. */
|
|
2328
|
+
readonly message: string; /** The wire `name` value both products resolve to. */
|
|
2329
|
+
readonly resolvedName: string;
|
|
2124
2330
|
};
|
|
2125
2331
|
/**
|
|
2126
2332
|
* I/O surface the shell injects into kind-module `normalize` calls. Carries
|
|
@@ -2184,6 +2390,8 @@ interface KindIo {
|
|
|
2184
2390
|
* },
|
|
2185
2391
|
* success: true,
|
|
2186
2392
|
* }),
|
|
2393
|
+
* changedFieldsBetween: (desired, current) =>
|
|
2394
|
+
* desired.name === current.name ? [] : ["name"],
|
|
2187
2395
|
* fieldsEqual: (desired, current) => desired.name === current.name,
|
|
2188
2396
|
* };
|
|
2189
2397
|
*
|
|
@@ -2192,14 +2400,22 @@ interface KindIo {
|
|
|
2192
2400
|
*/
|
|
2193
2401
|
interface ResourceKindModule<K extends ResourceKind> {
|
|
2194
2402
|
/**
|
|
2195
|
-
* Optional
|
|
2196
|
-
*
|
|
2403
|
+
* Optional reconcilability invariant invoked for every `(kind, key)` pair
|
|
2404
|
+
* that exists on both sides before `diff` runs. Surfaces kind-specific
|
|
2197
2405
|
* rejections (e.g. Removing a developer-product icon, which the upstream
|
|
2198
|
-
* API has no documented unset path for) before
|
|
2199
|
-
*
|
|
2200
|
-
* invariants omit this hook.
|
|
2406
|
+
* API has no documented unset path for) before any apply-side driver I/O
|
|
2407
|
+
* is attempted. Kinds without such invariants omit this hook.
|
|
2201
2408
|
*/
|
|
2202
2409
|
readonly assertReconcilable?: (current: ResourceCurrentState<K>, desired: DesiredFor<K>) => Result$1<undefined, BuildDesiredError>;
|
|
2410
|
+
/**
|
|
2411
|
+
* Top-level field names that differ between `desired` and `current`,
|
|
2412
|
+
* in a kind-specific deterministic order. Mirrors `fieldsEqual`
|
|
2413
|
+
* semantics, so `fieldsEqual(d, c)` is `true` iff this returns `[]`.
|
|
2414
|
+
* Granularity is top-level: `discordSocialLink`, not
|
|
2415
|
+
* `discordSocialLink.uri`. Preview and apply renderers consume this as
|
|
2416
|
+
* the single source of truth for "what changed" on an update op.
|
|
2417
|
+
*/
|
|
2418
|
+
changedFieldsBetween(desired: DesiredFor<K>, current: ResourceCurrentState<K>): ReadonlyArray<string>;
|
|
2203
2419
|
/** ArkType schema for the authored entry body of this kind. */
|
|
2204
2420
|
readonly entrySchema: Type$1<ResourceEntryByKind[K]>;
|
|
2205
2421
|
/**
|
|
@@ -2370,7 +2586,7 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<Result$1<Confi
|
|
|
2370
2586
|
/**
|
|
2371
2587
|
* Inputs for `deploy`. Every field except `environment` is optional;
|
|
2372
2588
|
* omitted dependencies are default-constructed from the project config
|
|
2373
|
-
* and the environment variables `
|
|
2589
|
+
* and the environment variables `BEDROCK_GITHUB_TOKEN` and `BEDROCK_API_KEY`.
|
|
2374
2590
|
*/
|
|
2375
2591
|
interface DeployOptions {
|
|
2376
2592
|
/** Pre-loaded, optionally-mutated project config. Omit to call `loadConfig()` automatically. */
|
|
@@ -2383,11 +2599,18 @@ interface DeployOptions {
|
|
|
2383
2599
|
readonly getEnv?: (name: string) => string | undefined;
|
|
2384
2600
|
/** Loader invoked when `config` is omitted; defaults to `loadConfig` from this package. */
|
|
2385
2601
|
readonly loadConfig?: (options?: LoadConfigOptions) => Promise<Result$1<Config, ConfigError>>;
|
|
2602
|
+
/**
|
|
2603
|
+
* Optional sink for per-resource and aggregate progress events. When
|
|
2604
|
+
* supplied, `applyOps` emits one started/terminal pair per non-noop op
|
|
2605
|
+
* (plus per-noop and summary events), and `deploy` emits `stateWritten`
|
|
2606
|
+
* after a successful state-write. Omit to run silently.
|
|
2607
|
+
*/
|
|
2608
|
+
readonly progress?: ProgressPort;
|
|
2386
2609
|
/** Reads file bytes for resources that have file-backed inputs. Defaults to `node:fs/promises.readFile`. */
|
|
2387
2610
|
readonly readFile?: (path: string) => Promise<Uint8Array>;
|
|
2388
2611
|
/** Per-kind driver table consulted for create / update dispatch. Default-constructed from `BEDROCK_API_KEY` when omitted. */
|
|
2389
2612
|
readonly registry?: DriverRegistry;
|
|
2390
|
-
/** Backend used to read the prior snapshot and persist the new one. Default-constructed from `config.state` and `
|
|
2613
|
+
/** Backend used to read the prior snapshot and persist the new one. Default-constructed from `config.state` and `BEDROCK_GITHUB_TOKEN` when omitted. */
|
|
2391
2614
|
readonly statePort?: StatePort;
|
|
2392
2615
|
}
|
|
2393
2616
|
/**
|
|
@@ -2399,7 +2622,7 @@ interface DeployOptions {
|
|
|
2399
2622
|
* `unsupportedBackend`, `registryConfigMissing`).
|
|
2400
2623
|
*/
|
|
2401
2624
|
type DeployError = IncompletePassEntryError | IncompletePlaceEntryError | IncompleteUniverseEntryError | MissingCredentialError | RegistryConfigError | StateNotConfiguredError | UnknownEnvironmentError | UnsupportedBackendError | {
|
|
2402
|
-
readonly cause:
|
|
2625
|
+
readonly cause: AggregateApplyError;
|
|
2403
2626
|
readonly kind: "applyFailed";
|
|
2404
2627
|
} | {
|
|
2405
2628
|
readonly cause: BuildDesiredError;
|
|
@@ -2417,9 +2640,13 @@ type DeployError = IncompletePassEntryError | IncompletePlaceEntryError | Incomp
|
|
|
2417
2640
|
};
|
|
2418
2641
|
/**
|
|
2419
2642
|
* Run a full reconcile end-to-end. Default-constructs missing deps from
|
|
2420
|
-
* the project config and the environment variables `
|
|
2421
|
-
* `BEDROCK_API_KEY`;
|
|
2422
|
-
*
|
|
2643
|
+
* the project config and the environment variables `BEDROCK_GITHUB_TOKEN`
|
|
2644
|
+
* and `BEDROCK_API_KEY`; emits a terminal `deploySuccess` or `deployFailure`
|
|
2645
|
+
* event through the resolved `progress` port. When `progress` is omitted,
|
|
2646
|
+
* the default port comes from `BEDROCK_CLI`: a non-empty value selects the
|
|
2647
|
+
* clack-backed adapter, any other reading selects the no-op adapter. No
|
|
2648
|
+
* environment lookups happen when `statePort`, `registry`, `config`, and
|
|
2649
|
+
* `progress` are all supplied explicitly.
|
|
2423
2650
|
*
|
|
2424
2651
|
* @param options - Target environment plus optional overrides.
|
|
2425
2652
|
* @returns The persisted `BedrockState` on success, or a stage-tagged
|
|
@@ -2481,6 +2708,167 @@ type DeployError = IncompletePassEntryError | IncompletePlaceEntryError | Incomp
|
|
|
2481
2708
|
*/
|
|
2482
2709
|
declare function deploy(options: DeployOptions): Promise<Result$1<BedrockState, DeployError>>;
|
|
2483
2710
|
//#endregion
|
|
2711
|
+
//#region src/cli/spawner.d.ts
|
|
2712
|
+
/**
|
|
2713
|
+
* Concrete invocation passed to {@link Spawner.spawn}: the executable name,
|
|
2714
|
+
* its argv, and an env-var override map layered on top of the host process
|
|
2715
|
+
* environment. The adapter is responsible for merging `envOverrides` with
|
|
2716
|
+
* `process.env` so overrides win on collisions.
|
|
2717
|
+
*/
|
|
2718
|
+
interface SpawnInvocation {
|
|
2719
|
+
/** Argv to pass to the spawned executable, excluding the command itself. */
|
|
2720
|
+
readonly args: ReadonlyArray<string>;
|
|
2721
|
+
/** Executable name to spawn (e.g. `"bun"`). */
|
|
2722
|
+
readonly command: string;
|
|
2723
|
+
/** Env-var entries that should overlay the host process environment. */
|
|
2724
|
+
readonly envOverrides: Readonly<Record<string, string>>;
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Structural shape of the underlying error a {@link Spawner} surfaces when
|
|
2728
|
+
* a child cannot be launched. Mirrors the subset of Node's `ErrnoException`
|
|
2729
|
+
* the dispatcher and downstream renderers care about, without requiring
|
|
2730
|
+
* consumers to install `@types/node` to consume the public surface.
|
|
2731
|
+
*/
|
|
2732
|
+
interface SpawnLaunchCause extends Error {
|
|
2733
|
+
/** Errno code like `"ENOENT"`, `"EACCES"`, or `undefined` when the error did not carry one. */
|
|
2734
|
+
readonly code?: string | undefined;
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Failure surfaced by {@link Spawner.spawn} when the child process could not
|
|
2738
|
+
* be started at all (e.g. `ENOENT` for a missing executable). Carries the
|
|
2739
|
+
* underlying error so callers can render a precise diagnostic.
|
|
2740
|
+
*/
|
|
2741
|
+
interface SpawnLaunchError {
|
|
2742
|
+
/** Underlying error from the spawn attempt; structurally an `Error` with an optional errno `code`. */
|
|
2743
|
+
readonly cause: SpawnLaunchCause;
|
|
2744
|
+
/** Discriminator tag. */
|
|
2745
|
+
readonly kind: "launchFailed";
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Plugin contract for spawning a child process: the interface an adapter
|
|
2749
|
+
* (a `node:child_process` wrapper, a fake recorder in tests) implements to
|
|
2750
|
+
* let the CLI dispatch override scripts without binding to a concrete
|
|
2751
|
+
* process API.
|
|
2752
|
+
*
|
|
2753
|
+
* `Spawner` is a *driven* (secondary) port; the dispatcher drives the
|
|
2754
|
+
* spawner to perform a launch. Stdio is always inherited from the parent
|
|
2755
|
+
* process so the spawned script's output appears within the CLI's frame
|
|
2756
|
+
* in chronological order.
|
|
2757
|
+
*
|
|
2758
|
+
* @example
|
|
2759
|
+
*
|
|
2760
|
+
* ```ts
|
|
2761
|
+
* import type { Spawner, SpawnInvocation } from "@bedrock-rbx/core";
|
|
2762
|
+
*
|
|
2763
|
+
* const invocations: Array<SpawnInvocation> = [];
|
|
2764
|
+
* const spawner: Spawner = {
|
|
2765
|
+
* async spawn(invocation) {
|
|
2766
|
+
* invocations.push(invocation);
|
|
2767
|
+
* return { data: 0, success: true };
|
|
2768
|
+
* },
|
|
2769
|
+
* };
|
|
2770
|
+
*
|
|
2771
|
+
* return spawner
|
|
2772
|
+
* .spawn({ args: ["--env", "production"], command: "bun", envOverrides: {} })
|
|
2773
|
+
* .then((result) => {
|
|
2774
|
+
* expect(result.success).toBeTrue();
|
|
2775
|
+
* expect(invocations).toHaveLength(1);
|
|
2776
|
+
* });
|
|
2777
|
+
* ```
|
|
2778
|
+
*/
|
|
2779
|
+
interface Spawner {
|
|
2780
|
+
/**
|
|
2781
|
+
* Launch the supplied invocation and resolve once the child closes.
|
|
2782
|
+
*
|
|
2783
|
+
* - `Ok(exitCode)` carries the child's numeric exit code (including 0).
|
|
2784
|
+
* - `Err(launchFailed)` covers cases where the child could not be
|
|
2785
|
+
* started at all (missing executable, permission denied, signal'd
|
|
2786
|
+
* before exit).
|
|
2787
|
+
*/
|
|
2788
|
+
spawn(invocation: SpawnInvocation): Promise<Result$1<number, SpawnLaunchError>>;
|
|
2789
|
+
}
|
|
2790
|
+
//#endregion
|
|
2791
|
+
//#region src/cli/dispatch-override.d.ts
|
|
2792
|
+
/**
|
|
2793
|
+
* Parsed deploy arguments forwarded to a `.bedrock/<command>.ts` override
|
|
2794
|
+
* script. Credential flags are translated into env-var overrides by the
|
|
2795
|
+
* dispatcher so secrets never reach the child's argv.
|
|
2796
|
+
*/
|
|
2797
|
+
interface OverrideInvocation {
|
|
2798
|
+
/** Optional `--api-key` value; translated to `BEDROCK_API_KEY` in env. */
|
|
2799
|
+
readonly apiKey?: string;
|
|
2800
|
+
/** Optional `--config <path>` value; forwarded unchanged in argv when present. */
|
|
2801
|
+
readonly configFile?: string;
|
|
2802
|
+
/** Target environment for this single override invocation. */
|
|
2803
|
+
readonly environment: string;
|
|
2804
|
+
/** Optional `--github-token` value; translated to `BEDROCK_GITHUB_TOKEN` in env. */
|
|
2805
|
+
readonly githubToken?: string;
|
|
2806
|
+
/** Path to the override script file to invoke. */
|
|
2807
|
+
readonly overridePath: string;
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Failure modes returned by {@link dispatchOverride}.
|
|
2811
|
+
*
|
|
2812
|
+
* - `launchFailed` — the child process could not be started (e.g. `bun`
|
|
2813
|
+
* missing, permission denied). Wraps the {@link SpawnLaunchCause} the
|
|
2814
|
+
* underlying spawner surfaced so callers can render a precise diagnostic.
|
|
2815
|
+
* - `nonZeroExit` — the child started, ran, and exited with a non-zero
|
|
2816
|
+
* exit code. Callers should propagate `exitCode` into the CLI's own
|
|
2817
|
+
* process exit code so CI failure modes mirror the override's outcome.
|
|
2818
|
+
*/
|
|
2819
|
+
type SpawnOverrideError = {
|
|
2820
|
+
readonly cause: SpawnLaunchCause;
|
|
2821
|
+
readonly kind: "launchFailed";
|
|
2822
|
+
} | {
|
|
2823
|
+
readonly exitCode: number;
|
|
2824
|
+
readonly kind: "nonZeroExit";
|
|
2825
|
+
};
|
|
2826
|
+
/**
|
|
2827
|
+
* Dispatch a single `.bedrock/<command>.ts` override invocation through the
|
|
2828
|
+
* supplied {@link Spawner}. Encapsulates the spawn protocol:
|
|
2829
|
+
*
|
|
2830
|
+
* - argv = `[overridePath, "--env", environment]`, with `"--config", configFile`
|
|
2831
|
+
* appended when supplied.
|
|
2832
|
+
* - `apiKey` becomes the `BEDROCK_API_KEY` env-var override; `githubToken`
|
|
2833
|
+
* becomes `BEDROCK_GITHUB_TOKEN`. Neither value appears in argv.
|
|
2834
|
+
* - `BEDROCK_CLI=1` is always set in the env. The override's `deploy()`
|
|
2835
|
+
* reads this on the `getEnv` seam to default to the clack progress
|
|
2836
|
+
* adapter; absent that downstream wiring, the variable is a forward-
|
|
2837
|
+
* compatible signal a future caller can act on.
|
|
2838
|
+
*
|
|
2839
|
+
* The dispatcher itself reads no ambient state: every input arrives via the
|
|
2840
|
+
* `invocation` argument and the `Spawner` port is the only side-effect seam.
|
|
2841
|
+
*
|
|
2842
|
+
* @param invocation - Path, environment, and parsed deploy-flag inputs.
|
|
2843
|
+
* @param spawner - Port the dispatcher hands the resolved
|
|
2844
|
+
* {@link SpawnInvocation} to.
|
|
2845
|
+
* @returns `Ok(undefined)` when the child exited zero; otherwise an
|
|
2846
|
+
* {@link SpawnOverrideError} discriminating launch vs non-zero exit.
|
|
2847
|
+
*
|
|
2848
|
+
* @example
|
|
2849
|
+
*
|
|
2850
|
+
* ```ts
|
|
2851
|
+
* import { dispatchOverride, type Spawner } from "@bedrock-rbx/core";
|
|
2852
|
+
*
|
|
2853
|
+
* const spawner: Spawner = {
|
|
2854
|
+
* async spawn() {
|
|
2855
|
+
* return { data: 0, success: true };
|
|
2856
|
+
* },
|
|
2857
|
+
* };
|
|
2858
|
+
*
|
|
2859
|
+
* return dispatchOverride(
|
|
2860
|
+
* {
|
|
2861
|
+
* environment: "production",
|
|
2862
|
+
* overridePath: "/abs/.bedrock/deploy.ts",
|
|
2863
|
+
* },
|
|
2864
|
+
* spawner,
|
|
2865
|
+
* ).then((result) => {
|
|
2866
|
+
* expect(result.success).toBeTrue();
|
|
2867
|
+
* });
|
|
2868
|
+
* ```
|
|
2869
|
+
*/
|
|
2870
|
+
declare function dispatchOverride(invocation: OverrideInvocation, spawner: Spawner): Promise<Result$1<void, SpawnOverrideError>>;
|
|
2871
|
+
//#endregion
|
|
2484
2872
|
//#region src/cli/render.d.ts
|
|
2485
2873
|
/**
|
|
2486
2874
|
* Output port the CLI renders through. Mirrors the subset of `@clack/prompts`
|
|
@@ -2521,70 +2909,6 @@ interface ClackPort {
|
|
|
2521
2909
|
outro(message: string): void;
|
|
2522
2910
|
}
|
|
2523
2911
|
//#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
2912
|
//#region src/adapters/clack-progress-adapter.d.ts
|
|
2589
2913
|
/**
|
|
2590
2914
|
* Configuration for {@link createClackProgressAdapter}.
|
|
@@ -2592,12 +2916,19 @@ interface ProgressPort {
|
|
|
2592
2916
|
interface ClackProgressAdapterDeps {
|
|
2593
2917
|
/** Output port the events are rendered through. */
|
|
2594
2918
|
readonly clack: ClackPort;
|
|
2919
|
+
/**
|
|
2920
|
+
* Loaded project config (raw `Config` or env-resolved `ResolvedConfig`);
|
|
2921
|
+
* the `stateWritten` case resolves the per-environment `StateConfig`
|
|
2922
|
+
* against this to format the backend label. When omitted, `stateWritten`
|
|
2923
|
+
* renders the generic `"state"` placeholder.
|
|
2924
|
+
*/
|
|
2925
|
+
readonly config?: Config | ResolvedConfig;
|
|
2595
2926
|
}
|
|
2596
2927
|
/**
|
|
2597
2928
|
* Build a {@link ProgressPort} that renders events through a `ClackPort`.
|
|
2598
|
-
* Pattern-matches on the event `kind`:
|
|
2599
|
-
*
|
|
2600
|
-
*
|
|
2929
|
+
* Pattern-matches on the event `kind`: per-resource events render one line each,
|
|
2930
|
+
* the aggregate `applySummary` becomes the deploy footer, and `stateWritten`
|
|
2931
|
+
* names the persistence backend resolved from the loaded `Config`.
|
|
2601
2932
|
*
|
|
2602
2933
|
* @example
|
|
2603
2934
|
*
|
|
@@ -2616,12 +2947,12 @@ interface ClackProgressAdapterDeps {
|
|
|
2616
2947
|
*
|
|
2617
2948
|
* const port = createClackProgressAdapter({ clack });
|
|
2618
2949
|
*
|
|
2619
|
-
* port.emit({ environment: "production", kind: "
|
|
2950
|
+
* port.emit({ environment: "production", kind: "stateWritten" });
|
|
2620
2951
|
*
|
|
2621
|
-
* expect(lines).toEqual(["
|
|
2952
|
+
* expect(lines).toEqual(["log: State written to state"]);
|
|
2622
2953
|
* ```
|
|
2623
2954
|
*
|
|
2624
|
-
* @param deps - The clack port the adapter renders through.
|
|
2955
|
+
* @param deps - The clack port and optional config the adapter renders through.
|
|
2625
2956
|
* @returns A `ProgressPort` that renders via clack.
|
|
2626
2957
|
*/
|
|
2627
2958
|
declare function createClackProgressAdapter(deps: ClackProgressAdapterDeps): ProgressPort;
|
|
@@ -3134,6 +3465,47 @@ declare function createUniverseDriver(deps: UniverseDriverDeps): ResourceDriver<
|
|
|
3134
3465
|
*/
|
|
3135
3466
|
declare function createClackPort(): ClackPort;
|
|
3136
3467
|
//#endregion
|
|
3468
|
+
//#region src/cli/default-spawner.d.ts
|
|
3469
|
+
/**
|
|
3470
|
+
* Construct a {@link Spawner} backed by `node:child_process.spawn` with
|
|
3471
|
+
* `stdio` inherited from the parent process. The child's environment is
|
|
3472
|
+
* `process.env` overlaid with {@link SpawnInvocation.envOverrides} (overrides
|
|
3473
|
+
* win on key collision).
|
|
3474
|
+
*
|
|
3475
|
+
* - Exit codes resolve as `Ok(exitCode)` (including `0`).
|
|
3476
|
+
* - `ENOENT` and other launch-time errors resolve as `Err(launchFailed)`
|
|
3477
|
+
* with the original error in `cause` (its `code` field carries the
|
|
3478
|
+
* errno where present).
|
|
3479
|
+
* - Children terminated by signal before producing an exit code collapse
|
|
3480
|
+
* into `launchFailed` with a synthetic `Error` whose message names the
|
|
3481
|
+
* signal; a distinct variant lands the day a caller needs to act on the
|
|
3482
|
+
* difference.
|
|
3483
|
+
*
|
|
3484
|
+
* @returns A `Spawner` whose `spawn` settles once the child closes.
|
|
3485
|
+
* @example
|
|
3486
|
+
*
|
|
3487
|
+
* ```ts
|
|
3488
|
+
* import { createDefaultSpawner } from "@bedrock-rbx/core";
|
|
3489
|
+
* import process from "node:process";
|
|
3490
|
+
*
|
|
3491
|
+
* const spawner = createDefaultSpawner();
|
|
3492
|
+
*
|
|
3493
|
+
* return spawner
|
|
3494
|
+
* .spawn({
|
|
3495
|
+
* args: ["-e", "process.exit(0)"],
|
|
3496
|
+
* command: process.execPath,
|
|
3497
|
+
* envOverrides: {},
|
|
3498
|
+
* })
|
|
3499
|
+
* .then((result) => {
|
|
3500
|
+
* expect(result.success).toBeTrue();
|
|
3501
|
+
* if (result.success) {
|
|
3502
|
+
* expect(result.data).toBe(0);
|
|
3503
|
+
* }
|
|
3504
|
+
* });
|
|
3505
|
+
* ```
|
|
3506
|
+
*/
|
|
3507
|
+
declare function createDefaultSpawner(): Spawner;
|
|
3508
|
+
//#endregion
|
|
3137
3509
|
//#region src/core/derive-price-fields.d.ts
|
|
3138
3510
|
/**
|
|
3139
3511
|
* Wire-shape pricing fragment produced by {@link derivePriceFields}: the
|
|
@@ -3184,12 +3556,16 @@ declare function derivePriceFields(desired: {
|
|
|
3184
3556
|
* `update` op if any declared field differs or a `noop` op if every field
|
|
3185
3557
|
* matches.
|
|
3186
3558
|
*
|
|
3187
|
-
* Ops appear in the order their desired entries appear in the input array
|
|
3188
|
-
*
|
|
3559
|
+
* Ops appear in the order their desired entries appear in the input array.
|
|
3560
|
+
* `applyOps` regroups them into Phase 1 (universe) and Phase 2 (everything
|
|
3561
|
+
* else) when dispatching; the execution order within Phase 2 is not
|
|
3562
|
+
* guaranteed because Phase 2 dispatches concurrently. Persisted state-file
|
|
3563
|
+
* order is determined by the merge in `deploy.runReconcile` (which retains
|
|
3564
|
+
* prior-snapshot positions for unchanged keys), not by this diff output.
|
|
3189
3565
|
*
|
|
3190
3566
|
* @param desired - Declared desired state from user config, already normalized
|
|
3191
3567
|
* (file hashes computed, nullable wire values mapped to `undefined`).
|
|
3192
|
-
* @param current - Last-known
|
|
3568
|
+
* @param current - Last-known current state from the state file.
|
|
3193
3569
|
* @returns Operations to reconcile the two snapshots.
|
|
3194
3570
|
*
|
|
3195
3571
|
* @example
|
|
@@ -3249,6 +3625,11 @@ declare function derivePriceFields(desired: {
|
|
|
3249
3625
|
* const ops = diff([unchanged, drifted, fresh], current);
|
|
3250
3626
|
*
|
|
3251
3627
|
* expect(ops.map((op) => op.type)).toEqual(["noop", "update", "create"]);
|
|
3628
|
+
*
|
|
3629
|
+
* const updateOp = ops[1]!;
|
|
3630
|
+
* if (updateOp.type === "update") {
|
|
3631
|
+
* expect(updateOp.changedFields).toStrictEqual(["name"]);
|
|
3632
|
+
* }
|
|
3252
3633
|
* ```
|
|
3253
3634
|
*/
|
|
3254
3635
|
declare function diff(desired: ReadonlyArray<ResourceDesiredState>, current: ReadonlyArray<ResourceCurrentState>): ReadonlyArray<Operation>;
|
|
@@ -3475,68 +3856,6 @@ declare function serializeStateFile(state: BedrockState): string;
|
|
|
3475
3856
|
*/
|
|
3476
3857
|
declare function parseStateFile(raw: string | undefined, file: string): Result$1<BedrockState | undefined, StateError>;
|
|
3477
3858
|
//#endregion
|
|
3478
|
-
//#region src/core/validate-plan.d.ts
|
|
3479
|
-
/**
|
|
3480
|
-
* Plan-time invariant check that runs after `buildDesired` and before
|
|
3481
|
-
* `diff`. Walks paired `(kind, key)` entries and dispatches to each
|
|
3482
|
-
* kind module's optional `assertReconcilable` hook so kind-specific
|
|
3483
|
-
* rejections (e.g. Removing a developer-product icon, which the upstream
|
|
3484
|
-
* API has no documented unset path for) surface as typed errors before
|
|
3485
|
-
* `diff` runs and before any apply-side driver I/O is attempted.
|
|
3486
|
-
*
|
|
3487
|
-
* Pure and synchronous. Current-only entries (no matching desired) are
|
|
3488
|
-
* ignored: their reconciliation is `diff`'s concern, not this seam's.
|
|
3489
|
-
*
|
|
3490
|
-
* @param desired - Desired state from `buildDesired`.
|
|
3491
|
-
* @param current - Prior current state from the state port.
|
|
3492
|
-
* @returns `Ok(undefined)` when every paired entry passes its kind-level
|
|
3493
|
-
* reconcilability check, or the first `Err` returned by a hook.
|
|
3494
|
-
*
|
|
3495
|
-
* @example
|
|
3496
|
-
*
|
|
3497
|
-
* ```ts
|
|
3498
|
-
* import { asResourceKey, asRobloxAssetId, asSha256Hex, validatePlan } from "@bedrock-rbx/core";
|
|
3499
|
-
*
|
|
3500
|
-
* const result = validatePlan(
|
|
3501
|
-
* [
|
|
3502
|
-
* {
|
|
3503
|
-
* description: "Stocks the player up with 1,000 premium gems.",
|
|
3504
|
-
* isRegionalPricingEnabled: undefined,
|
|
3505
|
-
* key: asResourceKey("gem-pack"),
|
|
3506
|
-
* kind: "developerProduct",
|
|
3507
|
-
* name: "Gem Pack",
|
|
3508
|
-
* price: undefined,
|
|
3509
|
-
* storePageEnabled: undefined,
|
|
3510
|
-
* },
|
|
3511
|
-
* ],
|
|
3512
|
-
* [
|
|
3513
|
-
* {
|
|
3514
|
-
* description: "Stocks the player up with 1,000 premium gems.",
|
|
3515
|
-
* icon: { "en-us": "assets/gem-pack.png" },
|
|
3516
|
-
* iconFileHashes: {
|
|
3517
|
-
* "en-us": asSha256Hex(
|
|
3518
|
-
* "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
3519
|
-
* ),
|
|
3520
|
-
* },
|
|
3521
|
-
* isRegionalPricingEnabled: undefined,
|
|
3522
|
-
* key: asResourceKey("gem-pack"),
|
|
3523
|
-
* kind: "developerProduct",
|
|
3524
|
-
* name: "Gem Pack",
|
|
3525
|
-
* outputs: { productId: asRobloxAssetId("9876543210") },
|
|
3526
|
-
* price: undefined,
|
|
3527
|
-
* storePageEnabled: undefined,
|
|
3528
|
-
* },
|
|
3529
|
-
* ],
|
|
3530
|
-
* );
|
|
3531
|
-
*
|
|
3532
|
-
* expect(result.success).toBeFalse();
|
|
3533
|
-
* if (!result.success) {
|
|
3534
|
-
* expect(result.err.kind).toBe("iconRemovalRejected");
|
|
3535
|
-
* }
|
|
3536
|
-
* ```
|
|
3537
|
-
*/
|
|
3538
|
-
declare function validatePlan(desired: ReadonlyArray<ResourceDesiredState>, current: ReadonlyArray<ResourceCurrentState>): Result$1<undefined, BuildDesiredError>;
|
|
3539
|
-
//#endregion
|
|
3540
3859
|
//#region src/shell/migrate-mantle-state.d.ts
|
|
3541
3860
|
type ConfigFormat = "typescript" | "yaml";
|
|
3542
3861
|
/**
|
|
@@ -3632,5 +3951,5 @@ interface MigrateMantleStateDeps {
|
|
|
3632
3951
|
*/
|
|
3633
3952
|
declare function migrateMantleState(deps: MigrateMantleStateDeps): Promise<Result$1<MigrationReport, MigrateError>>;
|
|
3634
3953
|
//#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
|
|
3954
|
+
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 OverrideInvocation, 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 SpawnInvocation, type SpawnLaunchCause, type SpawnLaunchError, type SpawnOverrideError, type Spawner, 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, createDefaultSpawner, createDeveloperProductDriver, createGamePassDriver, createGistStateAdapter, createNoOpProgressAdapter, createPlaceDriver, createUniverseDriver, defaultKindRegistry, defineConfig, deploy, derivePriceFields, diff, dispatchOverride, flattenConfig, getEnvironment, isGistStateConfig, isResourceKey, isRobloxAssetId, isSha256Hex, loadConfig, migrateMantleState, parseStateFile, renderDisplayNamePrefix, resolveStateConfig, selectEnvironment, serializeStateFile, shouldReuploadIcon, validateConfig, validateEnvironmentName };
|
|
3636
3955
|
//# sourceMappingURL=index.d.mts.map
|