@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/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as UniverseOverlayWithoutId, D as ConfigValidationIssue, E as ConfigError, S as UniverseOverlayWithId, T as validateConfig, _ as ResolvedPlaceEntry, a as ConfigEnvironmentUniverseId, b as StateConfig, c as DisplayNamePrefixConfig, d as GistStateConfig, f as PlaceEntry, g as ResolvedConfig, h as RedactedPlaceOverride, i as Config, l as EnvironmentEntry, m as RedactedGamePassOverride, n as ConfigInput, o as ConfigRootUniverseId, p as RedactedDeveloperProductOverride, r as defineConfig, s as DeveloperProductEntry, t as ConfigContext, u as GamePassEntry, v as ResolvedUniverseEntry, w as isGistStateConfig, x as UniverseEntry, y as ResourceEntryByKind } from "./define-config-Bd0XIiSX.mjs";
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 === "GITHUB_TOKEN" ? "ghp_example" : undefined),
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
- /** Last-known live state; the driver computes a patch against `desired`. */
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
- * Bare by design: the operation carries only `key` and `type` because no
1591
- * payload is needed at apply time. Callers that need the matching desired or
1592
- * current state look it up in the snapshots passed to `diff`.
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
- * `appliedSoFar` carries the driver outputs from operations that succeeded
1655
- * before the failing one, in dispatched order. Callers persist this so a
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 appliedSoFar: ReadonlyArray<ResourceCurrentState>;
1686
- readonly cause: OpenCloudError$1;
1687
- readonly key: ResourceKey;
1688
- readonly kind: "driverFailure";
1689
- } | {
1690
- readonly appliedSoFar: ReadonlyArray<ResourceCurrentState>;
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
- readonly kind: "updateUnsupported";
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
- * Dispatch each reconciliation operation to the matching resource driver
1696
- * with first-fail semantics: on the first `Err` (driver failure or
1697
- * `updateUnsupported`), the remaining operations are skipped and the error
1698
- * is returned verbatim.
1699
- *
1700
- * Behaviour:
1701
- * - `create` operations are routed to `registry[op.desired.kind].create`.
1702
- * - `update` operations are routed to `registry[op.desired.kind].update`
1703
- * when the driver exposes it; otherwise they short-circuit to an
1704
- * `updateUnsupported` Err without invoking the driver.
1705
- * - `noop` operations are skipped entirely (no I/O, no dispatch).
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
- * On success the returned array carries the driver outputs for every
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
- * const registry: DriverRegistry = {
1735
- * gamePass: {
1736
- * async create(desired) {
1737
- * return {
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
- * const ops: ReadonlyArray<Operation> = [
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
- * return applyOps(ops, registry).then((result) => {
1799
- * expect(result.success).toBe(true);
1800
- * expect(result.success && result.data).toHaveLength(1);
1801
- * });
1992
+ * expect(received).toEqual([
1993
+ * { environment: "production", kind: "deploySuccess", resourceCount: 3 },
1994
+ * ]);
1802
1995
  * ```
1803
1996
  */
1804
- declare function applyOps(ops: ReadonlyArray<Operation>, registry: DriverRegistry): Promise<Result$1<ReadonlyArray<ResourceCurrentState>, ApplyError>>;
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 during desired-state preparation. Two variants today:
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`: `validatePlan` saw a kind whose prior current
2094
- * state recorded an icon that the desired state no longer declares,
2095
- * and the kind has no documented unset path on the upstream API.
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
- * Both variants carry the offending `key` so the CLI can attribute the
2098
- * failure to a single resource entry.
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 plan-time invariant check called by `validatePlan` for every
2196
- * `(kind, key)` pair that exists on both sides. Surfaces kind-specific
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 `diff` runs and before
2199
- * any apply-side driver I/O is attempted. Kinds without plan-level
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 `GITHUB_TOKEN` and `BEDROCK_API_KEY`.
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 `GITHUB_TOKEN` when omitted. */
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: ApplyError;
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 `GITHUB_TOKEN` and
2421
- * `BEDROCK_API_KEY`; never reads `process.env` when `statePort`,
2422
- * `registry`, and `config` are all supplied explicitly.
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`: `deploySuccess` becomes a single
2599
- * success line and `deployFailure` delegates to the package's deploy-error
2600
- * rendering helper.
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: "deploySuccess", resourceCount: 3 });
2950
+ * port.emit({ environment: "production", kind: "stateWritten" });
2620
2951
  *
2621
- * expect(lines).toEqual(["ok: production: 3 resources reconciled"]);
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 so
3188
- * callers can rely on declaration order when logging or applying ops.
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 live state from the state file.
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, validatePlan };
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