@bedrock-rbx/core 0.1.0-beta.1 → 0.1.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/run.mjs +22 -14
- package/dist/cli/run.mjs.map +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/{define-config-D-LAhfSJ.d.mts → define-config-87u2jqjM.d.mts} +8 -16
- package/dist/{define-config-D-LAhfSJ.d.mts.map → define-config-87u2jqjM.d.mts.map} +1 -1
- package/dist/index.d.mts +10 -33
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{migrate-mantle-state-DqbJ1TLq.mjs → migrate-mantle-state-_7Tkn0hG.mjs} +256 -344
- package/dist/migrate-mantle-state-_7Tkn0hG.mjs.map +1 -0
- package/package.json +4 -4
- package/dist/migrate-mantle-state-DqbJ1TLq.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-mantle-state-_7Tkn0hG.mjs","names":["toCurrentState","toCurrentState","OPTIONAL_BOOLEAN","socialLinkOrUndefined","OPTIONAL_BOOLEAN","entrySchema","flatten","normalize","fieldsEqual","entrySchema","flatten","normalize","fieldsEqual","entrySchema","flatten","normalize","fieldsEqual","compositeKey","c12LoadConfig","loadConfig","resolvePath","defaultLoadConfig","readFile","nodeReadFile","buildEntry","isObjectPayload","readString","readPrice","coerceRobloxId","isObjectPayload","coerceRobloxId","PLACE_CONFIGURATION_KIND","BLOCKED_FIELDS","isObjectPayload","PLACE_KIND","PLACE_CONFIGURATION_KIND","bucketByKind","isStartPlace","readString","isObjectPayload","coerceRobloxId","isObjectPayload","coerceRobloxId","EXPERIENCE_KIND","EXPERIENCE_CONFIGURATION_KIND","isObjectPayload","isObjectPayload","isObjectPayload","isObjectPayload","isObjectPayload","readFile","nodeReadFile"],"sources":["../src/core/derive-price-fields.ts","../src/types/ids.ts","../src/core/kinds/hash.ts","../src/core/kinds/read-bytes.ts","../src/core/icons.ts","../src/core/plan-follow-up-patch.ts","../src/adapters/developer-product-driver.ts","../src/adapters/game-pass-driver.ts","../src/core/environment.ts","../src/core/state-file.ts","../src/adapters/gist-state-adapter.ts","../src/core/resources.ts","../src/adapters/place-driver.ts","../src/adapters/universe-driver.ts","../src/core/validate-universe-xor.ts","../src/core/schema.ts","../src/core/kinds/developer-product.ts","../src/core/kinds/game-pass.ts","../src/core/kinds/place.ts","../src/core/kinds/universe.ts","../src/core/kinds/index.ts","../src/core/diff.ts","../src/core/display-name-prefix.ts","../src/core/flatten.ts","../src/core/resolve-state-config.ts","../src/core/select-environment.ts","../src/core/validate-plan.ts","../src/shell/apply-ops.ts","../src/shell/build-default-registry.ts","../src/shell/build-desired.ts","../src/shell/build-state-port.ts","../src/shell/load-config-internal.ts","../src/adapters/lute-luau-evaluator.ts","../src/shell/load-config.ts","../src/shell/deploy.ts","../src/core/migrate/build-state.ts","../src/core/migrate/extract-display-name-prefix.ts","../src/core/migrate/environment-label.ts","../src/core/migrate/factorize-environments-warnings.ts","../src/core/migrate/factorize-places.ts","../src/core/migrate/factorize-products.ts","../src/core/migrate/factorize-universe.ts","../src/core/migrate/factorize-environments.ts","../src/core/migrate/fold-passes.ts","../src/core/migrate/fold-universe-shared.ts","../src/core/migrate/fold-blocked-place-fields.ts","../src/core/migrate/fold-places.ts","../src/core/migrate/fold-products.ts","../src/core/migrate/fold-blocked-experience-fields.ts","../src/core/migrate/fold-display-name.ts","../src/core/migrate/fold-experience-icon.ts","../src/core/migrate/fold-social-links.ts","../src/core/migrate/fold-universe.ts","../src/core/migrate/fold-unsupported.ts","../src/core/migrate/fold-environment.ts","../src/core/migrate/parse-state.ts","../src/core/migrate/serialize-config.ts","../src/core/migrate/summarize-warnings.ts","../src/shell/recompute-icon-hashes.ts","../src/shell/migrate-mantle-state.ts"],"sourcesContent":["/**\n * Wire-shape pricing fragment produced by {@link derivePriceFields}: the\n * `isForSale` flag and an optional numeric `price`. Mirrors the multipart\n * fields the Open Cloud `developer-products` create and update endpoints\n * accept for setting Robux pricing.\n */\nexport interface PriceFields {\n\t/** Whether the developer product should be purchasable. */\n\treadonly isForSale: boolean;\n\t/** Default price in Robux; absent when the product is off-sale. */\n\treadonly price?: number;\n}\n\n/**\n * Translate a Mantle-style optional price into the Open Cloud wire shape.\n *\n * `desired.price === undefined` (no price declared) becomes\n * `{ isForSale: false }` and the `price` key is omitted entirely. A defined\n * price (including `0`) becomes `{ isForSale: true, price }`. Both\n * `developerProduct` create and update paths share this helper so the\n * \"absent ⇒ off-sale\" semantics live in exactly one place.\n *\n * @param desired - Object carrying the user-declared `price`.\n * @returns The wire-shape `{ isForSale, price? }` fragment.\n *\n * @example\n *\n * ```ts\n * import { derivePriceFields } from \"@bedrock-rbx/core\";\n *\n * expect(derivePriceFields({ price: undefined })).toStrictEqual({ isForSale: false });\n * expect(derivePriceFields({ price: 250 })).toStrictEqual({ isForSale: true, price: 250 });\n * ```\n */\nexport function derivePriceFields(desired: { readonly price: number | undefined }): PriceFields {\n\tif (desired.price === undefined) {\n\t\treturn { isForSale: false };\n\t}\n\n\treturn { isForSale: true, price: desired.price };\n}\n","import type { Tagged } from \"type-fest\";\n\n/**\n * Regex source shared by the `ResourceKey` brand validator and the runtime\n * config schema. Kept as a string (not a `RegExp`) so arktype can consume it\n * directly in keyed-map signatures without re-escaping.\n */\nexport const RESOURCE_KEY_PATTERN_SOURCE = \"^[A-Za-z0-9_-]+$\";\n\nconst RESOURCE_KEY_PATTERN = new RegExp(RESOURCE_KEY_PATTERN_SOURCE);\n\nconst ROBLOX_ASSET_ID_PATTERN = /^\\d+$/;\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n/**\n * User-supplied identifier for a resource within a config (e.g. `\"vip-pass\"`).\n * Stable across deploys; used to correlate desired ↔ current state.\n */\nexport type ResourceKey = Tagged<string, \"ResourceKey\">;\n\n/**\n * Roblox-assigned numeric asset ID, represented as a string to avoid int64\n * precision loss in JavaScript.\n */\nexport type RobloxAssetId = Tagged<string, \"RobloxAssetId\">;\n\n/**\n * Lowercase hex-encoded SHA-256 digest (exactly 64 characters drawn from\n * `0-9a-f`). Used to detect changes to file-backed resource inputs such as\n * game-pass icons without re-uploading the file.\n */\nexport type Sha256Hex = Tagged<string, \"Sha256Hex\">;\n\n/**\n * Type predicate: test whether a raw string is a valid {@link ResourceKey}.\n *\n * Prefer this when the caller owns error handling (for example, constructing a\n * `Result` error in a shell-layer parser). Use {@link asResourceKey} when an\n * exception is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isResourceKey } from \"@bedrock-rbx/core\";\n *\n * const valid = isResourceKey(\"vip-pass\");\n * const invalid = isResourceKey(\"vip pass\");\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the ResourceKey shape; narrows `raw`.\n */\nexport function isResourceKey(raw: string): raw is ResourceKey {\n\treturn RESOURCE_KEY_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link ResourceKey}.\n *\n * Accepts non-empty strings of alphanumeric characters, hyphens, and\n * underscores (matching `/^[A-Za-z0-9_-]+$/`).\n *\n * @example\n *\n * ```ts\n * import { asResourceKey } from \"@bedrock-rbx/core\";\n *\n * const key = asResourceKey(\"vip-pass\");\n *\n * let thrown: unknown;\n * try {\n * asResourceKey(\"vip pass\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(key).toBe(\"vip-pass\");\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link ResourceKey}.\n * @throws RangeError when `raw` is empty or contains disallowed characters.\n */\nexport function asResourceKey(raw: string): ResourceKey {\n\tif (!isResourceKey(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`ResourceKey must match ${String(RESOURCE_KEY_PATTERN)} (got ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n\n/**\n * Type predicate: test whether a raw string is a valid {@link RobloxAssetId}.\n *\n * Prefer this when the caller owns error handling (for example, constructing a\n * `Result` error in a shell-layer parser). Use {@link asRobloxAssetId} when an\n * exception is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isRobloxAssetId } from \"@bedrock-rbx/core\";\n *\n * const valid = isRobloxAssetId(\"12345\");\n * const invalid = isRobloxAssetId(\"12345abc\");\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the RobloxAssetId shape; narrows `raw`.\n */\nexport function isRobloxAssetId(raw: string): raw is RobloxAssetId {\n\treturn ROBLOX_ASSET_ID_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link RobloxAssetId}.\n *\n * Accepts non-empty digit-only strings (matching `/^\\d+$/`). Roblox asset IDs\n * are int64 values carried as strings because JavaScript's `number` cannot\n * represent the full int64 range.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId } from \"@bedrock-rbx/core\";\n *\n * const id = asRobloxAssetId(\"12345\");\n *\n * let thrown: unknown;\n * try {\n * asRobloxAssetId(\"12345abc\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(id).toBe(\"12345\");\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link RobloxAssetId}.\n * @throws RangeError when `raw` is empty or contains non-digit characters.\n */\nexport function asRobloxAssetId(raw: string): RobloxAssetId {\n\tif (!isRobloxAssetId(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`RobloxAssetId must be a non-empty digit-only string matching ${String(\n\t\t\t\tROBLOX_ASSET_ID_PATTERN,\n\t\t\t)} (got ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n\n/**\n * Type predicate: test whether a raw string is a valid {@link Sha256Hex}.\n *\n * Accepts exactly 64 lowercase hexadecimal characters (matching\n * `/^[0-9a-f]{64}$/`). Prefer this when the caller owns error handling;\n * use {@link asSha256Hex} when throwing is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isSha256Hex } from \"@bedrock-rbx/core\";\n *\n * const digest = \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\";\n * const valid = isSha256Hex(digest);\n * const invalid = isSha256Hex(digest.toUpperCase());\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the Sha256Hex shape; narrows `raw`.\n */\nexport function isSha256Hex(raw: string): raw is Sha256Hex {\n\treturn SHA256_HEX_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link Sha256Hex}.\n *\n * Accepts exactly 64 lowercase hexadecimal characters. Uppercase hex, lengths\n * other than 64, and any non-hex character are rejected so the brand is a\n * canonical representation.\n *\n * @example\n *\n * ```ts\n * import { asSha256Hex } from \"@bedrock-rbx/core\";\n *\n * const digest = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n *\n * let thrown: unknown;\n * try {\n * asSha256Hex(\"deadbeef\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(digest).toHaveLength(64);\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link Sha256Hex}.\n * @throws RangeError when `raw` is not exactly 64 lowercase hex characters.\n */\nexport function asSha256Hex(raw: string): Sha256Hex {\n\tif (!isSha256Hex(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`Sha256Hex must be 64 lowercase hex characters matching ${String(\n\t\t\t\tSHA256_HEX_PATTERN,\n\t\t\t)} (got ${raw.length} chars: ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n","/**\n * Compute the SHA-256 hex digest of a byte sequence. Shared by kind modules\n * that hash file contents as part of pre-I/O normalization.\n *\n * @param bytes - Source bytes; typically the output of an injected `readFile`.\n * @returns Lowercase 64-character hex string.\n */\nexport async function sha256Hex(bytes: Uint8Array): Promise<string> {\n\t// `Uint8Array.from(bytes)` narrows `Uint8Array<ArrayBufferLike>` to\n\t// `Uint8Array<ArrayBuffer>` for `crypto.subtle.digest`, which rejects the\n\t// SharedArrayBuffer variant at the type level.\n\tconst buffer = await crypto.subtle.digest(\"SHA-256\", Uint8Array.from(bytes));\n\treturn Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, \"0\")).join(\n\t\t\"\",\n\t);\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { ResourceKey } from \"../../types/ids.ts\";\nimport type { BuildDesiredError, KindIo } from \"./module.ts\";\n\n/**\n * Read file bytes via the injected reader, translating rejections into a\n * `fileReadFailed` `BuildDesiredError`. Shared by kind modules whose\n * pre-I/O normalization hashes a file the user declared by path.\n *\n * @param target - Path to read plus the resource key blamed on failure.\n * @param io - I/O surface carrying the injected `readFile` function.\n * @returns `Ok` with the bytes, or `Err` with a `fileReadFailed` error.\n */\nexport async function readBytes(\n\ttarget: { readonly filePath: string; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Uint8Array, BuildDesiredError>> {\n\ttry {\n\t\treturn { data: await io.readFile(target.filePath), success: true };\n\t} catch (err) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkey: target.key,\n\t\t\t\tfilePath: target.filePath,\n\t\t\t\tkind: \"fileReadFailed\",\n\t\t\t\treason: err instanceof Error ? err.message : String(err),\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type, type Type } from \"arktype\";\n\nimport { asSha256Hex, type ResourceKey, type Sha256Hex } from \"../types/ids.ts\";\nimport { sha256Hex } from \"./kinds/hash.ts\";\nimport type { BuildDesiredError, KindIo } from \"./kinds/module.ts\";\nimport { readBytes } from \"./kinds/read-bytes.ts\";\n\n/**\n * ArkType schema for the locale-keyed icon path map. Today the only\n * accepted locale is `\"en-us\"`; declaring any other locale fails\n * validation. Shared by every icon-bearing entry schema (passes,\n * universe, and any future kind that adopts the same shape) so all\n * locale-rejection messages attribute the issue path identically.\n */\nexport const iconMap: Type<Record<\"en-us\", string>> = type({\n\t\"en-us\": \"string\",\n}).onUndeclaredKey(\"reject\");\n\n/**\n * Read one icon file via the injected I/O surface and return its branded\n * SHA-256 hex digest. Composes `readBytes` (which translates rejections\n * into `fileReadFailed` errors) with `sha256Hex` so kind modules and their\n * locale-iterating wrappers do not re-author the same orchestration.\n *\n * @param target - Path of the icon file plus the resource key blamed on\n * read failure; both are carried unchanged onto `BuildDesiredError`.\n * @param io - I/O surface carrying the injected `readFile`.\n * @returns `Ok` with the branded digest, or `Err` with a `fileReadFailed`\n * error carrying both `filePath` and `key` unchanged.\n */\nexport async function hashIconFile(\n\ttarget: { readonly filePath: string; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Sha256Hex, BuildDesiredError>> {\n\tconst read = await readBytes(target, io);\n\tif (!read.success) {\n\t\treturn read;\n\t}\n\n\treturn { data: asSha256Hex(await sha256Hex(read.data)), success: true };\n}\n\n/**\n * Hash every locale entry on a declared icon map, producing a hash map\n * keyed by the same locales. Iterates `hashIconFile` per locale so each\n * read failure surfaces the offending file's path verbatim. Stamped onto\n * desired-state entries by per-kind `normalize` implementations.\n *\n * @param input - Declared icon paths plus the resource key blamed on\n * read failure.\n * @param io - I/O surface carrying the injected `readFile`.\n * @returns `Ok` with hashes mirroring the locale shape of the input, or\n * `Err` from the first locale whose file could not be read.\n */\nexport async function hashIconLocales(\n\tinput: { readonly icon: Record<\"en-us\", string>; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Record<\"en-us\", Sha256Hex>, BuildDesiredError>> {\n\tconst enUs = await hashIconFile({ key: input.key, filePath: input.icon[\"en-us\"] }, io);\n\tif (!enUs.success) {\n\t\treturn enUs;\n\t}\n\n\treturn { data: { \"en-us\": enUs.data }, success: true };\n}\n\n/**\n * Locale-aware tri-state equality on icon-file hash maps. Used by per-kind\n * `fieldsEqual` implementations to detect drift on resources that carry an\n * optional locale-keyed icon (universe, game-pass, and any future kind that\n * adopts the same shape).\n *\n * The signature widens from kind-typed inputs to hash maps so the helper\n * does not couple to any specific resource-kind shape; callers project the\n * `iconFileHashes` field off their own current/desired values. Parameter\n * order mirrors `shouldReuploadIcon` so the two helpers can be read side\n * by side without re-orienting at each call.\n *\n * Tri-state semantics:\n *\n * - both `undefined` (icon absent on both sides): `true`.\n * - one `undefined`, the other present: `false`.\n * - both present: per-locale hash equality on the `\"en-us\"` key.\n *\n * @param current - Hashes recorded on the prior current-state entry.\n * @param desired - Hashes layered onto the desired-state entry by `normalize`.\n * @returns `true` when no re-upload is implied by the hash comparison.\n */\nexport function iconHashesEqual(\n\tcurrent: Record<\"en-us\", Sha256Hex> | undefined,\n\tdesired: Record<\"en-us\", Sha256Hex> | undefined,\n): boolean {\n\tif (current === undefined) {\n\t\treturn desired === undefined;\n\t}\n\n\tif (desired === undefined) {\n\t\treturn false;\n\t}\n\n\treturn current[\"en-us\"] === desired[\"en-us\"];\n}\n\n/**\n * Cost-gate for icon re-uploads. Returns `true` when the locally-hashed\n * desired icon differs from the hash recorded on the prior current-state\n * entry, signalling that the driver must re-upload before reconciling.\n * Returns `false` when the hashes match (no re-upload needed) and when\n * both sides report no icon.\n *\n * The signature takes hash maps directly (not whole-state) so the helper\n * is independent of any specific resource-kind shape; every icon-bearing\n * driver projects its own `iconFileHashes` and `outputs.iconFileHashes`\n * fields before calling.\n *\n * @param currentHashes - Hashes recorded on the prior current-state entry.\n * @param desiredHashes - Hashes layered onto the desired-state entry by\n * `normalize` from the local icon file's bytes.\n * @returns `true` when the driver should re-upload the icon.\n *\n * @example\n *\n * ```ts\n * import { asSha256Hex, shouldReuploadIcon } from \"@bedrock-rbx/core\";\n *\n * const previous = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n * const fresh = asSha256Hex(\n * \"2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881\",\n * );\n *\n * expect(shouldReuploadIcon({ \"en-us\": previous }, { \"en-us\": previous })).toBe(false);\n * expect(shouldReuploadIcon({ \"en-us\": previous }, { \"en-us\": fresh })).toBe(true);\n * ```\n */\nexport function shouldReuploadIcon(\n\tcurrentHashes: Record<\"en-us\", Sha256Hex> | undefined,\n\tdesiredHashes: Record<\"en-us\", Sha256Hex> | undefined,\n): boolean {\n\treturn !iconHashesEqual(currentHashes, desiredHashes);\n}\n","import type { DeveloperProduct } from \"@bedrock-rbx/ocale/developer-products\";\n\nimport type { DeveloperProductDesiredState } from \"./resources.ts\";\n\n/**\n * Optional follow-up PATCH body the developer-product driver issues after a\n * successful create POST when `storePageEnabled` was declared on desired\n * state. The Roblox v2 create endpoint does not accept `storePageEnabled`,\n * so the driver applies the flag in a PATCH after the POST returns.\n */\ninterface FollowUpPatchBody {\n\t/** The `storePageEnabled` value the driver should apply via PATCH. */\n\treadonly storePageEnabled: boolean;\n}\n\n/**\n * Plan the optional follow-up PATCH body needed after a developer-product\n * create POST. Returns `undefined` when no PATCH is required: either the\n * user did not declare `storePageEnabled`, or the create response already\n * matches the desired value.\n *\n * @param desired - Desired state for the developer product being created.\n * @param createResponse - The `storePageEnabled` value reported by the create POST response.\n * @returns The PATCH body to issue, or `undefined` when no follow-up is needed.\n */\nexport function planFollowUpPatch(\n\tdesired: DeveloperProductDesiredState,\n\tcreateResponse: Pick<DeveloperProduct, \"storePageEnabled\">,\n): FollowUpPatchBody | undefined {\n\tif (desired.storePageEnabled === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (desired.storePageEnabled === createResponse.storePageEnabled) {\n\t\treturn undefined;\n\t}\n\n\treturn { storePageEnabled: desired.storePageEnabled };\n}\n","import type { OpenCloudError, Result } from \"@bedrock-rbx/ocale\";\nimport type {\n\tDeveloperProduct,\n\tDeveloperProductsClient,\n} from \"@bedrock-rbx/ocale/developer-products\";\n\nimport { derivePriceFields } from \"../core/derive-price-fields.ts\";\nimport { shouldReuploadIcon } from \"../core/icons.ts\";\nimport { planFollowUpPatch } from \"../core/plan-follow-up-patch.ts\";\nimport type { DeveloperProductDesiredState, ResourceCurrentState } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId, type RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createDeveloperProductDriver`. `universeId` is captured\n * at construction time (matching `GamePassDriverDeps`) so each driver\n * instance is bound to a single universe; multi-universe deploys construct\n * one driver per universe. `readFile` exists on the driver (not upstream\n * in shell) because icon hashes flow through `diff` but bytes do not.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\n * import { asRobloxAssetId, type DeveloperProductDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: DeveloperProductDriverDeps = {\n * client: new DeveloperProductsClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface DeveloperProductDriverDeps {\n\t/** Configured developer-products client from `@bedrock-rbx/ocale/developer-products`. */\n\treadonly client: DeveloperProductsClient;\n\t/** Reads icon bytes for upload; rejections propagate out of `create` and `update`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every developer product this driver creates. */\n\treadonly universeId: RobloxAssetId;\n}\n\ninterface UpdateInputs {\n\treadonly current: ResourceCurrentState<\"developerProduct\">;\n\treadonly desired: DeveloperProductDesiredState;\n}\n\ninterface FollowUpPatchInputs {\n\treadonly created: DeveloperProduct;\n\treadonly desired: DeveloperProductDesiredState;\n}\n\n/**\n * Wraps {@link DeveloperProductsClient} as a `ResourceDriver<\"developerProduct\">`\n * that maps a desired-state entry to an ocale create or update call and the\n * response back to a `ResourceCurrentState<\"developerProduct\">`. The\n * `update` path consumes the upstream `204 No Content` response and\n * synthesizes the post-update `ResourceCurrentState` from `desired` plus\n * the existing `current.outputs`, carrying `iconImageAssetId` forward when\n * present.\n *\n * Upstream `OpenCloudError` results pass through as `Result` failures.\n *\n * @param deps - Injected ocale client and owning universe.\n * @returns A driver indexable by `\"developerProduct\"` in a `DriverRegistry`.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * createDeveloperProductDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: {\n * createdTimestamp: \"2024-01-15T10:30:00.000Z\",\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * iconImageAssetId: null,\n * isForSale: false,\n * isImmutable: false,\n * name: \"Gem Pack\",\n * priceInformation: null,\n * productId: 9_876_543_210,\n * storePageEnabled: false,\n * universeId: 1_234_567_890,\n * updatedTimestamp: \"2024-01-15T10:30:00.000Z\",\n * },\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createDeveloperProductDriver({\n * client: new DeveloperProductsClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * isRegionalPricingEnabled: undefined,\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * price: undefined,\n * storePageEnabled: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.productId).toBe(\"9876543210\");\n * }\n * });\n * ```\n */\nexport function createDeveloperProductDriver(\n\tdeps: DeveloperProductDriverDeps,\n): ResourceDriver<\"developerProduct\"> {\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn createOne(deps, desired);\n\t\t},\n\t\tasync update(current, desired) {\n\t\t\treturn updateOne(deps, { current, desired });\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: DeveloperProductDesiredState,\n\tdata: DeveloperProduct,\n): Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError> {\n\tconst iconImageAssetId =\n\t\tdata.iconImageAssetId === undefined ? undefined : asRobloxAssetId(data.iconImageAssetId);\n\n\treturn {\n\t\tdata: {\n\t\t\t...desired,\n\t\t\toutputs: {\n\t\t\t\tproductId: asRobloxAssetId(data.id),\n\t\t\t\t...(iconImageAssetId === undefined ? {} : { iconImageAssetId }),\n\t\t\t},\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nasync function applyFollowUpPatch(\n\tdeps: DeveloperProductDriverDeps,\n\t{ created, desired }: FollowUpPatchInputs,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst followUp = planFollowUpPatch(desired, created);\n\tif (followUp === undefined) {\n\t\treturn toCurrentState(desired, created);\n\t}\n\n\tconst patched = await deps.client.update({\n\t\tproductId: asRobloxAssetId(created.id),\n\t\tuniverseId: deps.universeId,\n\t\t...followUp,\n\t});\n\tif (patched.success) {\n\t\treturn toCurrentState(desired, created);\n\t}\n\n\t// PATCH failed but the POST persisted the resource. Surface success\n\t// carrying the wire-reported storePageEnabled so state captures what\n\t// actually exists; the next deploy's diff sees the desired/current\n\t// mismatch and retries the PATCH (self-heal).\n\treturn toCurrentState({ ...desired, storePageEnabled: created.storePageEnabled }, created);\n}\n\nasync function createOne(\n\tdeps: DeveloperProductDriverDeps,\n\tdesired: DeveloperProductDesiredState,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst imageFile =\n\t\tdesired.icon === undefined ? undefined : await deps.readFile(desired.icon[\"en-us\"]);\n\tconst created = await deps.client.create({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\tuniverseId: deps.universeId,\n\t\t...(imageFile === undefined ? {} : { imageFile }),\n\t\t...derivePriceFields(desired),\n\t\t...(desired.isRegionalPricingEnabled === undefined\n\t\t\t? {}\n\t\t\t: { isRegionalPricingEnabled: desired.isRegionalPricingEnabled }),\n\t});\n\tif (!created.success) {\n\t\treturn created;\n\t}\n\n\treturn applyFollowUpPatch(deps, { created: created.data, desired });\n}\n\nasync function updateOne(\n\tdeps: DeveloperProductDriverDeps,\n\t{ current, desired }: UpdateInputs,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst imageFile =\n\t\tdesired.icon !== undefined &&\n\t\tshouldReuploadIcon(current.iconFileHashes, desired.iconFileHashes)\n\t\t\t? await deps.readFile(desired.icon[\"en-us\"])\n\t\t\t: undefined;\n\tconst result = await deps.client.update({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\tproductId: current.outputs.productId,\n\t\tuniverseId: deps.universeId,\n\t\t...(imageFile === undefined ? {} : { imageFile }),\n\t\t...derivePriceFields(desired),\n\t\t...(desired.isRegionalPricingEnabled === undefined\n\t\t\t? {}\n\t\t\t: { isRegionalPricingEnabled: desired.isRegionalPricingEnabled }),\n\t\t...(desired.storePageEnabled === undefined\n\t\t\t? {}\n\t\t\t: { storePageEnabled: desired.storePageEnabled }),\n\t});\n\tif (!result.success) {\n\t\treturn result;\n\t}\n\n\t// The PATCH endpoint returns 204; the post-update state is the desired\n\t// entry composed with the existing Roblox-assigned outputs.\n\treturn { data: { ...desired, outputs: current.outputs }, success: true };\n}\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { GamePass, GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n\nimport type { GamePassDesiredState, ResourceCurrentState } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId, type RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * `universeId` is captured at construction time rather than on\n * `GamePassDesiredState` so state files round-trip with Mantle's `PassInputs`\n * shape. `readFile` exists on the driver (not upstream in shell) because icon\n * hashes flow through `diff` but bytes do not.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n * import { asRobloxAssetId, type GamePassDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: GamePassDriverDeps = {\n * client: new GamePassesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface GamePassDriverDeps {\n\t/** Configured game-passes client from `@bedrock-rbx/ocale/game-passes`. */\n\treadonly client: GamePassesClient;\n\t/** Reads icon bytes for upload; rejections propagate out of `create`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every game pass this driver creates. */\n\treadonly universeId: RobloxAssetId;\n}\n\n/**\n * Wraps {@link GamePassesClient} as a `ResourceDriver<\"gamePass\">` that maps\n * a desired-state entry to an ocale create call and the response back to a\n * `ResourceCurrentState<\"gamePass\">`.\n *\n * Upstream `OpenCloudError` results pass through as `Result` failures.\n * Filesystem errors from `deps.readFile` do not fit the `OpenCloudError`\n * shape and propagate as promise rejections; shell callers are expected to\n * translate them if a unified error surface is required.\n *\n * @param deps - Injected ocale client, file reader, and owning universe.\n * @returns A driver indexable by `\"gamePass\"` in a `DriverRegistry`.\n * @throws Whatever `deps.readFile` rejects with.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * createGamePassDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: {\n * createdTimestamp: \"2024-01-15T10:30:00.000Z\",\n * description: \"Grants VIP perks.\",\n * gamePassId: 9_876_543_210,\n * iconAssetId: 1_122_334_455,\n * isForSale: true,\n * name: \"VIP Pass\",\n * updatedTimestamp: \"2024-01-15T10:30:00.000Z\",\n * },\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createGamePassDriver({\n * client: new GamePassesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.assetId).toBe(\"9876543210\");\n * }\n * });\n * ```\n */\nexport function createGamePassDriver(deps: GamePassDriverDeps): ResourceDriver<\"gamePass\"> {\n\treturn {\n\t\tasync create(desired) {\n\t\t\tconst imageFile = await deps.readFile(desired.icon[\"en-us\"]);\n\t\t\tconst result = await deps.client.create({\n\t\t\t\tname: desired.name,\n\t\t\t\tdescription: desired.description,\n\t\t\t\timageFile,\n\t\t\t\tuniverseId: deps.universeId,\n\t\t\t\t...(desired.price !== undefined ? { price: desired.price } : {}),\n\t\t\t});\n\t\t\tif (!result.success) {\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\treturn toCurrentState(desired, result.data);\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: GamePassDesiredState,\n\tdata: GamePass,\n): Result<ResourceCurrentState<\"gamePass\">, OpenCloudError> {\n\tconst { id, iconAssetId } = data;\n\tif (iconAssetId === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t\"Malformed game pass response: iconAssetId missing after icon upload\",\n\t\t\t\t{ statusCode: 200 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\t...desired,\n\t\t\toutputs: {\n\t\t\t\tassetId: asRobloxAssetId(id),\n\t\t\t\ticonAssetIds: { \"en-us\": asRobloxAssetId(iconAssetId) },\n\t\t\t},\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { StateError } from \"./state.ts\";\n\n// Character class mirrors `RESOURCE_KEY_PATTERN_SOURCE` in types/ids.ts\n// (both are \"safe identifier\" alphabets). They're kept in separate literals\n// because the contracts are distinct: ResourceKey is unbounded, this one\n// caps length at 64 so adapter-stored filenames can't exceed filesystem\n// limits. If one alphabet changes, update the other deliberately.\n/**\n * Source pattern for environment names, including `^` and `$` anchors.\n * Letters, digits, `-`, `_`, length 1-64.\n *\n * Exported so the config schema can validate `environments` keys against\n * the same alphabet and length cap that adapters enforce on storage\n * identifiers. Single source of truth: changing the alphabet here changes\n * both the runtime check and the schema-level key constraint.\n *\n * Anchors are embedded so callers do not have to re-add them, matching\n * the `RESOURCE_KEY_PATTERN_SOURCE` convention in `types/ids.ts`.\n */\nexport const ENV_NAME_PATTERN_SOURCE = \"^[A-Za-z0-9_-]{1,64}$\";\n\nconst ENVIRONMENT_NAME_PATTERN = new RegExp(ENV_NAME_PATTERN_SOURCE);\n\n/**\n * Validate an environment name at a state-adapter boundary.\n *\n * Adapters that map environment names onto filesystem-like identifiers\n * (gist filenames, S3 keys) must reject names that could collide or escape\n * their storage layout. This helper accepts letters, digits, `-`, and `_`\n * only, with length between 1 and 64, and returns a `StateError` for\n * anything outside that set so the adapter can fail loudly instead of\n * silently stripping characters.\n *\n * @example\n *\n * ```ts\n * import { validateEnvironmentName } from \"@bedrock-rbx/core\";\n *\n * const ok = validateEnvironmentName(\"production\");\n * expect(ok.success).toBeTrue();\n *\n * const bad = validateEnvironmentName(\"prod/staging\");\n * expect(bad.success).toBeFalse();\n * ```\n *\n * @param environment - Raw environment name supplied by a caller.\n * @returns `Ok(environment)` when the name is safe to use, or\n * `Err(StateError)` with a descriptive reason when it is not.\n */\nexport function validateEnvironmentName(environment: string): Result<string, StateError> {\n\tif (!ENVIRONMENT_NAME_PATTERN.test(environment)) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tfile: environment,\n\t\t\t\tkind: \"stateError\",\n\t\t\t\treason: `invalid environment name: must match ${String(ENVIRONMENT_NAME_PATTERN)}`,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: environment, success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { ArkErrors, type } from \"arktype\";\n\nimport type { ResourceCurrentState } from \"./resources.ts\";\nimport type { BedrockState, StateError } from \"./state.ts\";\n\n// Resource-level validation is intentionally shallow: every resource must be\n// an object with a known `kind` discriminator, but per-kind field validation\n// (game-pass vs place vs universe) is deferred. Bedrock is both the writer\n// and the reader of state files, so tampering is out of scope for v0.1;\n// a follow-up issue widens this to full per-kind schema + brand narrowing.\nconst resourceShape = type({\n\t\"key\": \"string\",\n\t\"[string]\": \"unknown\",\n\t\"kind\": \"'developerProduct' | 'gamePass' | 'place' | 'universe'\",\n\t\"outputs\": \"object\",\n});\n\nconst envelopeSchema = type({\n\t$bedrock: { version: \"1\" },\n\tenvironment: \"string\",\n\tresources: resourceShape.array(),\n});\n\n/**\n * Serialize a {@link BedrockState} to the on-disk JSON representation used by\n * state-port adapters.\n *\n * The on-disk shape wraps the in-memory state with a\n * `$bedrock: { version: N }` envelope so that a future breaking change to the\n * schema can be detected and rejected at parse time rather than silently\n * accepted. The top-level `version` field is not duplicated on disk.\n *\n * @example\n *\n * ```ts\n * import { serializeStateFile, type BedrockState } from \"@bedrock-rbx/core\";\n *\n * const state: BedrockState = {\n * environment: \"production\",\n * resources: [],\n * version: 1,\n * };\n *\n * const wire = serializeStateFile(state);\n * expect(JSON.parse(wire)).toStrictEqual({\n * $bedrock: { version: 1 },\n * environment: \"production\",\n * resources: [],\n * });\n * ```\n *\n * @param state - The in-memory state snapshot to serialize.\n * @returns A pretty-printed JSON string ready to hand to a state adapter's write method.\n */\nexport function serializeStateFile(state: BedrockState): string {\n\tconst envelope = {\n\t\t$bedrock: { version: state.version },\n\t\tenvironment: state.environment,\n\t\tresources: state.resources,\n\t};\n\treturn JSON.stringify(envelope, undefined, 2);\n}\n\n/**\n * Parse a raw on-disk state file into a {@link BedrockState}.\n *\n * A backend that reports \"no state file for this environment yet\" must pass\n * `undefined`: that distinguishes a legitimate first deploy from a file that\n * exists but cannot be trusted.\n *\n * @example\n *\n * ```ts\n * import { parseStateFile } from \"@bedrock-rbx/core\";\n *\n * const freshStart = parseStateFile(undefined, \"gist:abc123/state.production.json\");\n * expect(freshStart.success).toBeTrue();\n * if (freshStart.success) {\n * expect(freshStart.data).toBeUndefined();\n * }\n * ```\n *\n * @param raw - Raw file contents as a string, or `undefined` when the\n * backend reports no file exists yet.\n * @param file - Adapter-specific identifier included in any `StateError`\n * surfaced during parsing.\n * @returns `Ok(undefined)` for a missing file, `Ok(state)` for a parseable\n * file, or `Err(StateError)` for anything that cannot be trusted.\n */\nexport function parseStateFile(\n\traw: string | undefined,\n\tfile: string,\n): Result<BedrockState | undefined, StateError> {\n\tif (raw === undefined) {\n\t\treturn { data: undefined, success: true };\n\t}\n\n\tconst parsed = parseJson(raw, file);\n\tif (!parsed.success) {\n\t\treturn parsed;\n\t}\n\n\tconst validated = envelopeSchema(parsed.data);\n\tif (validated instanceof ArkErrors) {\n\t\treturn errState(file, `invalid state file: ${validated.summary}`);\n\t}\n\n\tconst resources = validated.resources as unknown as ReadonlyArray<ResourceCurrentState>;\n\treturn {\n\t\tdata: { environment: validated.environment, resources, version: 1 },\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseJson(raw: string, file: string): Result<JSONValue, StateError> {\n\ttry {\n\t\treturn { data: JSON.parse(raw), success: true };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn {\n\t\t\terr: { file, kind: \"stateError\", reason: `malformed JSON: ${message}` },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n}\n\nfunction errState(file: string, reason: string): Result<BedrockState | undefined, StateError> {\n\treturn {\n\t\terr: { file, kind: \"stateError\", reason },\n\t\tsuccess: false,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { validateEnvironmentName } from \"../core/environment.ts\";\nimport { parseStateFile, serializeStateFile } from \"../core/state-file.ts\";\nimport type { BedrockState, StateError } from \"../core/state.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\n\nconst GITHUB_API_BASE = \"https://api.github.com\";\nconst GITHUB_API_VERSION = \"2026-03-10\";\nconst USER_AGENT = \"bedrock\";\nconst MAX_INLINE_BYTES = 10_000_000;\nconst MAX_RETRIES = 3;\nconst RETRYABLE_STATUSES: ReadonlySet<number> = new Set([409, 502, 503, 504]);\n\n/**\n * Minimal `fetch`-compatible signature the adapter needs, narrower than\n * `typeof globalThis.fetch` so test fakes do not have to stub runtime\n * extensions such as `fetch.preconnect`.\n */\nexport type GistFetch = (\n\tinput: globalThis.Request | string | URL,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Configuration for {@link createGistStateAdapter}.\n */\nexport interface GistStateAdapterDeps {\n\t/** Injection seam for tests; defaults to `globalThis.fetch`. */\n\treadonly fetch?: GistFetch | undefined;\n\t/** ID of an existing GitHub Gist that holds this project's state files. */\n\treadonly gistId: string;\n\t/**\n\t * Injection seam for retry backoff timing; defaults to a `setTimeout`-based\n\t * promise. Tests pass a fake to keep retry assertions deterministic.\n\t */\n\treadonly sleep?: ((ms: number) => Promise<void>) | undefined;\n\t/** GitHub token (fine-grained PAT or classic PAT) with gist read/write scope. */\n\treadonly token: string;\n}\n\ninterface AdapterContext {\n\treadonly fetchFn: GistFetch;\n\treadonly gistId: string;\n\treadonly sleep: (ms: number) => Promise<void>;\n\treadonly token: string;\n}\n\ninterface GistFile {\n\treadonly content: string | undefined;\n\treadonly isTruncated: boolean;\n\treadonly rawUrl: string | undefined;\n\treadonly size: number;\n}\n\ninterface HttpFailure {\n\treadonly file: string;\n\treadonly gistId: string;\n\treadonly status: number;\n}\n\ninterface ReadContentParameters {\n\treadonly entry: GistFile;\n\treadonly fetchFn: GistFetch;\n\treadonly file: string;\n\treadonly sleep: (ms: number) => Promise<void>;\n}\n\n/**\n * Build a `StatePort` that persists Bedrock state in a GitHub Gist.\n *\n * One gist holds one file per environment, named `state.<env>.json`. The\n * adapter authenticates with a user-supplied token and speaks the GitHub\n * REST API directly; no SDK dependency.\n *\n * @example\n *\n * ```ts\n * import { createGistStateAdapter } from \"@bedrock-rbx/core\";\n *\n * const port = createGistStateAdapter({\n * fetch: async () =>\n * new Response(JSON.stringify({ files: {} }), { status: 200 }),\n * gistId: \"abc123def456\",\n * token: \"ghp_example\",\n * });\n *\n * return port.read(\"production\").then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toBeUndefined();\n * }\n * });\n * ```\n *\n * @param deps - Gist ID, GitHub token, and optional fetch override.\n * @returns A `StatePort` ready to be passed to `deploy()`.\n */\nexport function createGistStateAdapter(deps: GistStateAdapterDeps): StatePort {\n\tconst ctx: AdapterContext = {\n\t\tfetchFn: deps.fetch ?? globalThis.fetch.bind(globalThis),\n\t\tgistId: deps.gistId,\n\t\tsleep: deps.sleep ?? defaultSleep,\n\t\ttoken: deps.token,\n\t};\n\n\treturn {\n\t\tasync read(environment) {\n\t\t\tconst safe = validateEnvironmentName(environment);\n\t\t\tif (!safe.success) {\n\t\t\t\treturn safe;\n\t\t\t}\n\n\t\t\treturn readPath(ctx, safe.data);\n\t\t},\n\t\tasync write(state) {\n\t\t\tconst safe = validateEnvironmentName(state.environment);\n\t\t\tif (!safe.success) {\n\t\t\t\treturn safe;\n\t\t\t}\n\n\t\t\treturn writePath(ctx, state);\n\t\t},\n\t};\n}\n\nasync function defaultSleep(ms: number): Promise<void> {\n\tawait new Promise<void>((resolve) => {\n\t\tsetTimeout(resolve, ms);\n\t});\n}\n\nfunction fileLabel(gistId: string, environment: string): string {\n\treturn `gist:${gistId}/state.${environment}.json`;\n}\n\nfunction fileName(environment: string): string {\n\treturn `state.${environment}.json`;\n}\n\nfunction toGistFile(entry: unknown): GistFile | undefined {\n\tif (typeof entry !== \"object\" || entry === null) {\n\t\treturn undefined;\n\t}\n\n\tconst record = entry as Record<string, unknown>;\n\tconst content = typeof record[\"content\"] === \"string\" ? record[\"content\"] : undefined;\n\tconst rawUrl = typeof record[\"raw_url\"] === \"string\" ? record[\"raw_url\"] : undefined;\n\tconst size = typeof record[\"size\"] === \"number\" ? record[\"size\"] : 0;\n\tconst isTruncated = record[\"truncated\"] === true;\n\treturn { content, isTruncated, rawUrl, size };\n}\n\nfunction mapHttpError({ file, gistId, status }: HttpFailure): StateError {\n\tif (status === 404) {\n\t\treturn { file, kind: \"stateError\", reason: `gist ${gistId} not found: check gistId` };\n\t}\n\n\tif (status === 401 || status === 403) {\n\t\treturn { file, kind: \"stateError\", reason: `auth failed (${status}): check token scopes` };\n\t}\n\n\treturn { file, kind: \"stateError\", reason: `github returned ${status}` };\n}\n\nfunction networkError(error: unknown, file: string): StateError {\n\tconst message = error instanceof Error ? error.message : String(error);\n\treturn { file, kind: \"stateError\", reason: `network error: ${message}` };\n}\n\nfunction buildHeaders(token: string): Headers {\n\tconst headers = new Headers();\n\theaders.set(\"Accept\", \"application/vnd.github+json\");\n\theaders.set(\"Authorization\", `Bearer ${token}`);\n\theaders.set(\"User-Agent\", USER_AGENT);\n\theaders.set(\"X-GitHub-Api-Version\", GITHUB_API_VERSION);\n\treturn headers;\n}\n\nasync function sendGet(ctx: AdapterContext): Promise<Response> {\n\treturn ctx.fetchFn(`${GITHUB_API_BASE}/gists/${ctx.gistId}`, {\n\t\theaders: buildHeaders(ctx.token),\n\t\tmethod: \"GET\",\n\t});\n}\n\nfunction isRetryableStatus(status: number): boolean {\n\treturn RETRYABLE_STATUSES.has(status);\n}\n\nfunction backoffMs(attempt: number): number {\n\treturn 1000 * 2 ** attempt;\n}\n\nasync function withRetry(\n\tsleep: (ms: number) => Promise<void>,\n\toperation: () => Promise<Response>,\n): Promise<Response> {\n\tlet response = await operation();\n\tfor (let attempt = 0; attempt < MAX_RETRIES; attempt += 1) {\n\t\tif (response.ok || !isRetryableStatus(response.status)) {\n\t\t\treturn response;\n\t\t}\n\n\t\tawait sleep(backoffMs(attempt));\n\t\tresponse = await operation();\n\t}\n\n\treturn response;\n}\n\nasync function fetchGistBody(\n\tctx: AdapterContext,\n\tfile: string,\n): Promise<Result<Record<string, unknown>, StateError>> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await withRetry(ctx.sleep, async () => sendGet(ctx));\n\t} catch (err) {\n\t\treturn { err: networkError(err, file), success: false };\n\t}\n\n\tif (!response.ok) {\n\t\treturn {\n\t\t\terr: mapHttpError({ file, gistId: ctx.gistId, status: response.status }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst body = (await response.json()) as Record<string, unknown>;\n\treturn { data: body, success: true };\n}\n\nfunction stateErr<T>(file: string, reason: string): Result<T, StateError> {\n\treturn { err: { file, kind: \"stateError\", reason }, success: false };\n}\n\nasync function readGistContent({\n\tentry,\n\tfetchFn,\n\tfile,\n\tsleep,\n}: ReadContentParameters): Promise<Result<BedrockState | undefined, StateError>> {\n\tif (entry.size > MAX_INLINE_BYTES) {\n\t\treturn stateErr(file, `state file too large: ${entry.size} bytes`);\n\t}\n\n\tif (entry.isTruncated) {\n\t\tif (entry.rawUrl === undefined) {\n\t\t\treturn stateErr(file, \"truncated gist file missing raw_url\");\n\t\t}\n\n\t\tconst { rawUrl } = entry;\n\t\tlet rawResponse: Response;\n\t\ttry {\n\t\t\trawResponse = await withRetry(sleep, async () => fetchFn(rawUrl));\n\t\t} catch (err) {\n\t\t\treturn { err: networkError(err, file), success: false };\n\t\t}\n\n\t\tif (!rawResponse.ok) {\n\t\t\treturn stateErr(file, `raw_url fetch returned ${rawResponse.status}`);\n\t\t}\n\n\t\tconst raw = await rawResponse.text();\n\t\treturn parseStateFile(raw, file);\n\t}\n\n\treturn parseStateFile(entry.content, file);\n}\n\nasync function readPath(\n\tctx: AdapterContext,\n\tenvironment: string,\n): Promise<Result<BedrockState | undefined, StateError>> {\n\tconst file = fileLabel(ctx.gistId, environment);\n\tconst gist = await fetchGistBody(ctx, file);\n\tif (!gist.success) {\n\t\treturn gist;\n\t}\n\n\tconst files = gist.data[\"files\"] as Record<string, unknown> | undefined;\n\tconst entry = toGistFile(files?.[fileName(environment)]);\n\tif (entry === undefined) {\n\t\treturn { data: undefined, success: true };\n\t}\n\n\treturn readGistContent({ entry, fetchFn: ctx.fetchFn, file, sleep: ctx.sleep });\n}\n\nasync function sendPatch(ctx: AdapterContext, body: string): Promise<Response> {\n\tconst headers = buildHeaders(ctx.token);\n\theaders.set(\"Content-Type\", \"application/json\");\n\treturn ctx.fetchFn(`${GITHUB_API_BASE}/gists/${ctx.gistId}`, {\n\t\tbody,\n\t\theaders,\n\t\tmethod: \"PATCH\",\n\t});\n}\n\nasync function writePath(\n\tctx: AdapterContext,\n\tstate: BedrockState,\n): Promise<Result<void, StateError>> {\n\tconst file = fileLabel(ctx.gistId, state.environment);\n\tconst body = JSON.stringify({\n\t\tfiles: { [fileName(state.environment)]: { content: serializeStateFile(state) } },\n\t});\n\n\tlet response: Response;\n\ttry {\n\t\tresponse = await withRetry(ctx.sleep, async () => sendPatch(ctx, body));\n\t} catch (err) {\n\t\treturn { err: networkError(err, file), success: false };\n\t}\n\n\tif (response.ok) {\n\t\treturn { data: undefined, success: true };\n\t}\n\n\tif (response.status === 422) {\n\t\treturn stateErr(file, \"invalid PATCH body sent to github\");\n\t}\n\n\treturn {\n\t\terr: mapHttpError({ file, gistId: ctx.gistId, status: response.status }),\n\t\tsuccess: false,\n\t};\n}\n","import type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport {\n\tasResourceKey,\n\ttype ResourceKey,\n\ttype RobloxAssetId,\n\ttype Sha256Hex,\n} from \"../types/ids.ts\";\n\n/**\n * Desired state for a game pass, as declared in user config.\n *\n * Each field is `readonly` because desired state is treated as an immutable\n * snapshot once normalized by `buildDesired`; downstream consumers (`diff`,\n * drivers) must not mutate it.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, asSha256Hex, type GamePassDesiredState } from \"@bedrock-rbx/core\";\n *\n * const pass: GamePassDesiredState = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: undefined,\n * };\n *\n * expect(pass.kind).toBe(\"gamePass\");\n * expect(pass.price).toBeUndefined();\n * ```\n */\nexport interface GamePassDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing game-pass name as shown on the Roblox storefront. */\n\treadonly name: string;\n\t/** User-facing description shown on the game-pass detail page. */\n\treadonly description: string;\n\t/**\n\t * Locale-keyed icon paths declared on the authored config. The Roblox\n\t * game-pass API is monolingual, so only the `\"en-us\"` icon is ever\n\t * uploaded; the map shape mirrors `UniverseDesiredState.icon` for\n\t * cross-kind parity.\n\t */\n\treadonly icon: Record<\"en-us\", string>;\n\t/**\n\t * SHA-256 digests of the local icon files keyed by the same locales as\n\t * the icon map. The diff compares this map against the prior current\n\t * state so the driver re-uploads only when a file's bytes change.\n\t */\n\treadonly iconFileHashes: Record<\"en-us\", Sha256Hex>;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"gamePass\";\n\t/**\n\t * Robux price. `undefined` means off-sale (mirrors Mantle's `Option<u32>`;\n\t * the state parser normalizes JSON `null` to `undefined` at the wire\n\t * boundary per the project type convention).\n\t */\n\treadonly price: number | undefined;\n}\n\n/**\n * Desired state for a place, the `.rbxl` or `.rbxlx` file a universe serves\n * as one of its levels.\n *\n * `placeId` sits on desired state (rather than on outputs like\n * {@link GamePassOutputs.assetId}) because Roblox Open Cloud cannot mint\n * places; the user supplies the existing place ID per entry. `filePath` and\n * `fileHash` describe the local file the driver publishes; `buildDesired`\n * computes `fileHash` from the file bytes so `diff` can detect drift without\n * re-uploading unchanged content. `displayName`, `description`, and\n * `serverSize` are optional metadata fields routed through\n * `PlacesClient.update`; `undefined` leaves the server value untouched.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * type PlaceDesiredState,\n * } from \"@bedrock-rbx/core\";\n *\n * const place: PlaceDesiredState = {\n * description: undefined,\n * displayName: \"Start Place\",\n * fileHash: asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: 50,\n * };\n *\n * expect(place.kind).toBe(\"place\");\n * expect(place.displayName).toBe(\"Start Place\");\n * expect(place.description).toBeUndefined();\n * expect(place.serverSize).toBe(50);\n * ```\n */\nexport interface PlaceDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing place description; `undefined` leaves the server value untouched. */\n\treadonly description: string | undefined;\n\t/** User-facing place name; `undefined` leaves the server value untouched. */\n\treadonly displayName: string | undefined;\n\t/** SHA-256 hex digest of the place file, computed by `buildDesired` in shell. */\n\treadonly fileHash: Sha256Hex;\n\t/** Path to the `.rbxl` or `.rbxlx` file on disk, relative to the config file. */\n\treadonly filePath: string;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"place\";\n\t/** Existing Roblox place ID; Open Cloud cannot create places, so this is an input, not an output. */\n\treadonly placeId: RobloxAssetId;\n\t/** Maximum players per server; positive integer. `undefined` leaves the server value untouched. */\n\treadonly serverSize: number | undefined;\n}\n\n/**\n * Ordered list of optional metadata fields the driver routes through\n * `PlacesClient.update`. Iterated by `placeKind.fieldsEqual` and the place\n * driver's parameter translator so drift detection and the constructed\n * `updateMask` cannot drift apart.\n */\nexport const PLACE_MANAGED_METADATA_FIELDS = [\n\t\"displayName\",\n\t\"description\",\n\t\"serverSize\",\n] as const satisfies ReadonlyArray<keyof PlaceDesiredState>;\n\n/**\n * Roblox-returned value produced by publishing a place version. The publish\n * endpoint does not return an asset ID (the `placeId` is supplied by the\n * caller); `versionNumber` is the only Roblox-assigned field the response\n * carries.\n */\nexport interface PlaceOutputs {\n\t/** Auto-incrementing version number assigned by Roblox on every publish. */\n\treadonly versionNumber: number;\n}\n\n/**\n * Desired state for the singleton universe a config manages.\n *\n * The universe is adopted rather than provisioned: the user supplies an\n * existing `universeId` (Open Cloud cannot mint universes) and bedrock\n * reconciles the declared managed fields against it.\n *\n * Most managed fields use `T | undefined` to mean \"unmanaged\": the diff\n * treats `undefined` as absent and the driver omits the field from the\n * `updateMask`. The clearable fields (`privateServerPriceRobux` and each\n * social link) are additionally key-presence aware: a present key with\n * `undefined` tells the driver to clear the server value (ocale emits\n * JSON `null`), while an absent key leaves the server value untouched.\n *\n * @example\n *\n * ```ts\n * import {\n * asRobloxAssetId,\n * UNIVERSE_SINGLETON_KEY,\n * type UniverseDesiredState,\n * } from \"@bedrock-rbx/core\";\n *\n * const universe: UniverseDesiredState = {\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * discordSocialLink: { title: \"Join our Discord\", uri: \"https://discord.gg/example\" },\n * displayName: \"Fun Universe\",\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: false,\n * privateServerPriceRobux: undefined,\n * tabletEnabled: undefined,\n * twitterSocialLink: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * };\n *\n * expect(universe.kind).toBe(\"universe\");\n * expect(\"privateServerPriceRobux\" in universe).toBeTrue();\n * expect(universe.discordSocialLink?.title).toBe(\"Join our Discord\");\n * expect(universe.twitterSocialLink).toBeUndefined();\n * ```\n */\nexport interface UniverseDesiredState {\n\t/** Fixed singleton key (`\"main\"`); bedrock synthesizes it in `flattenConfig`. */\n\treadonly key: ResourceKey;\n\t/** Whether console players can join; `undefined` leaves the server value untouched. */\n\treadonly consoleEnabled: boolean | undefined;\n\t/** Whether desktop players can join; `undefined` leaves the server value untouched. */\n\treadonly desktopEnabled: boolean | undefined;\n\t/** Discord social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly discordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. `undefined` leaves the server\n\t * value untouched. The driver routes declared updates through\n\t * `PlacesClient.update` because the universe PATCH endpoint treats\n\t * `displayName` as read-only.\n\t */\n\treadonly displayName: string | undefined;\n\t/** Facebook social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly facebookSocialLink?: SocialLink | undefined;\n\t/** Guilded social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly guildedSocialLink?: SocialLink | undefined;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"universe\";\n\t/** Whether mobile players can join; `undefined` leaves the server value untouched. */\n\treadonly mobileEnabled: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. A present key with `undefined`\n\t * clears the server value on apply; an absent key leaves the server\n\t * value untouched.\n\t */\n\treadonly privateServerPriceRobux?: number | undefined;\n\t/** Roblox Group social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly robloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; `undefined` leaves the server value untouched. */\n\treadonly tabletEnabled: boolean | undefined;\n\t/** Twitch social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly twitchSocialLink?: SocialLink | undefined;\n\t/** Twitter social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly twitterSocialLink?: SocialLink | undefined;\n\t/** User-supplied Roblox universe ID; the universe must already exist. */\n\treadonly universeId: RobloxAssetId;\n\t/** Whether voice chat is enabled; `undefined` leaves the server value untouched. */\n\treadonly voiceChatEnabled: boolean | undefined;\n\t/** Whether VR players can join; `undefined` leaves the server value untouched. */\n\treadonly vrEnabled: boolean | undefined;\n\t/** YouTube social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly youtubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * Ordered list of optional boolean managed fields on {@link UniverseDesiredState}.\n *\n * The driver translator and the diff's per-field equality guard both iterate\n * this list so they cannot drift apart. Order drives `updateMask` sequence in\n * generated requests.\n */\nexport const UNIVERSE_MANAGED_FLAGS = [\n\t\"desktopEnabled\",\n\t\"mobileEnabled\",\n\t\"tabletEnabled\",\n\t\"consoleEnabled\",\n\t\"vrEnabled\",\n\t\"voiceChatEnabled\",\n] as const satisfies ReadonlyArray<keyof UniverseDesiredState>;\n\n/** Key of an optional boolean managed field on {@link UniverseDesiredState}. */\nexport type UniverseManagedFlag = (typeof UNIVERSE_MANAGED_FLAGS)[number];\n\n/**\n * Tuple of every social link field name on {@link UniverseDesiredState}.\n * Iterated by flatten, driver, and diff to handle the tri-state clearable\n * semantics uniformly across all seven fields.\n */\nexport const SOCIAL_LINK_FIELDS = [\n\t\"discordSocialLink\",\n\t\"facebookSocialLink\",\n\t\"guildedSocialLink\",\n\t\"robloxGroupSocialLink\",\n\t\"twitchSocialLink\",\n\t\"twitterSocialLink\",\n\t\"youtubeSocialLink\",\n] as const satisfies ReadonlyArray<keyof UniverseDesiredState>;\n\n/** Union of the seven social link field names on {@link UniverseDesiredState}. */\nexport type SocialLinkField = (typeof SOCIAL_LINK_FIELDS)[number];\n\n/**\n * Desired state for a developer product, the consumable a player can buy via\n * `MarketplaceService:PromptProductPurchase`.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type DeveloperProductDesiredState } from \"@bedrock-rbx/core\";\n *\n * const product: DeveloperProductDesiredState = {\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * isRegionalPricingEnabled: true,\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * price: 100,\n * storePageEnabled: true,\n * };\n *\n * expect(product.kind).toBe(\"developerProduct\");\n * expect(product.price).toBe(100);\n * ```\n */\nexport interface DeveloperProductDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing developer product name as shown on the storefront. */\n\treadonly name: string;\n\t/** User-facing description shown on the developer product detail page. */\n\treadonly description: string;\n\t/**\n\t * Locale-keyed icon paths declared on the authored config. Absent when\n\t * the user did not declare an icon block. The Roblox developer-product\n\t * API is monolingual, so only the `\"en-us\"` icon is ever uploaded; the\n\t * map shape mirrors `GamePassDesiredState.icon` for cross-kind parity.\n\t */\n\treadonly icon?: Record<\"en-us\", string>;\n\t/**\n\t * SHA-256 digests of the local icon files keyed by the same locales as\n\t * the icon map. The diff compares this map against the prior current\n\t * state so the driver re-uploads only when a file's bytes change. Absent\n\t * when `icon` is absent.\n\t */\n\treadonly iconFileHashes?: Record<\"en-us\", Sha256Hex>;\n\t/**\n\t * Whether Roblox-managed regional pricing applies to the product.\n\t * Tri-state: `undefined` means the flag is unmanaged (the diff ignores\n\t * it); a defined value is propagated to Roblox on every deploy.\n\t */\n\treadonly isRegionalPricingEnabled: boolean | undefined;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"developerProduct\";\n\t/**\n\t * Robux price. `undefined` means off-sale; removing the field from config\n\t * takes the product off-sale on the next deploy, re-adding puts it back\n\t * on sale.\n\t */\n\treadonly price: number | undefined;\n\t/**\n\t * Whether the product appears on the universe's external store page.\n\t * Tri-state: `undefined` means the flag is unmanaged. A defined value is\n\t * applied via a follow-up PATCH after the create POST because the v2\n\t * create endpoint does not accept this field.\n\t */\n\treadonly storePageEnabled: boolean | undefined;\n}\n\n/**\n * Discriminated union of every desired-state shape Bedrock manages.\n *\n * Extend by adding new members to this union; the mapped\n * `ResourceOutputsByKind` interface then forces a matching outputs entry for\n * the new kind at compile time.\n */\nexport type ResourceDesiredState =\n\t| DeveloperProductDesiredState\n\t| GamePassDesiredState\n\t| PlaceDesiredState\n\t| UniverseDesiredState;\n\n/**\n * Roblox-returned identifiers produced by creating or updating a game pass.\n *\n * Distinct from `GamePassDesiredState.key`: the desired-state key is a\n * user-supplied handle, while these IDs are assigned by Roblox and\n * discovered only after the first successful API call.\n */\nexport interface GamePassOutputs {\n\t/** Primary Roblox asset ID for the game pass itself. */\n\treadonly assetId: RobloxAssetId;\n\t/**\n\t * Locale-keyed Roblox-assigned image IDs for the game-pass icons. The\n\t * Roblox game-pass API is monolingual, so the `\"en-us\"` entry is the\n\t * only one ever populated; the map shape leaves room for translated\n\t * icons should the API ever expose them.\n\t */\n\treadonly iconAssetIds: Record<\"en-us\", RobloxAssetId>;\n}\n\n/**\n * Roblox-returned identifiers produced by creating or updating a developer\n * product. `productId` is what `MarketplaceService:PromptProductPurchase`\n * accepts and is stable across re-deploys. `iconImageAssetId` is optional\n * because the wire returns it as nullable when no icon is uploaded; slice 1\n * never populates it because icon support lands in a later slice.\n */\nexport interface DeveloperProductOutputs {\n\t/** Roblox asset ID of the uploaded icon image; `undefined` when no icon is uploaded. */\n\treadonly iconImageAssetId?: RobloxAssetId | undefined;\n\t/** Roblox-assigned developer product ID; stable across re-deploys. */\n\treadonly productId: RobloxAssetId;\n}\n\n/**\n * Roblox-returned value produced by reconciling a universe. The root place\n * ID is server-authoritative: bedrock cannot set it directly, but records it\n * so a future places slice can cross-validate the declared start place.\n */\nexport interface UniverseOutputs {\n\t/** Server-assigned root place ID for the universe. */\n\treadonly rootPlaceId: RobloxAssetId;\n}\n\n/**\n * String union of every discriminator tag in `ResourceDesiredState`.\n *\n * Derived from the union rather than hand-maintained so adding a new\n * `ResourceDesiredState` member automatically widens this type.\n */\nexport type ResourceKind = ResourceDesiredState[\"kind\"];\n\n/**\n * Per-kind outputs registry. Each `ResourceKind` must have a matching entry\n * or `ResourceOutputs<K>` is a compile error. Modelled as an interface (not a\n * type alias) so downstream packages can use declaration merging to register\n * outputs for new kinds without touching this module.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId, type ResourceOutputsByKind } from \"@bedrock-rbx/core\";\n *\n * const outputs: ResourceOutputsByKind[\"gamePass\"] = {\n * assetId: asRobloxAssetId(\"9876543210\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"1122334455\") },\n * };\n *\n * expect(outputs.assetId).toBe(\"9876543210\");\n * ```\n */\nexport interface ResourceOutputsByKind {\n\t/** Outputs returned by the Roblox API for a developer-product resource. */\n\tdeveloperProduct: DeveloperProductOutputs;\n\t/** Outputs returned by the Roblox API for a game-pass resource. */\n\tgamePass: GamePassOutputs;\n\t/** Outputs returned by the Roblox API for a place publish. */\n\tplace: PlaceOutputs;\n\t/** Outputs returned by the Roblox API for a universe reconcile. */\n\tuniverse: UniverseOutputs;\n}\n\n/**\n * Resolved outputs for a specific resource kind.\n *\n * @template K - The resource kind discriminator.\n */\nexport type ResourceOutputs<K extends ResourceKind> = ResourceOutputsByKind[K];\n\n/**\n * Current (live) state for a resource kind.\n *\n * Composed from the matching desired-state shape plus a nested `outputs`\n * object carrying Roblox-assigned identifiers. The outer `K extends\n * ResourceKind` conditional distributes `K` across the union so the default\n * `ResourceCurrentState` resolves to a clean per-kind union rather than a\n * cross-product intersection of every kind's fields.\n *\n * The `outputs` sub-object stays nested (rather than flattening into the\n * top level) to mirror Mantle's `{ inputs, outputs }` state layout,\n * keeping migration copy clean.\n *\n * @template K - The resource kind discriminator. Defaults to the full\n * `ResourceKind` union for the broad form used in `ReadonlyArray`\n * collections.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * type ResourceCurrentState,\n * } from \"@bedrock-rbx/core\";\n *\n * const current: ResourceCurrentState<\"gamePass\"> = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * outputs: {\n * assetId: asRobloxAssetId(\"9876543210\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"1122334455\") },\n * },\n * price: 500,\n * };\n *\n * expect(current.outputs.assetId).toBe(\"9876543210\");\n * expect(current.kind).toBe(\"gamePass\");\n * ```\n */\nexport type ResourceCurrentState<K extends ResourceKind = ResourceKind> = K extends ResourceKind\n\t? Prettify<\n\t\t\tExtract<ResourceDesiredState, { kind: K }> & { readonly outputs: ResourceOutputs<K> }\n\t\t>\n\t: never;\n\ntype Prettify<T> = { readonly [K in keyof T]: T[K] };\n\ntype WithOptionalSocialLinks = Readonly<Partial<Record<SocialLinkField, SocialLink | undefined>>>;\n\n/**\n * Copy every social link field that is present as a key on `source`,\n * preserving the tri-state distinction between \"key absent\" (unmanaged,\n * omitted from result) and \"key present with `undefined`\" (cleared,\n * forwarded as-is). Shared by flatten, build-desired, and the universe\n * driver so all three layers propagate the same tri-state semantics.\n *\n * @param source - Object whose declared social link keys should be copied.\n * @returns Partial record containing only the social link keys present on\n * `source`; absent keys stay absent.\n */\nexport function copyDeclaredSocialLinks(\n\tsource: WithOptionalSocialLinks,\n): Partial<Record<SocialLinkField, SocialLink | undefined>> {\n\tconst copied: Partial<Record<SocialLinkField, SocialLink | undefined>> = {};\n\tfor (const field of SOCIAL_LINK_FIELDS) {\n\t\tif (field in source) {\n\t\t\tcopied[field] = source[field];\n\t\t}\n\t}\n\n\treturn copied;\n}\n\n/**\n * Fixed stable key for the singleton universe resource. `flattenConfig`\n * stamps this onto the sole `UniverseDesiredInput` it emits; fixtures and\n * state adapters share the constant so the invariant is encoded once.\n *\n * @example\n *\n * ```ts\n * import { UNIVERSE_SINGLETON_KEY } from \"@bedrock-rbx/core\";\n *\n * expect(UNIVERSE_SINGLETON_KEY).toBe(\"main\");\n * ```\n */\nexport const UNIVERSE_SINGLETON_KEY: ResourceKey = asResourceKey(\"main\");\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { PlacesClient, UpdatePlaceParameters } from \"@bedrock-rbx/ocale/places\";\n\nimport type { PlaceDesiredState, PlaceOutputs, ResourceCurrentState } from \"../core/resources.ts\";\nimport { PLACE_MANAGED_METADATA_FIELDS } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport type { RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createPlaceDriver`. `universeId` is captured at\n * construction time (matching `GamePassDriverDeps`) so each driver instance\n * is bound to a single universe; multi-universe deploys construct one driver\n * per universe. `readFile` is injected because `diff` operates on file hashes\n * while the driver is the only place that needs the raw bytes.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import { asRobloxAssetId, type PlaceDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: PlaceDriverDeps = {\n * client: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array(),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface PlaceDriverDeps {\n\t/** Configured places client from `@bedrock-rbx/ocale/places`. */\n\treadonly client: PlacesClient;\n\t/** Reads place-file bytes for upload; rejections propagate out of the driver. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every place this driver publishes. */\n\treadonly universeId: RobloxAssetId;\n}\n\n/**\n * Wraps {@link PlacesClient} as a `ResourceDriver<\"place\">`. `create` and\n * `update` are both thin wrappers over a shared publish helper because the\n * upstream Open Cloud call is identical either way: there is no \"create\n * place\" endpoint (the place is user-supplied input), only \"publish version\".\n *\n * Format is detected from the file extension (`.rbxl` → binary,\n * `.rbxlx` → XML); any other extension returns an `ApiError`-backed failure\n * without hitting the network.\n *\n * @param deps - Injected ocale client, file reader, and owning universe.\n * @returns A driver indexable by `\"place\"` in a `DriverRegistry`.\n * @throws Whatever `deps.readFile` rejects with.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * createPlaceDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: { body: { versionNumber: 1 }, headers: {}, status: 200 },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createPlaceDriver({\n * client: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () =>\n * new Uint8Array([\n * 0x3c, 0x72, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x21, 0x89, 0xff, 0x0d, 0x0a, 0x1a,\n * 0x0a,\n * ]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: undefined,\n * displayName: undefined,\n * fileHash: asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.versionNumber).toBe(1);\n * }\n * });\n * ```\n */\nexport function createPlaceDriver(deps: PlaceDriverDeps): ResourceDriver<\"place\"> {\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn publishPlace(deps, desired);\n\t\t},\n\t\tasync update(_current, desired) {\n\t\t\treturn publishPlace(deps, desired);\n\t\t},\n\t};\n}\n\nfunction buildMetadataParameters(\n\tuniverseId: RobloxAssetId,\n\tdesired: PlaceDesiredState,\n): undefined | UpdatePlaceParameters {\n\tconst metadata = PLACE_MANAGED_METADATA_FIELDS.reduce<Partial<UpdatePlaceParameters>>(\n\t\t(accumulator, field) => {\n\t\t\tconst value = desired[field];\n\t\t\treturn value === undefined ? accumulator : { ...accumulator, [field]: value };\n\t\t},\n\t\t{},\n\t);\n\n\tif (Object.keys(metadata).length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn { ...metadata, placeId: desired.placeId, universeId };\n}\n\nfunction detectFormat(filePath: string): \"rbxl\" | \"rbxlx\" | undefined {\n\tif (filePath.endsWith(\".rbxlx\")) {\n\t\treturn \"rbxlx\";\n\t}\n\n\tif (filePath.endsWith(\".rbxl\")) {\n\t\treturn \"rbxl\";\n\t}\n\n\treturn undefined;\n}\n\nasync function publishVersion(\n\tdeps: PlaceDriverDeps,\n\tdesired: PlaceDesiredState,\n): Promise<Result<PlaceOutputs, OpenCloudError>> {\n\tconst format = detectFormat(desired.filePath);\n\tif (format === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t`Unsupported place file extension for ${desired.filePath}; expected .rbxl or .rbxlx`,\n\t\t\t\t{ statusCode: 0 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst body = await deps.readFile(desired.filePath);\n\treturn deps.client.publish({\n\t\t// Narrows `Uint8Array<ArrayBufferLike>` to `Uint8Array<ArrayBuffer>`\n\t\t// so the ocale wire type rejects SharedArrayBuffer at the call site.\n\t\tbody: Uint8Array.from(body),\n\t\tformat,\n\t\tplaceId: desired.placeId,\n\t\tuniverseId: deps.universeId,\n\t});\n}\n\nasync function publishPlace(\n\tdeps: PlaceDriverDeps,\n\tdesired: PlaceDesiredState,\n): Promise<Result<ResourceCurrentState<\"place\">, OpenCloudError>> {\n\tconst publishResult = await publishVersion(deps, desired);\n\tif (!publishResult.success) {\n\t\treturn publishResult;\n\t}\n\n\tconst metadataParameters = buildMetadataParameters(deps.universeId, desired);\n\tif (metadataParameters !== undefined) {\n\t\tconst metadataResult = await deps.client.update(metadataParameters);\n\t\tif (!metadataResult.success) {\n\t\t\treturn metadataResult;\n\t\t}\n\t}\n\n\treturn { data: { ...desired, outputs: publishResult.data }, success: true };\n}\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { PlacesClient } from \"@bedrock-rbx/ocale/places\";\nimport type { UniversesClient, UpdateUniverseParameters } from \"@bedrock-rbx/ocale/universes\";\n\nimport {\n\tcopyDeclaredSocialLinks,\n\ttype ResourceCurrentState,\n\tSOCIAL_LINK_FIELDS,\n\tUNIVERSE_MANAGED_FLAGS,\n\ttype UniverseDesiredState,\n} from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createUniverseDriver`. The driver reconciles the\n * universe singleton against both the universes endpoint and the root\n * place (for fields Roblox marks read-only on the universe, like\n * `displayName`). There is no `universeId` at construction time because\n * the universe *is* the resource the driver reconciles, so the ID rides\n * along on each `UniverseDesiredState`.\n */\nexport interface UniverseDriverDeps {\n\t/** Configured places client from `@bedrock-rbx/ocale/places`. */\n\treadonly places: PlacesClient;\n\t/** Configured universes client from `@bedrock-rbx/ocale/universes`. */\n\treadonly universes: UniversesClient;\n}\n\ninterface ResolvedUniverse {\n\treadonly rootPlaceId: string;\n}\n\ninterface ReconcileInputs {\n\treadonly deps: UniverseDriverDeps;\n\treadonly desired: UniverseDesiredState;\n}\n\n/**\n * Wraps {@link UniversesClient} as a `ResourceDriver<\"universe\">`. `create`\n * and `update` both delegate to a shared reconcile helper because Open\n * Cloud cannot mint universes; the user supplies an existing `universeId`\n * and bedrock adopts the universe on first apply.\n *\n * A `NotFound` error (HTTP 404) from `UniversesClient.update` is repackaged\n * as an adoption-error `ApiError` whose message names the config key and\n * the `universeId`, so operators can tell adoption failure apart from\n * transient upstream errors. A successful response whose `rootPlaceId` is\n * absent surfaces as an `ApiError` with status 200, mirroring the\n * malformed-response guard in `GamePassDriver`.\n *\n * When `displayName` is declared, the driver routes that field through\n * `PlacesClient.update` on the root place after the universe PATCH\n * succeeds. A subsequent places failure surfaces to the caller as the\n * driver's error result without rolling back the prior universe patch,\n * so callers observing a partial failure should reconcile by\n * reapplying rather than assuming the universe-level fields are\n * unchanged.\n *\n * @param deps - Injected ocale clients (universes plus places for the\n * read-only universe fields Roblox derives from the root place).\n * @returns A driver indexable by `\"universe\"` in a `DriverRegistry`.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import { UniversesClient } from \"@bedrock-rbx/ocale/universes\";\n * import { validUniverseBody } from \"@bedrock-rbx/ocale/testing\";\n * import {\n * asRobloxAssetId,\n * createUniverseDriver,\n * UNIVERSE_SINGLETON_KEY,\n * } from \"@bedrock-rbx/core\";\n *\n * const universeBodyHttpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: validUniverseBody({\n * path: \"universes/1234567890\",\n * rootPlace: \"universes/1234567890/places/4711\",\n * }),\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createUniverseDriver({\n * places: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient: universeBodyHttpClient,\n * sleep: async () => {},\n * }),\n * universes: new UniversesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient: universeBodyHttpClient,\n * sleep: async () => {},\n * }),\n * });\n *\n * return driver\n * .create({\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * displayName: undefined,\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: undefined,\n * privateServerPriceRobux: undefined,\n * tabletEnabled: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.rootPlaceId).toBe(\"4711\");\n * }\n * });\n * ```\n */\nexport function createUniverseDriver(deps: UniverseDriverDeps): ResourceDriver<\"universe\"> {\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn reconcileUniverse({ deps, desired });\n\t\t},\n\t\tasync update(_current, desired) {\n\t\t\treturn reconcileUniverse({ deps, desired });\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: UniverseDesiredState,\n\trootPlaceId: string,\n): ResourceCurrentState<\"universe\"> {\n\treturn {\n\t\t...desired,\n\t\toutputs: { rootPlaceId: asRobloxAssetId(rootPlaceId) },\n\t};\n}\n\nfunction buildParameters(desired: UniverseDesiredState): UpdateUniverseParameters {\n\tconst base = UNIVERSE_MANAGED_FLAGS.reduce<UpdateUniverseParameters>(\n\t\t(accumulator, flag) => {\n\t\t\tconst isEnabled = desired[flag];\n\t\t\treturn isEnabled === undefined ? accumulator : { ...accumulator, [flag]: isEnabled };\n\t\t},\n\t\t{ universeId: desired.universeId },\n\t);\n\n\tconst withPrice =\n\t\t\"privateServerPriceRobux\" in desired\n\t\t\t? { ...base, privateServerPriceRobux: desired.privateServerPriceRobux }\n\t\t\t: base;\n\n\treturn { ...withPrice, ...copyDeclaredSocialLinks(desired) };\n}\n\nfunction wrapUpdateError(err: OpenCloudError, desired: UniverseDesiredState): OpenCloudError {\n\tif (err instanceof ApiError && err.statusCode === 404) {\n\t\treturn new ApiError(\n\t\t\t`Universe ${desired.universeId} (key '${desired.key}') was not found; adoption failed`,\n\t\t\t{ statusCode: 404 },\n\t\t);\n\t}\n\n\treturn err;\n}\n\nfunction hasUniverseLevelUpdate(desired: UniverseDesiredState): boolean {\n\tif (UNIVERSE_MANAGED_FLAGS.some((flag) => desired[flag] !== undefined)) {\n\t\treturn true;\n\t}\n\n\tif (\"privateServerPriceRobux\" in desired) {\n\t\treturn true;\n\t}\n\n\treturn SOCIAL_LINK_FIELDS.some((field) => field in desired);\n}\n\nasync function resolveUniverse(\n\tdeps: UniverseDriverDeps,\n\tdesired: UniverseDesiredState,\n): Promise<Result<ResolvedUniverse, OpenCloudError>> {\n\tconst result = hasUniverseLevelUpdate(desired)\n\t\t? await deps.universes.update(buildParameters(desired))\n\t\t: await deps.universes.get({ universeId: desired.universeId });\n\n\tif (!result.success) {\n\t\treturn { err: wrapUpdateError(result.err, desired), success: false };\n\t}\n\n\tconst { rootPlaceId } = result.data;\n\tif (rootPlaceId === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t`Malformed universe response for ${desired.universeId}: rootPlaceId missing`,\n\t\t\t\t{ statusCode: 200 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: { rootPlaceId }, success: true };\n}\n\nasync function reconcileUniverse(\n\tinputs: ReconcileInputs,\n): Promise<Result<ResourceCurrentState<\"universe\">, OpenCloudError>> {\n\tconst { deps, desired } = inputs;\n\tconst universeResult = await resolveUniverse(deps, desired);\n\tif (!universeResult.success) {\n\t\treturn universeResult;\n\t}\n\n\tconst { rootPlaceId } = universeResult.data;\n\tif (desired.displayName !== undefined) {\n\t\tconst placesResult = await deps.places.update({\n\t\t\tdisplayName: desired.displayName,\n\t\t\tplaceId: rootPlaceId,\n\t\t\tuniverseId: desired.universeId,\n\t\t});\n\t\tif (!placesResult.success) {\n\t\t\treturn { err: placesResult.err, success: false };\n\t\t}\n\t}\n\n\treturn { data: toCurrentState(desired, rootPlaceId), success: true };\n}\n","import type { EnvironmentEntry, UniverseEntry } from \"./schema.ts\";\n\n/**\n * Loose authored-shape that the runtime narrow operates on before the\n * config gets cast to the discriminated `Config` union. Mirrors the\n * arktype schema's inferred shape: every field is structurally a\n * supertype of every `Config` variant arm, so the narrow can run before\n * the XOR rule has discriminated the value into one arm.\n */\ninterface LooseConfigForValidation {\n\treadonly environments: Record<string, EnvironmentEntry>;\n\treadonly universe?: UniverseEntry;\n}\n\ninterface UniverseIdIssue {\n\treadonly message: string;\n\treadonly path: ReadonlyArray<string>;\n}\n\n/**\n * Walk the loose authored-shape and surface every place the\n * universeId-XOR-between-root-and-env rule is violated. Pure: returns\n * the issue list; the caller hands it to arktype's `ctx.reject` so each\n * one lands at the offending config path. The schema's runtime narrow\n * uses this to enforce the rule at validation time before the validated\n * value is cast to the strict `Config` discriminated union.\n *\n * @param value - Parsed config the schema is validating.\n * @returns Zero or more issues. Empty when the config satisfies the rule.\n */\nexport function collectUniverseIdIssues(\n\tvalue: LooseConfigForValidation,\n): ReadonlyArray<UniverseIdIssue> {\n\tconst rootUniverseId = value.universe?.universeId;\n\tconst hasRootUniverseBlock = value.universe !== undefined;\n\tconst environmentEntries = Object.entries(value.environments);\n\tconst hasEnvironmentUniverseId = environmentEntries.some(\n\t\t([, environment]) => environment.universe?.universeId !== undefined,\n\t);\n\n\tconst environmentIssues = collectEnvironmentIssues(rootUniverseId, environmentEntries);\n\tconst rootIssues =\n\t\thasRootUniverseBlock && rootUniverseId === undefined && !hasEnvironmentUniverseId\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\"universeId must be declared on the root universe block, or on every environment that declares its own universe overlay.\",\n\t\t\t\t\t\tpath: [\"universe\", \"universeId\"],\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [];\n\n\treturn [...environmentIssues, ...rootIssues];\n}\n\nfunction collectEnvironmentIssues(\n\trootUniverseId: string | undefined,\n\tenvironmentEntries: ReadonlyArray<readonly [string, EnvironmentEntry]>,\n): ReadonlyArray<UniverseIdIssue> {\n\treturn environmentEntries.flatMap(([environmentName, environment]) => {\n\t\tif (environment.universe === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (rootUniverseId !== undefined && environment.universe.universeId !== undefined) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"universeId is declared at the root universe block; remove it from this environment overlay (root is authoritative) or remove it from the root and declare it on every environment.\",\n\t\t\t\t\tpath: [\"environments\", environmentName, \"universe\", \"universeId\"],\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\tif (rootUniverseId === undefined && environment.universe.universeId === undefined) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"universeId must be declared on this environment overlay because the root universe block does not provide one.\",\n\t\t\t\t\tpath: [\"environments\", environmentName, \"universe\", \"universeId\"],\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\treturn [];\n\t});\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\nimport type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport { ArkErrors, type, type Type } from \"arktype\";\nimport type { SetRequired } from \"type-fest\";\n\nimport { RESOURCE_KEY_PATTERN_SOURCE } from \"../types/ids.ts\";\nimport type { ConfigError } from \"./config-error.ts\";\nimport { ENV_NAME_PATTERN_SOURCE } from \"./environment.ts\";\nimport { iconMap } from \"./icons.ts\";\nimport { collectUniverseIdIssues } from \"./validate-universe-xor.ts\";\n\n/**\n * Body of a single entry in the `passes` collection. Keys in the parent\n * record are `ResourceKey`-shaped strings enforced at schema validation.\n */\nexport interface GamePassEntry {\n\t/** Name shown on the Roblox storefront. */\n\tname: string;\n\t/** Description shown on the game-pass detail page. */\n\tdescription: string;\n\t/**\n\t * Locale-keyed icon path. The Roblox game-pass API is monolingual, so\n\t * only the `\"en-us\"` key is accepted; the map shape leaves room for\n\t * translated icons should the API ever expose them.\n\t */\n\ticon: Record<\"en-us\", string>;\n\t/** Robux price, or omitted / `undefined` for off-sale. */\n\tprice?: number | undefined;\n}\n\n/**\n * Body of a single entry in the `products` collection. Keys in the parent\n * record are `ResourceKey`-shaped strings enforced at schema validation.\n */\nexport interface DeveloperProductEntry {\n\t/** Name shown on the Roblox storefront. */\n\tname: string;\n\t/** Description shown on the developer-product detail page. */\n\tdescription: string;\n\t/**\n\t * Locale-keyed icon path. Mirrors `GamePassEntry.icon`; the Roblox\n\t * developer-product API is monolingual, so only the `\"en-us\"` key is\n\t * accepted.\n\t */\n\ticon?: Record<\"en-us\", string>;\n\t/**\n\t * Whether Roblox-managed regional pricing applies to the product.\n\t * Tri-state: omit (or set `undefined`) to leave the flag unmanaged;\n\t * setting `true` or `false` is propagated to Roblox on every deploy.\n\t */\n\tisRegionalPricingEnabled?: boolean | undefined;\n\t/**\n\t * Robux price. Omit (or set `undefined`) for an off-sale product;\n\t * re-adding the field puts the product back on sale on the next deploy.\n\t */\n\tprice?: number | undefined;\n\t/**\n\t * Whether the product appears on the universe's external store page.\n\t * Tri-state: omit (or set `undefined`) to leave the flag unmanaged.\n\t * The Roblox v2 create endpoint does not accept this field, so the\n\t * driver applies it via a follow-up PATCH after the create POST.\n\t */\n\tstorePageEnabled?: boolean | undefined;\n}\n\n/**\n * Body of a single entry under the root `places` collection. Carries the\n * file-path environments share plus the optional Open-Cloud-supported\n * metadata fields. The Roblox `placeId` is environment-specific and lives\n * on each per-environment overlay so the same `.rbxl` file can publish to\n * different places across staging, production, and so on.\n */\nexport interface PlaceEntry {\n\t/** User-facing description shown on the place's detail page. */\n\tdescription?: string | undefined;\n\t/** User-facing place name shown on the Roblox storefront. */\n\tdisplayName?: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; handed to `readFile` verbatim by `buildDesired`. */\n\tfilePath: string;\n\t/** Maximum players per server; positive integer. */\n\tserverSize?: number | undefined;\n}\n\n/**\n * Body of a places entry after `selectEnvironment` has merged the\n * matching per-environment overlay onto the root entry. `filePath` flows\n * from the root (or an overlay override), `placeId` is supplied by the\n * per-environment overlay, and the optional metadata fields fall through\n * from the root unless overridden per-environment.\n *\n * `placeId` is user-supplied because Open Cloud cannot mint places; the\n * place must already exist in Roblox before Bedrock can publish versions\n * to it.\n */\nexport interface ResolvedPlaceEntry {\n\t/** User-facing description shown on the place's detail page. */\n\tdescription?: string | undefined;\n\t/** User-facing place name shown on the Roblox storefront. */\n\tdisplayName?: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; handed to `readFile` verbatim by `buildDesired`. */\n\tfilePath: string;\n\t/** Existing Roblox place ID. */\n\tplaceId: string;\n\t/** Maximum players per server; positive integer. */\n\tserverSize?: number | undefined;\n}\n\n/**\n * Body of the singleton `universe` block. Bedrock synthesizes the\n * `ResourceKey` (`\"main\"`) in `flattenConfig`, so user config supplies\n * only the existing `universeId` plus any managed fields they want\n * bedrock to own. Fields omitted here remain unmanaged (the diff treats\n * them as non-drift and the driver omits them from the `updateMask`).\n *\n * `universeId` is user-supplied because Open Cloud cannot mint universes;\n * the universe must already exist in Roblox before bedrock can reconcile\n * its configuration. Declare `universeId` either here at the root (which\n * applies to every environment) or under each `environments[name].universe`\n * overlay, but never both: the schema rejects a config that sets it in\n * both places, and rejects a `universe` block without a resolvable\n * `universeId`.\n */\nexport interface UniverseEntry {\n\t/** Whether console players can join; omit or set `undefined` to leave unmanaged. */\n\tconsoleEnabled?: boolean | undefined;\n\t/** Whether desktop players can join; omit or set `undefined` to leave unmanaged. */\n\tdesktopEnabled?: boolean | undefined;\n\t/**\n\t * Discord social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tdiscordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. Because Roblox derives this from\n\t * the root place's name, the driver routes the update through\n\t * `PlacesClient.update`; omit or set `undefined` to leave unmanaged.\n\t */\n\tdisplayName?: string | undefined;\n\t/**\n\t * Facebook social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tfacebookSocialLink?: SocialLink | undefined;\n\t/**\n\t * Guilded social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tguildedSocialLink?: SocialLink | undefined;\n\t/** Whether mobile players can join; omit or set `undefined` to leave unmanaged. */\n\tmobileEnabled?: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. Declare as `undefined` to disable\n\t * private servers (cancels active subscriptions); omit to leave the\n\t * server value untouched.\n\t */\n\tprivateServerPriceRobux?: number | undefined;\n\t/**\n\t * Roblox Group social link; omit to leave the server value untouched, set\n\t * to `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\trobloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; omit or set `undefined` to leave unmanaged. */\n\ttabletEnabled?: boolean | undefined;\n\t/**\n\t * Twitch social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\ttwitchSocialLink?: SocialLink | undefined;\n\t/**\n\t * Twitter social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\ttwitterSocialLink?: SocialLink | undefined;\n\t/**\n\t * Existing Roblox universe ID. Optional in this entry shape because\n\t * authors may declare it here (root-authoritative, single universe) or\n\t * on each `environments[name].universe` overlay (per-environment\n\t * universes), but never both.\n\t */\n\tuniverseId?: string | undefined;\n\t/** Whether voice chat is enabled; omit or set `undefined` to leave unmanaged. */\n\tvoiceChatEnabled?: boolean | undefined;\n\t/** Whether VR players can join; omit or set `undefined` to leave unmanaged. */\n\tvrEnabled?: boolean | undefined;\n\t/**\n\t * YouTube social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tyoutubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * State configuration for the GitHub Gist backend. Holds the public gist\n * ID; the GitHub token is read from `GITHUB_TOKEN` only when the library\n * default-constructs the adapter.\n */\nexport interface GistStateConfig {\n\t/** Discriminator selecting the gist adapter. */\n\treadonly backend: \"gist\";\n\t/** ID of an existing GitHub Gist that holds this project's state files. */\n\treadonly gistId: string;\n}\n\n/**\n * Tagged union describing where Bedrock persists its state. The `backend`\n * tag is `\"gist\" | (string & {})` so unknown names autocomplete the\n * builtins while permitting custom values for plugin scenarios. The\n * dispatch path inside `deploy()` rejects unknown names with a typed\n * `unsupportedBackend` error.\n */\nexport type StateConfig = GistStateConfig | { readonly backend: string & {} };\n\n/**\n * Body of a single entry under `environments`. Per-environment overrides\n * narrow root-level settings for that environment without redefining\n * unrelated fields. Resource overlays (`passes`, `places`, `universe`)\n * derive their field shapes from the matching root entry types so adding\n * a field to a base entry surfaces on the overlay automatically.\n *\n * `placeId` stays required when the matching `places` overlay is present\n * because each environment targets its own Roblox place. `universeId` is\n * optional on the `universe` overlay because authors may declare it\n * either at the root (root-authoritative) or per environment, but never\n * both: the schema enforces this XOR at validation time, attributing the\n * failure to the offending field's path.\n */\nexport interface EnvironmentEntry {\n\t/**\n\t * Human-readable label fed to the project-level\n\t * {@link DisplayNamePrefixConfig.format | displayNamePrefix.format}\n\t * template. An environment without a label (or with an empty string)\n\t * is implicitly excluded from prefixing even when the project enables\n\t * it.\n\t */\n\tlabel?: string | undefined;\n\t/**\n\t * Per-environment game-pass overlay. Every field is optional; missing\n\t * fields fall through to the matching root `passes` entry at merge time.\n\t *\n\t * Uses `Partial<GamePassEntry>` directly rather than `Overlay<T, K>`\n\t * because game passes have no user-supplied identity key (Open Cloud\n\t * mints the asset ID). The other overlay fields use `Overlay<T, K>`\n\t * to keep their identity-bearing key required.\n\t */\n\tpasses?: Record<string, Partial<GamePassEntry>>;\n\t/**\n\t * Per-environment places overlay. `placeId` is required on every\n\t * declared entry; `filePath` is optional and falls through to the\n\t * matching root `places` entry when omitted.\n\t */\n\tplaces?: Record<string, Overlay<ResolvedPlaceEntry, \"placeId\">>;\n\t/**\n\t * Per-environment developer-product overlay. Every field is optional;\n\t * missing fields fall through to the matching root `products` entry at\n\t * merge time. Mirrors the `passes` shape because developer products\n\t * also have no user-supplied identity key (Open Cloud mints the\n\t * `productId`).\n\t */\n\tproducts?: Record<string, Partial<DeveloperProductEntry>>;\n\t/** Per-environment state override; takes precedence over root `state`. */\n\tstate?: StateConfig;\n\t/**\n\t * Per-environment universe overlay. Every field is optional, including\n\t * `universeId`: the schema-level XOR rule requires `universeId` here if\n\t * and only if the root `universe` block does not declare one. Other\n\t * fields fall through to the root `universe` block when omitted.\n\t */\n\tuniverse?: Partial<UniverseEntry>;\n}\n\n/**\n * Per-kind entry registry. Each `ResourceKind` must have a matching entry\n * type or `ResourceEntryByKind[K]` is a compile error. Modelled as an\n * interface (not a type alias) so downstream resource kinds can declare\n * their entry type alongside the kind's other domain types without\n * touching this module.\n *\n * @example\n *\n * ```ts\n * import type { ResourceEntryByKind } from \"@bedrock-rbx/core/config\";\n *\n * const entry: ResourceEntryByKind[\"gamePass\"] = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * };\n *\n * expect(entry.name).toBe(\"VIP Pass\");\n * ```\n */\nexport interface ResourceEntryByKind {\n\t/** Authored entry body for a developer-product resource. */\n\tdeveloperProduct: DeveloperProductEntry;\n\t/** Authored entry body for a game-pass resource. */\n\tgamePass: GamePassEntry;\n\t/** Post-merge entry body for a place resource (root + env overlay). */\n\tplace: ResolvedPlaceEntry;\n\t/** Authored entry body for a universe resource. */\n\tuniverse: UniverseEntry;\n}\n\n/**\n * Project-level prefixing policy for universe and place display names.\n * Each environment's `label` flows through `format` to render a prefix\n * that `selectEnvironment` prepends to every declared display name.\n *\n * Defaults: `enabled` is `true`; `format` is `\"[{LABEL}] \"`.\n */\nexport interface DisplayNamePrefixConfig {\n\t/**\n\t * Whether the project applies environment-label prefixing. Treat\n\t * `undefined` as enabled; set `false` to opt out across the project.\n\t */\n\tenabled?: boolean | undefined;\n\t/**\n\t * Template string applied to each environment's `label`. Placeholders:\n\t *\n\t * - `{label}`: label as written.\n\t * - `{LABEL}`: upper-cased label.\n\t * - `{Label}`: capitalized label (first character upper, rest as\n\t * written).\n\t *\n\t * Any other characters in the template flow through verbatim. The\n\t * rendered string is prepended to each declared `displayName`.\n\t */\n\tformat?: string | undefined;\n}\n\n/**\n * Per-environment universe overlay shape that prevents `universeId` from\n * being redeclared alongside a root-authoritative `universeId`.\n * Used by {@link ConfigRootUniverseId}: when the root universe block\n * declares `universeId`, no per-env overlay may redeclare it. Setting\n * `universeId` here produces a descriptive type error pointing at this\n * field rather than the opaque `never` message.\n */\nexport type UniverseOverlayWithoutId = Partial<WithoutKey<UniverseEntry, \"universeId\">> & {\n\tuniverseId?: \"universeId is already declared on the root universe block; remove it from this environment overlay, or remove it from root and declare it on every environment overlay instead\" & {\n\t\treadonly errorBrand: never;\n\t};\n};\n\n/**\n * Per-environment universe overlay shape that requires `universeId`.\n * Used by {@link ConfigEnvironmentUniverseId}: when the root universe\n * block does not declare `universeId`, every env that declares a\n * `universe` overlay must supply one of its own.\n */\nexport type UniverseOverlayWithId = Partial<WithoutKey<UniverseEntry, \"universeId\">> & {\n\tuniverseId: string;\n};\n\n/**\n * Variant of `Config` where the root `universe` block declares\n * `universeId`. Per-environment universe overlays may carry shared\n * fields (device flags, social links, display name) but cannot\n * redeclare `universeId`; the schema rejects any env overlay that\n * does. The runtime `selectEnvironment` merges shared-field overlays\n * onto the root and inherits `universeId` from the root unchanged.\n */\nexport type ConfigRootUniverseId = ConfigBase & {\n\t/**\n\t * Per-environment overrides keyed by environment name. Required and\n\t * non-empty; environment names match `[A-Za-z0-9_-]{1,64}`. Each env\n\t * entry's `universe` overlay forbids `universeId` because the root\n\t * declares it.\n\t */\n\tenvironments: Record<\n\t\tstring,\n\t\tWithoutKey<EnvironmentEntry, \"universe\"> & { universe?: UniverseOverlayWithoutId }\n\t>;\n\t/**\n\t * Singleton universe block declaring the Roblox universe bedrock\n\t * manages. `universeId` is required in this variant because no\n\t * per-environment overlay may supply one.\n\t */\n\tuniverse?: UniverseEntry & { universeId: string };\n};\n\n/**\n * Variant of `Config` where the root `universe` block omits\n * `universeId`. Every env that declares a `universe` overlay must\n * supply its own `universeId`; envs that omit the overlay deploy no\n * universe at all. The root may still carry shared fields (device\n * flags, social links, display name, icon) which `selectEnvironment`\n * merges onto each env's overlay at resolution time.\n */\nexport type ConfigEnvironmentUniverseId = ConfigBase & {\n\t/**\n\t * Per-environment overrides keyed by environment name. Required and\n\t * non-empty; environment names match `[A-Za-z0-9_-]{1,64}`. Every\n\t * env that declares a `universe` overlay must include `universeId`\n\t * because the root universe block does not provide one.\n\t */\n\tenvironments: Record<\n\t\tstring,\n\t\tWithoutKey<EnvironmentEntry, \"universe\"> & { universe?: UniverseOverlayWithId }\n\t>;\n\t/**\n\t * Singleton universe block declaring the Roblox universe bedrock\n\t * manages. `universeId` is not permitted here in this variant because\n\t * every environment supplies its own; setting it produces a descriptive\n\t * type error rather than the opaque `never` message.\n\t */\n\tuniverse?: WithoutKey<UniverseEntry, \"universeId\"> & {\n\t\tuniverseId?: \"universeId is already declared per environment; remove it from the root universe block, or remove it from every environment overlay and declare it here instead\" & {\n\t\t\treadonly errorBrand: never;\n\t\t};\n\t};\n};\n\n/**\n * Validated project config as accepted by `loadConfig`. Plain mutable so\n * users can adjust fields in a long-running script before deploying.\n *\n * Discriminated union over the location of `universeId`: it lives at the\n * root universe block ({@link ConfigRootUniverseId}) or on every\n * environment universe overlay ({@link ConfigEnvironmentUniverseId}), but never\n * both. The TypeScript types reject the both-set case at compile time,\n * and the arktype runtime narrow rejects every offending field path at\n * `validateConfig` time. State must be configured at the root or under\n * every entry of `environments`; `resolveStateConfig` surfaces the\n * missing case at the deploy boundary as `stateNotConfigured`.\n *\n * @example\n *\n * ```ts\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc123def456\" },\n * passes: {\n * \"vip-pass\": {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * };\n *\n * expect(config.passes![\"vip-pass\"]!.name).toBe(\"VIP Pass\");\n * ```\n */\nexport type Config = ConfigEnvironmentUniverseId | ConfigRootUniverseId;\n\n/**\n * Body of the singleton `universe` block after `selectEnvironment` has\n * merged a per-environment overlay onto the root. Identical to\n * {@link UniverseEntry} except `universeId` is required: the schema-level\n * XOR rule ensures every projected universe carries a resolved\n * `universeId`. Resource drivers consume this shape rather than\n * `UniverseEntry` so the post-merge invariant is visible in the type\n * system.\n */\nexport interface ResolvedUniverseEntry extends Pick<\n\tUniverseEntry,\n\tExclude<keyof UniverseEntry, \"universeId\">\n> {\n\t/** Existing Roblox universe ID, resolved from the root or per-environment overlay. */\n\tuniverseId: string;\n}\n\n/**\n * Project config after `selectEnvironment` has merged a single\n * environment's overlays onto the root. The shape mirrors `Config`\n * except `places` carries `ResolvedPlaceEntry` (both `filePath` and\n * `placeId`), since the resolver fails before this point if an entry is\n * missing its environment-supplied `placeId`. Downstream consumers\n * (`flattenConfig`, `buildDefaultRegistry`, the deploy pipeline) accept\n * this shape rather than `Config` so the post-merge invariant is visible\n * in the type system.\n *\n * @example\n *\n * ```ts\n * import { selectEnvironment, type ResolvedConfig } from \"@bedrock-rbx/core\";\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: {\n * production: { places: { \"start-place\": { placeId: \"4711\" } } },\n * },\n * places: { \"start-place\": { filePath: \"places/start.rbxl\" } },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * };\n *\n * const result = selectEnvironment(config, \"production\");\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * const resolved: ResolvedConfig = result.data;\n * expect(resolved.places?.[\"start-place\"]?.placeId).toBe(\"4711\");\n * }\n * ```\n */\nexport interface ResolvedConfig extends Pick<ConfigBase, Exclude<keyof ConfigBase, \"places\">> {\n\t/**\n\t * Per-environment overrides preserved from the source `Config`.\n\t * Carried for downstream context; `selectEnvironment` does not read\n\t * other environments after resolving the requested one.\n\t */\n\tenvironments: Record<string, EnvironmentEntry>;\n\t/** Keyed-map collection of resolved place entries; both `filePath` and `placeId` are present. */\n\tplaces?: Record<string, ResolvedPlaceEntry>;\n\t/**\n\t * Singleton universe block after `selectEnvironment` has resolved the\n\t * XOR between root and per-environment `universeId`. The schema narrow\n\t * rejects any config that would leave `universeId` unresolved, so the\n\t * post-merge invariant promotes `universeId` from optional to required.\n\t */\n\tuniverse?: ResolvedUniverseEntry;\n}\n\n/**\n * Overlay shape used by per-environment entries: every field of `T`\n * becomes optional, except `RequiredKey`, which stays required so the\n * overlay still re-asserts the identity-bearing field of its target\n * resource.\n *\n * @template T - Base entry type whose field shapes the overlay derives from.\n * @template RequiredKey - Identity-bearing key on `T` that the overlay must\n * still declare (for example `\"placeId\"` or `\"universeId\"`).\n */\ntype Overlay<T, RequiredKey extends keyof T> = SetRequired<Partial<T>, RequiredKey>;\n\n/**\n * Helper that produces a shallow `Omit<T, K>` without using TypeScript's\n * built-in `Omit` (deprecated under the project's lint rules because of\n * its lossy interaction with mapped types).\n *\n * @template T - Source type to project keys away from.\n * @template Key - Key (or union of keys) on `T` to remove.\n */\ntype WithoutKey<T, Key extends keyof T> = Pick<T, Exclude<keyof T, Key>>;\n\n/**\n * Fields shared by every {@link Config} variant. The discriminated\n * `Config` union narrows `universe` and `environments` to enforce the\n * `universeId` XOR rule between the root and per-environment overlays;\n * everything else lives here.\n */\ninterface ConfigBase {\n\t/**\n\t * Project-level prefixing of universe and place display names with the\n\t * environment label. Default behaviour (when omitted) is enabled with a\n\t * `\"[{LABEL}] \"` template; set `enabled: false` to opt out, or set\n\t * `format` to a custom template.\n\t */\n\tdisplayNamePrefix?: DisplayNamePrefixConfig;\n\t/** Reserved at the root for c12's config layering / overlay work. */\n\textends?: unknown;\n\t/** Keyed-map collection of game-pass entries by user-supplied ResourceKey. */\n\tpasses?: Record<string, GamePassEntry>;\n\t/** Keyed-map collection of place entries by user-supplied ResourceKey. */\n\tplaces?: Record<string, PlaceEntry>;\n\t/** Keyed-map collection of developer-product entries by user-supplied ResourceKey. */\n\tproducts?: Record<string, DeveloperProductEntry>;\n\t/** Where Bedrock persists state for this project; required at deploy time. */\n\tstate?: StateConfig;\n}\n\n/**\n * Narrow a `StateConfig` to the `GistStateConfig` arm. The `(string & {})`\n * autocomplete idiom prevents TypeScript from narrowing on\n * `backend === \"gist\"` alone, so dispatch sites use this guard to\n * preserve the `gistId` field shape.\n *\n * @example\n *\n * ```ts\n * import { isGistStateConfig } from \"@bedrock-rbx/core\";\n * import type { StateConfig } from \"@bedrock-rbx/core/config\";\n *\n * const config: StateConfig = { backend: \"gist\", gistId: \"abc\" };\n *\n * expect(isGistStateConfig(config)).toBeTrue();\n * if (isGistStateConfig(config)) {\n * expect(config.gistId).toBe(\"abc\");\n * }\n * ```\n *\n * @param config - Resolved state config to inspect.\n * @returns `true` when `config.backend === \"gist\"`; otherwise `false`.\n */\nexport function isGistStateConfig(config: StateConfig): config is GistStateConfig {\n\treturn config.backend === \"gist\";\n}\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst OPTIONAL_STRING = \"string | undefined\";\n\n/**\n * Shared arktype constraint for any optional positive-integer field.\n * Reused by per-kind entry schemas so positive-integer fields validate\n * identically.\n */\nexport const OPTIONAL_POSITIVE_INTEGER = \"(number.integer >= 1) | undefined\";\n\n/**\n * Shared arktype constraint for any optional Robux-price field. The schema\n * rejects negatives, fractional values, `NaN`, and `Infinity` at config\n * validation time so a malformed price surfaces with a path attributing the\n * failure to the offending field, rather than slipping through to the\n * Roblox API and surfacing as an opaque error at apply time. Per-kind entry\n * schemas reuse this constant so all Robux-price fields validate\n * identically.\n */\nexport const OPTIONAL_ROBUX_PRICE = \"number.integer >= 0 | undefined\";\n\n// Resource-kind entry schemas. Adding a new kind is two additions:\n// 1. Declare its entry schema and keyed-map collection below.\n// 2. Reference that collection as an optional property on `rootSchema`.\n// No existing entries change. The ResourceKey regex lives on the map key\n// signature so invalid identifiers surface as schema failures pointing at\n// the offending key, not as deferred errors downstream.\nconst gamePassEntry = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon\": iconMap,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n});\n\nconst passesCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: gamePassEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst developerProductEntry = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n}).onUndeclaredKey(\"reject\");\n\nconst productsCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: developerProductEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst ROBLOX_ID_DIGITS = \"string.digits\";\n\nconst placeEntry = type({\n\t\"description?\": OPTIONAL_STRING,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"filePath\": \"string\",\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nconst placesCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: placeEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst socialLink = type({\n\ttitle: \"string\",\n\turi: \"string\",\n}).onUndeclaredKey(\"reject\");\n\nconst socialLinkOrUndefined = socialLink.or(\"undefined\");\n\nconst universeEntry = type({\n\t\"consoleEnabled?\": OPTIONAL_BOOLEAN,\n\t\"desktopEnabled?\": OPTIONAL_BOOLEAN,\n\t\"discordSocialLink?\": socialLinkOrUndefined,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"facebookSocialLink?\": socialLinkOrUndefined,\n\t\"guildedSocialLink?\": socialLinkOrUndefined,\n\t\"mobileEnabled?\": OPTIONAL_BOOLEAN,\n\t\"privateServerPriceRobux?\": OPTIONAL_ROBUX_PRICE,\n\t\"robloxGroupSocialLink?\": socialLinkOrUndefined,\n\t\"tabletEnabled?\": OPTIONAL_BOOLEAN,\n\t\"twitchSocialLink?\": socialLinkOrUndefined,\n\t\"twitterSocialLink?\": socialLinkOrUndefined,\n\t\"universeId?\": ROBLOX_ID_DIGITS,\n\t\"voiceChatEnabled?\": OPTIONAL_BOOLEAN,\n\t\"vrEnabled?\": OPTIONAL_BOOLEAN,\n\t\"youtubeSocialLink?\": socialLinkOrUndefined,\n}).onUndeclaredKey(\"reject\");\n\nconst stateConfig = type({\n\t\"backend\": \"string\",\n\t\"gistId?\": \"string > 0\",\n}).onUndeclaredKey(\"reject\");\n\n// Overlay schemas mirror the base entry schemas but with every field\n// optional, except the identity-bearing key (`placeId`, `universeId`)\n// which stays required. Game passes have no user-supplied identity, so\n// the overlay is fully partial.\nconst gamePassOverlay = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n}).onUndeclaredKey(\"reject\");\n\nconst passesOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: gamePassOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst developerProductOverlay = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n}).onUndeclaredKey(\"reject\");\n\nconst productsOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: developerProductOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst placeOverlay = type({\n\t\"description?\": OPTIONAL_STRING,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"filePath?\": \"string\",\n\t\"placeId\": ROBLOX_ID_DIGITS,\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nconst placesOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: placeOverlay,\n}).onUndeclaredKey(\"reject\");\n\n// `Partial<UniverseEntry>` is structurally equal to `UniverseEntry`\n// itself because every field on `UniverseEntry` is optional. Reusing\n// `universeEntry` here keeps the field set in lockstep and avoids a\n// parallel declaration to drift. The XOR rule that ties root and\n// per-environment `universeId` together lives on `rootSchema` below,\n// where both sides of the relationship are in scope.\nconst universeOverlay = universeEntry;\n\nconst environmentEntry: Type<EnvironmentEntry> = type({\n\t\"label?\": OPTIONAL_STRING,\n\t\"passes?\": passesOverlayCollection,\n\t\"places?\": placesOverlayCollection,\n\t\"products?\": productsOverlayCollection,\n\t\"state?\": stateConfig,\n\t\"universe?\": universeOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst displayNamePrefix: Type<DisplayNamePrefixConfig> = type({\n\t\"enabled?\": OPTIONAL_BOOLEAN,\n\t\"format?\": OPTIONAL_STRING,\n}).onUndeclaredKey(\"reject\");\n\nconst environmentsCollection = type({\n\t[`[/${ENV_NAME_PATTERN_SOURCE}/]`]: environmentEntry,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(\"an environments record with at least one declared environment\");\n\t\t}\n\n\t\treturn true;\n\t});\n\n// `rootSchema` is intentionally not annotated `Type<Config>` because\n// `Config` is a discriminated union enforcing the universeId XOR rule\n// at the type level. The arktype schema describes the loose\n// authored-shape that's structurally a supertype of every union arm;\n// the runtime narrow rejects any value that doesn't satisfy one arm so\n// `validateConfig` can cast the result to `Config` safely. Splitting\n// the schema into two `.or()` variants would mirror the type union but\n// duplicate every field declaration without buying additional runtime\n// coverage on top of the narrow.\nconst rootSchema = type({\n\t\"displayNamePrefix?\": displayNamePrefix,\n\t\"environments\": environmentsCollection,\n\t\"extends?\": \"unknown\",\n\t\"passes?\": passesCollection,\n\t\"places?\": placesCollection,\n\t\"products?\": productsCollection,\n\t\"state?\": stateConfig,\n\t\"universe?\": universeEntry,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\t// `ctx.reject` returns `false` for every issue and `reduce` walks the\n\t\t// whole list so every offending field gets attributed; the seeded\n\t\t// `true` flips to `false` on the first issue.\n\t\treturn collectUniverseIdIssues(value).reduce<boolean>((_accumulator, issue) => {\n\t\t\treturn ctx.reject({ message: issue.message, path: [...issue.path] });\n\t\t}, true);\n\t});\n\n/**\n * Validate a parsed config value against the runtime schema. Returns the\n * validated `Config` on success or a `validationFailed` `ConfigError` with\n * one issue per problem, each attributed to a field path. `sourceFile`\n * appears in the error so callers can point a human at the offending file.\n *\n * @param input - Parsed value from a config source (object tree from a\n * config loader, or a hand-built literal). Shape is checked, not assumed.\n * @param sourceFile - Path or identifier of the source file, used in the\n * `validationFailed` error.\n * @returns `Ok` with the validated `Config`, or `Err` with a\n * `validationFailed` error carrying each issue's field path.\n * @example\n *\n * ```ts\n * import { validateConfig } from \"@bedrock-rbx/core\";\n *\n * const ok = validateConfig(\n * {\n * environments: { production: {} },\n * passes: {\n * \"vip-pass\": {\n * description: \"VIP perks.\",\n * icon: { \"en-us\": \"assets/vip.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * },\n * \"bedrock.config.ts\",\n * );\n * expect(ok.success).toBeTrue();\n *\n * const err = validateConfig(\n * { environments: { production: {} }, passes: { \"vip-pass\": { name: \"VIP\" } } },\n * \"bedrock.config.ts\",\n * );\n * expect(err.success).toBeFalse();\n * if (!err.success) {\n * expect(err.err.kind).toBe(\"validationFailed\");\n * }\n * ```\n */\nexport function validateConfig(input: unknown, sourceFile: string): Result<Config, ConfigError> {\n\tconst validated = rootSchema(input);\n\tif (validated instanceof ArkErrors) {\n\t\tconst issues = Array.from(validated, (issue) => {\n\t\t\treturn {\n\t\t\t\tmessage: issue.message,\n\t\t\t\tpath: [...issue.path].map((segment) => String(segment)),\n\t\t\t};\n\t\t});\n\n\t\treturn {\n\t\t\terr: { issues, kind: \"validationFailed\", sourceFile },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\t// Precondition for the cast: the runtime narrow rejects every value\n\t// that violates the universeId XOR rule, so a successful validation\n\t// always lands in one arm of the discriminated `Config` union. The\n\t// schema's inferred type is the structurally loose authored-shape\n\t// (universeId optional in both root and per-env overlays); the cast\n\t// collapses it to the strict union without loss of safety.\n\treturn { data: validated as unknown as Config, success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey } from \"../../types/ids.ts\";\nimport type { DeveloperProductDesiredInput } from \"../flatten.ts\";\nimport { hashIconLocales, iconHashesEqual, iconMap } from \"../icons.ts\";\nimport type { DeveloperProductDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_ROBUX_PRICE, type ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst entrySchema = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n});\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<DeveloperProductDesiredInput> {\n\treturn Object.entries(config.products ?? {}).map<DeveloperProductDesiredInput>(\n\t\t([key, entry]) => {\n\t\t\tconst base: DeveloperProductDesiredInput = {\n\t\t\t\tkey: asResourceKey(key),\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tisRegionalPricingEnabled: entry.isRegionalPricingEnabled,\n\t\t\t\tkind: \"developerProduct\",\n\t\t\t\tprice: entry.price,\n\t\t\t\tstorePageEnabled: entry.storePageEnabled,\n\t\t\t};\n\t\t\treturn entry.icon === undefined ? base : { ...base, icon: entry.icon };\n\t\t},\n\t);\n}\n\nasync function normalize(\n\tinput: DeveloperProductDesiredInput,\n\tio: KindIo,\n): Promise<Result<DeveloperProductDesiredState, BuildDesiredError>> {\n\tconst base: DeveloperProductDesiredState = {\n\t\tkey: input.key,\n\t\tname: input.name,\n\t\tdescription: input.description,\n\t\tisRegionalPricingEnabled: input.isRegionalPricingEnabled,\n\t\tkind: \"developerProduct\",\n\t\tprice: input.price,\n\t\tstorePageEnabled: input.storePageEnabled,\n\t};\n\n\tif (input.icon === undefined) {\n\t\treturn { data: base, success: true };\n\t}\n\n\tconst hashes = await hashIconLocales({ key: input.key, icon: input.icon }, io);\n\tif (!hashes.success) {\n\t\treturn hashes;\n\t}\n\n\treturn {\n\t\tdata: { ...base, icon: input.icon, iconFileHashes: hashes.data },\n\t\tsuccess: true,\n\t};\n}\n\nfunction fieldsEqual(\n\tdesired: DeveloperProductDesiredState,\n\tcurrent: ResourceCurrentState<\"developerProduct\">,\n): boolean {\n\t// `isRegionalPricingEnabled` and `storePageEnabled` are tri-state:\n\t// `undefined` on the desired side means the user does not manage the\n\t// field, so any current value is accepted as a match.\n\treturn (\n\t\tdesired.description === current.description &&\n\t\tdesired.icon?.[\"en-us\"] === current.icon?.[\"en-us\"] &&\n\t\ticonHashesEqual(current.iconFileHashes, desired.iconFileHashes) &&\n\t\tdesired.name === current.name &&\n\t\tdesired.price === current.price &&\n\t\t(desired.isRegionalPricingEnabled === undefined ||\n\t\t\tdesired.isRegionalPricingEnabled === current.isRegionalPricingEnabled) &&\n\t\t(desired.storePageEnabled === undefined ||\n\t\t\tdesired.storePageEnabled === current.storePageEnabled)\n\t);\n}\n\nfunction assertReconcilable(\n\tcurrent: ResourceCurrentState<\"developerProduct\">,\n\tdesired: DeveloperProductDesiredState,\n): Result<undefined, BuildDesiredError> {\n\tif (current.iconFileHashes !== undefined && desired.iconFileHashes === undefined) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkey: desired.key,\n\t\t\t\tkind: \"iconRemovalRejected\",\n\t\t\t\tmessage: `developer product '${desired.key}' had an icon recorded in state, but the desired entry no longer declares one. The Roblox developer-product API has no documented way to unset an icon; remove the resource entry to delete the product, or restore the icon path to keep the existing image.`,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: undefined, success: true };\n}\n\n/**\n * Resource-kind module for Roblox developer products. Owns the entry\n * schema, flattening, icon-hash normalization, drift-equality, and the\n * plan-time icon-removal rejection for the `developerProduct` kind.\n */\nexport const developerProductKind: ResourceKindModule<\"developerProduct\"> = {\n\tassertReconcilable,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"developerProduct\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey } from \"../../types/ids.ts\";\nimport type { GamePassDesiredInput } from \"../flatten.ts\";\nimport { hashIconLocales, iconHashesEqual, iconMap } from \"../icons.ts\";\nimport type { GamePassDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_ROBUX_PRICE, type ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst entrySchema = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon\": iconMap,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n});\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<GamePassDesiredInput> {\n\treturn Object.entries(config.passes ?? {}).map<GamePassDesiredInput>(([key, entry]) => {\n\t\treturn {\n\t\t\tkey: asResourceKey(key),\n\t\t\tname: entry.name,\n\t\t\tdescription: entry.description,\n\t\t\ticon: entry.icon,\n\t\t\tkind: \"gamePass\",\n\t\t\tprice: entry.price,\n\t\t};\n\t});\n}\n\nasync function normalize(\n\tinput: GamePassDesiredInput,\n\tio: KindIo,\n): Promise<Result<GamePassDesiredState, BuildDesiredError>> {\n\tconst hashes = await hashIconLocales({ key: input.key, icon: input.icon }, io);\n\tif (!hashes.success) {\n\t\treturn hashes;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tkey: input.key,\n\t\t\tname: input.name,\n\t\t\tdescription: input.description,\n\t\t\ticon: input.icon,\n\t\t\ticonFileHashes: hashes.data,\n\t\t\tkind: \"gamePass\",\n\t\t\tprice: input.price,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction fieldsEqual(\n\tdesired: GamePassDesiredState,\n\tcurrent: ResourceCurrentState<\"gamePass\">,\n): boolean {\n\treturn (\n\t\tdesired.description === current.description &&\n\t\tdesired.icon[\"en-us\"] === current.icon[\"en-us\"] &&\n\t\ticonHashesEqual(current.iconFileHashes, desired.iconFileHashes) &&\n\t\tdesired.name === current.name &&\n\t\tdesired.price === current.price\n\t);\n}\n\n/**\n * Resource-kind module for Roblox game passes. Owns the entry schema,\n * flattening, icon-hash normalization, and drift-equality for the\n * `gamePass` kind.\n */\nexport const gamePassKind: ResourceKindModule<\"gamePass\"> = {\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"gamePass\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey, asRobloxAssetId, asSha256Hex } from \"../../types/ids.ts\";\nimport type { PlaceDesiredInput } from \"../flatten.ts\";\nimport { PLACE_MANAGED_METADATA_FIELDS } from \"../resources.ts\";\nimport type { PlaceDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_POSITIVE_INTEGER } from \"../schema.ts\";\nimport type { ResolvedConfig } from \"../schema.ts\";\nimport { sha256Hex } from \"./hash.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\nimport { readBytes } from \"./read-bytes.ts\";\n\nconst entrySchema = type({\n\t\"description?\": \"string | undefined\",\n\t\"displayName?\": \"string | undefined\",\n\t\"filePath\": \"string\",\n\t\"placeId\": \"string.digits\",\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<PlaceDesiredInput> {\n\treturn Object.entries(config.places ?? {}).map<PlaceDesiredInput>(([key, entry]) => {\n\t\treturn {\n\t\t\tkey: asResourceKey(key),\n\t\t\tdescription: entry.description,\n\t\t\tdisplayName: entry.displayName,\n\t\t\tfilePath: entry.filePath,\n\t\t\tkind: \"place\",\n\t\t\tplaceId: asRobloxAssetId(entry.placeId),\n\t\t\tserverSize: entry.serverSize,\n\t\t};\n\t});\n}\n\nasync function normalize(\n\tinput: PlaceDesiredInput,\n\tio: KindIo,\n): Promise<Result<PlaceDesiredState, BuildDesiredError>> {\n\tconst read = await readBytes({ key: input.key, filePath: input.filePath }, io);\n\tif (!read.success) {\n\t\treturn read;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tkey: input.key,\n\t\t\tdescription: input.description,\n\t\t\tdisplayName: input.displayName,\n\t\t\tfileHash: asSha256Hex(await sha256Hex(read.data)),\n\t\t\tfilePath: input.filePath,\n\t\t\tkind: \"place\",\n\t\t\tplaceId: input.placeId,\n\t\t\tserverSize: input.serverSize,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction fieldsEqual(desired: PlaceDesiredState, current: ResourceCurrentState<\"place\">): boolean {\n\tif (\n\t\tdesired.fileHash !== current.fileHash ||\n\t\tdesired.filePath !== current.filePath ||\n\t\tdesired.placeId !== current.placeId\n\t) {\n\t\treturn false;\n\t}\n\n\treturn PLACE_MANAGED_METADATA_FIELDS.every((field) => {\n\t\tconst desiredValue = desired[field];\n\t\treturn desiredValue === undefined || desiredValue === current[field];\n\t});\n}\n\n/**\n * Resource-kind module for Roblox places. Owns the entry schema,\n * flattening, file-hash normalization, and drift-equality for the `place`\n * kind.\n */\nexport const placeKind: ResourceKindModule<\"place\"> = {\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"place\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\nimport type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport { type } from \"arktype\";\n\nimport { asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { UniverseDesiredInput } from \"../flatten.ts\";\nimport {\n\tcopyDeclaredSocialLinks,\n\ttype ResourceCurrentState,\n\tSOCIAL_LINK_FIELDS,\n\tUNIVERSE_MANAGED_FLAGS,\n\tUNIVERSE_SINGLETON_KEY,\n\ttype UniverseDesiredState,\n} from \"../resources.ts\";\nimport type { ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst socialLink = type({\n\ttitle: \"string\",\n\turi: \"string\",\n}).onUndeclaredKey(\"reject\");\n\nconst socialLinkOrUndefined = socialLink.or(\"undefined\");\n\nconst entrySchema = type({\n\t\"consoleEnabled?\": OPTIONAL_BOOLEAN,\n\t\"desktopEnabled?\": OPTIONAL_BOOLEAN,\n\t\"discordSocialLink?\": socialLinkOrUndefined,\n\t\"displayName?\": \"string | undefined\",\n\t\"facebookSocialLink?\": socialLinkOrUndefined,\n\t\"guildedSocialLink?\": socialLinkOrUndefined,\n\t\"mobileEnabled?\": OPTIONAL_BOOLEAN,\n\t\"privateServerPriceRobux?\": \"number.integer >= 0 | undefined\",\n\t\"robloxGroupSocialLink?\": socialLinkOrUndefined,\n\t\"tabletEnabled?\": OPTIONAL_BOOLEAN,\n\t\"twitchSocialLink?\": socialLinkOrUndefined,\n\t\"twitterSocialLink?\": socialLinkOrUndefined,\n\t\"universeId\": \"string.digits\",\n\t\"voiceChatEnabled?\": OPTIONAL_BOOLEAN,\n\t\"vrEnabled?\": OPTIONAL_BOOLEAN,\n\t\"youtubeSocialLink?\": socialLinkOrUndefined,\n}).onUndeclaredKey(\"reject\");\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<UniverseDesiredInput> {\n\tconst entry = config.universe;\n\tif (entry === undefined) {\n\t\treturn [];\n\t}\n\n\tconst base: UniverseDesiredInput = {\n\t\tkey: UNIVERSE_SINGLETON_KEY,\n\t\tconsoleEnabled: entry.consoleEnabled,\n\t\tdesktopEnabled: entry.desktopEnabled,\n\t\tdisplayName: entry.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: entry.mobileEnabled,\n\t\ttabletEnabled: entry.tabletEnabled,\n\t\tuniverseId: asRobloxAssetId(entry.universeId),\n\t\tvoiceChatEnabled: entry.voiceChatEnabled,\n\t\tvrEnabled: entry.vrEnabled,\n\t\t...copyDeclaredSocialLinks(entry),\n\t};\n\n\treturn [\n\t\t\"privateServerPriceRobux\" in entry\n\t\t\t? { ...base, privateServerPriceRobux: entry.privateServerPriceRobux }\n\t\t\t: base,\n\t];\n}\n\nfunction buildBaseDesired(input: UniverseDesiredInput): UniverseDesiredState {\n\tconst base: UniverseDesiredState = {\n\t\tkey: input.key,\n\t\tconsoleEnabled: input.consoleEnabled,\n\t\tdesktopEnabled: input.desktopEnabled,\n\t\tdisplayName: input.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: input.mobileEnabled,\n\t\ttabletEnabled: input.tabletEnabled,\n\t\tuniverseId: input.universeId,\n\t\tvoiceChatEnabled: input.voiceChatEnabled,\n\t\tvrEnabled: input.vrEnabled,\n\t\t...copyDeclaredSocialLinks(input),\n\t};\n\n\treturn \"privateServerPriceRobux\" in input\n\t\t? { ...base, privateServerPriceRobux: input.privateServerPriceRobux }\n\t\t: base;\n}\n\nasync function normalize(\n\tinput: UniverseDesiredInput,\n\t_io: KindIo,\n): Promise<Result<UniverseDesiredState, BuildDesiredError>> {\n\treturn { data: buildBaseDesired(input), success: true };\n}\n\nfunction socialLinkEqual(a: SocialLink | undefined, b: SocialLink | undefined): boolean {\n\tif (a === undefined) {\n\t\treturn b === undefined;\n\t}\n\n\tif (b === undefined) {\n\t\treturn false;\n\t}\n\n\treturn a.title === b.title && a.uri === b.uri;\n}\n\nfunction declaredSocialLinksEqual(\n\tdesired: UniverseDesiredState,\n\tcurrent: ResourceCurrentState<\"universe\">,\n): boolean {\n\tfor (const field of SOCIAL_LINK_FIELDS) {\n\t\tif (!(field in desired)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!socialLinkEqual(desired[field], current[field])) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction fieldsEqual(\n\tdesired: UniverseDesiredState,\n\tcurrent: ResourceCurrentState<\"universe\">,\n): boolean {\n\tif (desired.universeId !== current.universeId) {\n\t\treturn false;\n\t}\n\n\t// Only compare flags the user has declared. An undeclared flag stays\n\t// owned by whoever else writes to the universe (Creator Hub, another\n\t// tool), so a concrete current value for it is not drift.\n\tfor (const flag of UNIVERSE_MANAGED_FLAGS) {\n\t\tconst isDesiredEnabled = desired[flag];\n\t\tif (isDesiredEnabled !== undefined && isDesiredEnabled !== current[flag]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (desired.displayName !== undefined && desired.displayName !== current.displayName) {\n\t\treturn false;\n\t}\n\n\tif (\n\t\t\"privateServerPriceRobux\" in desired &&\n\t\tdesired.privateServerPriceRobux !== current.privateServerPriceRobux\n\t) {\n\t\treturn false;\n\t}\n\n\treturn declaredSocialLinksEqual(desired, current);\n}\n\n/**\n * Resource-kind module for the singleton Roblox universe. Owns the entry\n * schema, flattening, pass-through normalize (no file I/O), and\n * drift-equality for the `universe` kind.\n */\nexport const universeKind: ResourceKindModule<\"universe\"> = {\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"universe\",\n\tnormalize,\n};\n","import { developerProductKind } from \"./developer-product.ts\";\nimport { gamePassKind } from \"./game-pass.ts\";\nimport type { KindRegistry } from \"./module.ts\";\nimport { placeKind } from \"./place.ts\";\nimport { universeKind } from \"./universe.ts\";\n\n/**\n * Default {@link KindRegistry} composing every resource kind bedrock ships\n * out of the box. Iteration order (`gamePass`, `place`, `universe`,\n * `developerProduct`) matches the order `flattenConfig` emits entries\n * today, preserving the observable order of generated operations.\n *\n * @example\n *\n * ```ts\n * import { defaultKindRegistry } from \"@bedrock-rbx/core\";\n *\n * expect(defaultKindRegistry.gamePass.kind).toBe(\"gamePass\");\n * expect(defaultKindRegistry.place.kind).toBe(\"place\");\n * expect(defaultKindRegistry.universe.kind).toBe(\"universe\");\n * expect(defaultKindRegistry.developerProduct.kind).toBe(\"developerProduct\");\n * ```\n */\nexport const defaultKindRegistry: KindRegistry = {\n\tdeveloperProduct: developerProductKind,\n\tgamePass: gamePassKind,\n\tplace: placeKind,\n\tuniverse: universeKind,\n};\n","import { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { ResourceKindModule } from \"./kinds/module.ts\";\nimport type { Operation } from \"./operations.ts\";\nimport type { ResourceCurrentState, ResourceDesiredState, ResourceKind } from \"./resources.ts\";\n\n/**\n * Computes the operations required to reconcile `current` state with `desired`\n * state. Pure and synchronous: no I/O, no side effects, no `Result` wrapper.\n *\n * Each entry in `desired` is matched to `current` by `(kind, key)`: resources\n * are uniquely identified by that pair, so a `place` and a `universe` keyed\n * `\"main\"` are independent slots. A `(kind, key)` pair present only in\n * `desired` produces a `create` op; a pair present in both produces an\n * `update` op if any declared field differs or a `noop` op if every field\n * matches.\n *\n * Ops appear in the order their desired entries appear in the input array so\n * callers can rely on declaration order when logging or applying ops.\n *\n * @param desired - Declared desired state from user config, already normalized\n * (file hashes computed, nullable wire values mapped to `undefined`).\n * @param current - Last-known live state from the state file.\n * @returns Operations to reconcile the two snapshots.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * diff,\n * type GamePassDesiredState,\n * type ResourceCurrentState,\n * } from \"@bedrock-rbx/core\";\n *\n * const hash = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n *\n * const unchanged: GamePassDesiredState = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: { \"en-us\": hash },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * };\n * const drifted: GamePassDesiredState = {\n * ...unchanged,\n * key: asResourceKey(\"legend-pass\"),\n * name: \"Legend Pass (renamed)\",\n * };\n * const fresh: GamePassDesiredState = {\n * ...unchanged,\n * key: asResourceKey(\"rookie-pass\"),\n * name: \"Rookie Pass\",\n * };\n *\n * const current: ReadonlyArray<ResourceCurrentState> = [\n * {\n * ...unchanged,\n * outputs: {\n * assetId: asRobloxAssetId(\"111\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"222\") },\n * },\n * },\n * {\n * ...drifted,\n * name: \"Legend Pass\",\n * outputs: {\n * assetId: asRobloxAssetId(\"333\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"444\") },\n * },\n * },\n * ];\n *\n * const ops = diff([unchanged, drifted, fresh], current);\n *\n * expect(ops.map((op) => op.type)).toEqual([\"noop\", \"update\", \"create\"]);\n * ```\n */\nexport function diff(\n\tdesired: ReadonlyArray<ResourceDesiredState>,\n\tcurrent: ReadonlyArray<ResourceCurrentState>,\n): ReadonlyArray<Operation> {\n\tconst currentByKey = new Map(current.map((entry) => [compositeKey(entry), entry]));\n\treturn desired.map((entry) => operationFor(entry, currentByKey.get(compositeKey(entry))));\n}\n\nfunction compositeKey(resource: { readonly key: string; readonly kind: ResourceKind }): string {\n\treturn `${resource.kind}:${resource.key}`;\n}\n\nfunction desiredFieldsEqual(desired: ResourceDesiredState, current: ResourceCurrentState): boolean {\n\t// Composite-key matching guarantees `desired.kind === current.kind`,\n\t// so the per-kind module is consulted directly without a kind guard.\n\tconst module = defaultKindRegistry[desired.kind] as ResourceKindModule<ResourceKind>;\n\treturn module.fieldsEqual(desired, current);\n}\n\nfunction operationFor(\n\tdesired: ResourceDesiredState,\n\tcurrent: ResourceCurrentState | undefined,\n): Operation {\n\tif (current === undefined) {\n\t\treturn { key: desired.key, desired, type: \"create\" };\n\t}\n\n\tif (desiredFieldsEqual(desired, current)) {\n\t\treturn { key: desired.key, type: \"noop\" };\n\t}\n\n\treturn { key: desired.key, current, desired, type: \"update\" };\n}\n","/**\n * Default template applied when a project enables display-name prefixing\n * without supplying its own `displayNamePrefix.format`. Yields outputs\n * like `[STAGING] ` for an environment whose `label` is `\"staging\"`.\n */\nexport const DEFAULT_PREFIX_FORMAT = \"[{LABEL}] \";\n\nconst PLACEHOLDER_PATTERN = /\\{(LABEL|Label|label)\\}/g;\n\n/**\n * Render the prefix that selectEnvironment prepends to declared display\n * names when a project enables `displayNamePrefix`. The template\n * recognizes three placeholders:\n *\n * - `{label}`: label as written.\n * - `{LABEL}`: upper-cased label.\n * - `{Label}`: capitalized label (first character upper, rest as written).\n *\n * Other characters in the template flow through verbatim.\n *\n * @param label - Environment label declared on `EnvironmentEntry.label`.\n * @param format - Template string. Falls back to\n * {@link DEFAULT_PREFIX_FORMAT} when omitted.\n * @returns The rendered prefix string.\n *\n * @example\n *\n * ```ts\n * import { renderDisplayNamePrefix } from \"@bedrock-rbx/core\";\n *\n * expect(renderDisplayNamePrefix(\"staging\")).toBe(\"[STAGING] \");\n * expect(renderDisplayNamePrefix(\"staging\", \"{Label}: \")).toBe(\"Staging: \");\n * expect(renderDisplayNamePrefix(\"dev\", \"{LABEL}-{label}\")).toBe(\"DEV-dev\");\n * ```\n */\nexport function renderDisplayNamePrefix(label: string, format?: string): string {\n\tconst template = format ?? DEFAULT_PREFIX_FORMAT;\n\treturn template.replace(PLACEHOLDER_PATTERN, (_match, kind) => {\n\t\tif (kind === \"LABEL\") {\n\t\t\treturn label.toUpperCase();\n\t\t}\n\n\t\tif (kind === \"Label\") {\n\t\t\treturn capitalize(label);\n\t\t}\n\n\t\treturn label;\n\t});\n}\n\nfunction capitalize(value: string): string {\n\treturn value.charAt(0).toUpperCase() + value.slice(1);\n}\n","import type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport type { ResourceKey, RobloxAssetId } from \"../types/ids.ts\";\nimport { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { ResourceKindModule } from \"./kinds/module.ts\";\nimport type { ResourceKind } from \"./resources.ts\";\nimport type { DeveloperProductEntry, GamePassEntry, ResolvedConfig } from \"./schema.ts\";\n\n/**\n * Pre-I/O game-pass input the flattener emits. Extends the authored\n * `GamePassEntry` with the tag discriminator and the `ResourceKey`-branded\n * key so `buildDesired` can consume a flat tagged list and layer on the\n * SHA-256 icon digest.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type GamePassDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: GamePassDesiredInput = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * };\n *\n * expect(input.kind).toBe(\"gamePass\");\n * ```\n */\nexport interface GamePassDesiredInput extends Readonly<GamePassEntry> {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"gamePass\";\n}\n\n/**\n * Pre-I/O place input the flattener emits. Carries the resolved place\n * fields (`filePath` from the root, `placeId` from the per-environment\n * overlay) plus the optional metadata fields and the tag discriminator and\n * the `ResourceKey`-branded key, so `buildDesired` can consume a flat\n * tagged list and layer on the SHA-256 file digest.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, asRobloxAssetId, type PlaceDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: PlaceDesiredInput = {\n * description: undefined,\n * displayName: \"Start Place\",\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: 50,\n * };\n *\n * expect(input.kind).toBe(\"place\");\n * expect(input.displayName).toBe(\"Start Place\");\n * ```\n */\nexport interface PlaceDesiredInput {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** User-facing place description; `undefined` leaves the server value untouched. */\n\treadonly description: string | undefined;\n\t/** User-facing place name; `undefined` leaves the server value untouched. */\n\treadonly displayName: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; read by `buildDesired`. */\n\treadonly filePath: string;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"place\";\n\t/** Existing Roblox place ID, validated and branded at flatten time. */\n\treadonly placeId: RobloxAssetId;\n\t/** Maximum players per server; `undefined` leaves the server value untouched. */\n\treadonly serverSize: number | undefined;\n}\n\n/**\n * Pre-I/O universe input the flattener emits. Carries the fixed singleton\n * key (`\"main\"`) and the branded `universeId` so `buildDesired` can hand a\n * single tagged record downstream without a shape divergence for the\n * singleton kind.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId, UNIVERSE_SINGLETON_KEY, type UniverseDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: UniverseDesiredInput = {\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * displayName: undefined,\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: undefined,\n * tabletEnabled: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * };\n *\n * expect(input.kind).toBe(\"universe\");\n * expect(input.key).toBe(\"main\");\n * expect(input.desktopEnabled).toBeTrue();\n * ```\n */\nexport interface UniverseDesiredInput {\n\t/** Synthesized singleton key (`\"main\"`), already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Whether console players can join; `undefined` leaves the server value untouched. */\n\treadonly consoleEnabled: boolean | undefined;\n\t/** Whether desktop players can join; `undefined` leaves the server value untouched. */\n\treadonly desktopEnabled: boolean | undefined;\n\t/** Discord social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly discordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. `undefined` leaves the server value\n\t * untouched. The driver routes declared updates through\n\t * `PlacesClient.update` because the universe PATCH endpoint treats\n\t * `displayName` as read-only.\n\t */\n\treadonly displayName: string | undefined;\n\t/** Facebook social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly facebookSocialLink?: SocialLink | undefined;\n\t/** Guilded social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly guildedSocialLink?: SocialLink | undefined;\n\t/**\n\t * Locale-keyed experience-icon paths copied from the user-supplied\n\t * `UniverseEntry`. Absent when the user did not declare an icon block.\n\t */\n\treadonly icon?: Record<\"en-us\", string>;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"universe\";\n\t/** Whether mobile players can join; `undefined` leaves the server value untouched. */\n\treadonly mobileEnabled: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. A present key with `undefined`\n\t * clears the server value (ocale emits JSON `null`); an absent key\n\t * leaves the server value untouched.\n\t */\n\treadonly privateServerPriceRobux?: number | undefined;\n\t/** Roblox Group social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly robloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; `undefined` leaves the server value untouched. */\n\treadonly tabletEnabled: boolean | undefined;\n\t/** Twitch social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly twitchSocialLink?: SocialLink | undefined;\n\t/** Twitter social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly twitterSocialLink?: SocialLink | undefined;\n\t/** Existing Roblox universe ID, validated and branded at flatten time. */\n\treadonly universeId: RobloxAssetId;\n\t/** Whether voice chat is enabled; `undefined` leaves the server value untouched. */\n\treadonly voiceChatEnabled: boolean | undefined;\n\t/** Whether VR players can join; `undefined` leaves the server value untouched. */\n\treadonly vrEnabled: boolean | undefined;\n\t/** YouTube social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly youtubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * Pre-I/O developer-product input the flattener emits. Extends the authored\n * `DeveloperProductEntry` with the tag discriminator and the\n * `ResourceKey`-branded key so `buildDesired` can consume a flat tagged\n * list.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type DeveloperProductDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: DeveloperProductDesiredInput = {\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * };\n *\n * expect(input.kind).toBe(\"developerProduct\");\n * ```\n */\nexport interface DeveloperProductDesiredInput extends Readonly<DeveloperProductEntry> {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"developerProduct\";\n}\n\n/**\n * Flat tagged input for `buildDesired`. One member per resource kind; future\n * kinds widen this union as they land.\n */\nexport type ResourceDesiredInput =\n\t| DeveloperProductDesiredInput\n\t| GamePassDesiredInput\n\t| PlaceDesiredInput\n\t| UniverseDesiredInput;\n\n/**\n * Turn a resolved `Config` into a flat, tagged list of resource inputs.\n *\n * Pure and infallible: validation and per-environment overlay merging\n * have already happened upstream (typically via `selectEnvironment`), so\n * every invariant this function relies on is guaranteed by the input\n * shape. Entries appear in the insertion order of each collection;\n * `passes` are emitted before `places`.\n *\n * @param config - Resolved config returned by `selectEnvironment`.\n * @returns Flat tagged list ready for `buildDesired`.\n * @example\n *\n * ```ts\n * import { flattenConfig, selectEnvironment, type Config } from \"@bedrock-rbx/core\";\n *\n * const config: Config = {\n * environments: {\n * production: { places: { \"start-place\": { placeId: \"4711\" } } },\n * },\n * passes: {\n * \"vip-pass\": {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * places: { \"start-place\": { filePath: \"places/start.rbxl\" } },\n * };\n *\n * const resolved = selectEnvironment(config, \"production\");\n * expect(resolved.success).toBeTrue();\n * if (resolved.success) {\n * const inputs = flattenConfig(resolved.data);\n * expect(inputs.map((input) => input.kind)).toEqual([\"gamePass\", \"place\"]);\n * expect(inputs.map((input) => input.key)).toEqual([\"vip-pass\", \"start-place\"]);\n * }\n * ```\n */\nexport function flattenConfig(config: ResolvedConfig): ReadonlyArray<ResourceDesiredInput> {\n\tconst modules = Object.values(defaultKindRegistry) as ReadonlyArray<\n\t\tResourceKindModule<ResourceKind>\n\t>;\n\treturn modules.flatMap((module) => module.flatten(config));\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { StateConfig } from \"./schema.ts\";\n\n/**\n * Failure surfaced when no `StateConfig` is configured for the requested\n * environment. The shell layer wraps this in a `DeployError` when default\n * state-port construction is requested but the project has not declared\n * where state should live.\n */\nexport interface StateNotConfiguredError {\n\t/** Environment that the resolver was called against. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"stateNotConfigured\";\n}\n\n/**\n * Minimal structural input the state resolver needs. Both `Config`\n * (pre-merge, discriminated XOR union) and `ResolvedConfig` (post-merge)\n * satisfy this shape, so callers can route either in without coupling\n * the resolver to the discriminated-union arms.\n */\ninterface StateResolutionInputs {\n\treadonly environments: Record<string, undefined | { readonly state?: StateConfig }>;\n\treadonly state?: StateConfig;\n}\n\n/**\n * Pick the `StateConfig` that applies to `environment`. Per-environment\n * overrides win over the root block; if neither is present, returns\n * `Err(stateNotConfigured)` so the deploy boundary can surface a typed\n * error instead of silently falling back.\n *\n * @param config - Validated project config.\n * @param environment - Target environment name.\n * @returns The resolved `StateConfig`, or `Err(stateNotConfigured)` when\n * neither the environment override nor the root block is set.\n * @example\n *\n * ```ts\n * import { resolveStateConfig } from \"@bedrock-rbx/core\";\n *\n * const result = resolveStateConfig(\n * {\n * state: { backend: \"gist\", gistId: \"root-gist\" },\n * environments: {\n * production: { state: { backend: \"gist\", gistId: \"prod-gist\" } },\n * },\n * },\n * \"production\",\n * );\n *\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toContainEntry([\"gistId\", \"prod-gist\"]);\n * }\n * ```\n */\nexport function resolveStateConfig(\n\tconfig: StateResolutionInputs,\n\tenvironment: string,\n): Result<StateConfig, StateNotConfiguredError> {\n\tconst override = config.environments[environment]?.state;\n\tif (override !== undefined) {\n\t\treturn { data: override, success: true };\n\t}\n\n\tif (config.state !== undefined) {\n\t\treturn { data: config.state, success: true };\n\t}\n\n\treturn { err: { environment, kind: \"stateNotConfigured\" }, success: false };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { defu } from \"defu\";\n\nimport { renderDisplayNamePrefix } from \"./display-name-prefix.ts\";\nimport type {\n\tConfig,\n\tDeveloperProductEntry,\n\tEnvironmentEntry,\n\tGamePassEntry,\n\tResolvedConfig,\n\tResolvedPlaceEntry,\n\tResolvedUniverseEntry,\n\tUniverseEntry,\n} from \"./schema.ts\";\n\n/**\n * Failure surfaced when `selectEnvironment` is asked for an environment\n * name that is not a key of `config.environments`. Carries the list of\n * declared names so callers can render a \"did you mean?\" hint or a\n * close-match suggestion.\n */\nexport interface UnknownEnvironmentError {\n\t/** Environment names that the config actually declared. */\n\treadonly declared: ReadonlyArray<string>;\n\t/** Environment name the caller asked for. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"unknownEnvironment\";\n}\n\n/**\n * Failure surfaced when a merged place entry is missing a required field.\n * Two paths reach this error: a root place declared without a matching\n * per-environment overlay supplying `placeId`, and an overlay-only place\n * declared under `environments.X.places` with no matching root entry to\n * supply `filePath`. Surfacing both at the resolution boundary attributes\n * the missing field to the offending entry's key instead of letting\n * `buildDesired` crash with a generic `fileReadFailed` later on.\n */\nexport interface IncompletePlaceEntryError {\n\t/** ResourceKey of the place entry that is missing a required field. */\n\treadonly key: string;\n\t/** Environment whose overlay was projected onto the config. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"incompletePlaceEntry\";\n\t/** Field that the merged entry lacks. */\n\treadonly missingField: \"filePath\" | \"placeId\";\n}\n\n/**\n * Failure surfaced when a merged `universe` block lacks `universeId`.\n * The schema-level XOR rule normally prevents this by requiring\n * `universeId` either at the root or on every per-environment overlay;\n * this error remains as a typed safety net for callers that bypass\n * `validateConfig` and hand a `Config` to `selectEnvironment` directly.\n */\nexport interface IncompleteUniverseEntryError {\n\t/** Environment whose overlay was projected onto the config. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"incompleteUniverseEntry\";\n\t/** Field that the merged entry lacks. V1 only surfaces `\"universeId\"`. */\n\treadonly missingField: \"universeId\";\n}\n\n/** Failure modes returned by {@link selectEnvironment}. */\nexport type SelectEnvironmentError =\n\t| IncompletePlaceEntryError\n\t| IncompleteUniverseEntryError\n\t| UnknownEnvironmentError;\n\ninterface ProjectInputs {\n\treadonly config: Config;\n\treadonly entry: Config[\"environments\"][string];\n}\n\n/**\n * Project a validated `Config` onto a single environment. Looks up the\n * matching `environments[environment]` entry, deep-merges its resource\n * overlay (`passes`, `places`, `universe`) over the root config via defu,\n * and applies the env-level state override when present (the env entry's\n * `state` field wins; otherwise the root `state` flows through).\n *\n * Pure: no I/O. Returns a `ResolvedConfig` ready to feed into downstream\n * functions (`flattenConfig`, `buildDefaultRegistry`, `resolveStateConfig`).\n * The post-merge view promotes `places` from `Record<string, PlaceEntry>`\n * (root: file-paths only) to `Record<string, ResolvedPlaceEntry>` (root +\n * overlay merged). `environments` and `extends` are passed through\n * unchanged because they preserve the shape relationship to `Config`;\n * downstream consumers do not read them post-merge.\n *\n * Defu's merge semantics are deliberate: keyed-map collections merge by\n * key (so a place declared in both root and overlay produces a single\n * entry whose overlay-supplied fields win), and `null` / `undefined` in\n * the overlay are skipped (so the overlay never deletes a root field).\n * State has its own resolution path (a single replacement, not a\n * deep-merge) because it is a tagged union: a deep-merge of\n * `{ backend: \"s3\" }` over `{ backend: \"gist\", gistId }` would produce\n * a malformed `{ backend: \"s3\", gistId }`.\n *\n * State is left absent when neither the env override nor the root block\n * provides one. Callers that require a resolved `StateConfig` should\n * route through `resolveStateConfig` or `buildStatePort`; the absent\n * case surfaces as a typed `stateNotConfigured` there.\n *\n * Limitation in v1: a per-environment universe overlay that introduces a\n * brand-new universe block may still have optional fields missing, since\n * the overlay type only requires the identity-bearing key. The resolver\n * surfaces the entry as-is; the universe driver reports the missing\n * field when it tries to consume the entry. Universe is a singleton with\n * 20+ optional fields, so the same `incompletePlaceEntry`-style validation\n * is deferred to a separate follow-up.\n *\n * When the project sets a `displayNamePrefix` (or omits it, in which case\n * prefixing defaults to enabled) and the chosen environment declares a\n * non-empty `label`, the resolver renders the configured template via\n * `renderDisplayNamePrefix` and prepends the result to `universe.displayName`\n * and every declared place `displayName`. An undeclared `displayName`, an\n * empty/absent label, or an explicit `displayNamePrefix.enabled: false` all\n * skip prefixing for the affected fields.\n *\n * @example\n *\n * ```ts\n * import { selectEnvironment } from \"@bedrock-rbx/core\";\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: {\n * production: { universe: { universeId: \"999\" } },\n * },\n * state: { backend: \"gist\", gistId: \"abc123\" },\n * universe: { voiceChatEnabled: true },\n * };\n *\n * const result = selectEnvironment(config, \"production\");\n *\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.universe?.universeId).toBe(\"999\");\n * expect(result.data.universe?.voiceChatEnabled).toBeTrue();\n * expect(result.data.state?.backend).toBe(\"gist\");\n * }\n * ```\n *\n * @param config - Validated project config carrying at least one\n * environment under `environments`.\n * @param environment - Environment name to project onto. Must be a key\n * of `config.environments`.\n * @returns `Ok(ResolvedConfig)` with the merged resource fields and the\n * resolved state, or `Err(SelectEnvironmentError)` describing why the\n * projection failed.\n */\nexport function selectEnvironment(\n\tconfig: Config,\n\tenvironment: string,\n): Result<ResolvedConfig, SelectEnvironmentError> {\n\tconst entry = config.environments[environment];\n\tif (entry === undefined) {\n\t\treturn { err: unknownEnvironment(config, environment), success: false };\n\t}\n\n\tconst projected = projectConfig({ config, entry });\n\tconst incompletePlace = findIncompletePlace(projected, environment);\n\tif (incompletePlace !== undefined) {\n\t\treturn { err: incompletePlace, success: false };\n\t}\n\n\tconst incompleteUniverse = findIncompleteUniverse(projected, environment);\n\tif (incompleteUniverse !== undefined) {\n\t\treturn { err: incompleteUniverse, success: false };\n\t}\n\n\treturn { data: projected, success: true };\n}\n\nfunction findIncompleteUniverse(\n\tprojected: ResolvedConfig,\n\tenvironment: string,\n): IncompleteUniverseEntryError | undefined {\n\tconst { universe } = projected;\n\tif (universe === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// `universe` is typed as `ResolvedUniverseEntry` (universeId required)\n\t// because the merge boundary already promised completeness; this routine\n\t// exists to honour that promise at runtime, so it widens the view back to\n\t// `Partial<ResolvedUniverseEntry>` for the duration of the check.\n\tconst candidate: Partial<ResolvedUniverseEntry> = universe;\n\tif (candidate.universeId === undefined) {\n\t\treturn { environment, kind: \"incompleteUniverseEntry\", missingField: \"universeId\" };\n\t}\n\n\treturn undefined;\n}\n\nfunction findIncompletePlace(\n\tprojected: ResolvedConfig,\n\tenvironment: string,\n): IncompletePlaceEntryError | undefined {\n\tconst { places } = projected;\n\tif (places === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// `places` is typed as `Record<string, ResolvedPlaceEntry>` because the\n\t// merge boundary already promised completeness; this routine exists to\n\t// honour that promise at runtime, so it widens the view back to\n\t// `Partial<ResolvedPlaceEntry>` for the duration of the check.\n\tconst candidates: Record<string, Partial<ResolvedPlaceEntry>> = places;\n\tfor (const [key, entry] of Object.entries(candidates)) {\n\t\tif (entry.placeId === undefined) {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"incompletePlaceEntry\",\n\t\t\t\tmissingField: \"placeId\",\n\t\t\t};\n\t\t}\n\n\t\tif (entry.filePath === undefined) {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"incompletePlaceEntry\",\n\t\t\t\tmissingField: \"filePath\",\n\t\t\t};\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction mergeEntry<Resolved extends object>(\n\toverlay: Partial<Resolved>,\n\tbase: Partial<Resolved> | undefined,\n): Resolved {\n\t// Precondition for the cast: every public success path of\n\t// `selectEnvironment` MUST run completeness validation (today only\n\t// `findIncompletePlace`) before exposing the merged record to a caller.\n\t// The cast trades compile-time soundness for the freedom to surface\n\t// partial entries to a validator that can attribute the missing field to\n\t// a typed error. New resource kinds that adopt this merge pattern owe\n\t// their own `findIncomplete<Kind>Entry` validator before returning.\n\t//\n\t// defu treats `undefined` as the empty object, so an overlay-only entry\n\t// (no matching root) flows through unchanged. defu's return type is\n\t// `MergeObjects<Partial<Resolved>, Partial<Resolved>>` which the compiler\n\t// cannot prove equals `Resolved`.\n\treturn defu(overlay, base ?? {}) as Resolved;\n}\n\nfunction mergeKeyedRecord<Resolved extends object>(\n\toverlay: Record<string, Partial<Resolved>> | undefined,\n\tbase: Record<string, Partial<Resolved>> | undefined,\n): Record<string, Resolved> | undefined {\n\tif (overlay === undefined) {\n\t\t// Same precondition as `mergeEntry`: passing the base record straight\n\t\t// through is sound only when the caller validates completeness on the\n\t\t// returned record before publishing it.\n\t\treturn base as Record<string, Resolved> | undefined;\n\t}\n\n\treturn {\n\t\t...((base ?? {}) as Record<string, Resolved>),\n\t\t...Object.fromEntries(\n\t\t\tObject.entries(overlay).map(([key, partial]) => {\n\t\t\t\treturn [key, mergeEntry<Resolved>(partial, base?.[key])];\n\t\t\t}),\n\t\t),\n\t};\n}\n\nfunction mergeUniverse(\n\toverlay: Partial<UniverseEntry> | undefined,\n\tbase: undefined | UniverseEntry,\n): ResolvedUniverseEntry | undefined {\n\tif (overlay === undefined && base === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// Precondition for the cast: see `mergeEntry`. The schema-level XOR rule\n\t// guarantees a present `universeId` post-merge whenever the result is\n\t// non-empty, and `findIncompleteUniverse` re-verifies the invariant on\n\t// the success path. The `defu` call type-resolves to a wider partial\n\t// because both sides declare `universeId` as optional; the cast collapses\n\t// it to the resolved shape.\n\treturn defu(overlay ?? {}, base ?? {}) as ResolvedUniverseEntry;\n}\n\nfunction resolvePrefix(config: Config, entry: EnvironmentEntry): string | undefined {\n\tif (config.displayNamePrefix?.enabled === false) {\n\t\treturn undefined;\n\t}\n\n\tconst { label } = entry;\n\tif (label === undefined || label === \"\") {\n\t\treturn undefined;\n\t}\n\n\treturn renderDisplayNamePrefix(label, config.displayNamePrefix?.format);\n}\n\nfunction applyUniversePrefix(\n\tuniverse: ResolvedUniverseEntry | undefined,\n\tprefix: string | undefined,\n): ResolvedUniverseEntry | undefined {\n\tif (universe === undefined || prefix === undefined || universe.displayName === undefined) {\n\t\treturn universe;\n\t}\n\n\treturn { ...universe, displayName: prefix + universe.displayName };\n}\n\nfunction applyPlacesPrefix(\n\tplaces: Record<string, ResolvedPlaceEntry> | undefined,\n\tprefix: string | undefined,\n): Record<string, ResolvedPlaceEntry> | undefined {\n\tif (places === undefined || prefix === undefined) {\n\t\treturn places;\n\t}\n\n\treturn Object.fromEntries(\n\t\tObject.entries(places).map(([key, place]) => {\n\t\t\tif (place.displayName === undefined) {\n\t\t\t\treturn [key, place];\n\t\t\t}\n\n\t\t\treturn [key, { ...place, displayName: prefix + place.displayName }];\n\t\t}),\n\t);\n}\n\nfunction projectConfig(inputs: ProjectInputs): ResolvedConfig {\n\tconst { config, entry } = inputs;\n\tconst passes = mergeKeyedRecord<GamePassEntry>(entry.passes, config.passes);\n\tconst mergedPlaces = mergeKeyedRecord<ResolvedPlaceEntry>(entry.places, config.places);\n\tconst products = mergeKeyedRecord<DeveloperProductEntry>(entry.products, config.products);\n\tconst merged = mergeUniverse(entry.universe, config.universe);\n\tconst prefix = resolvePrefix(config, entry);\n\tconst universe = applyUniversePrefix(merged, prefix);\n\tconst places = applyPlacesPrefix(mergedPlaces, prefix);\n\tconst state = entry.state ?? config.state;\n\n\tconst {\n\t\tplaces: _placesRoot,\n\t\tproducts: _productsRoot,\n\t\tuniverse: _universeRoot,\n\t\t...rest\n\t} = config;\n\n\treturn {\n\t\t...rest,\n\t\t...(passes === undefined ? {} : { passes }),\n\t\t...(places === undefined ? {} : { places }),\n\t\t...(products === undefined ? {} : { products }),\n\t\t...(state === undefined ? {} : { state }),\n\t\t...(universe === undefined ? {} : { universe }),\n\t};\n}\n\nfunction unknownEnvironment(config: Config, environment: string): UnknownEnvironmentError {\n\treturn {\n\t\tdeclared: Object.keys(config.environments),\n\t\tenvironment,\n\t\tkind: \"unknownEnvironment\",\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { BuildDesiredError, ResourceKindModule } from \"./kinds/module.ts\";\nimport type { ResourceCurrentState, ResourceDesiredState, ResourceKind } from \"./resources.ts\";\n\n/**\n * Plan-time invariant check that runs after `buildDesired` and before\n * `diff`. Walks paired `(kind, key)` entries and dispatches to each\n * kind module's optional `assertReconcilable` hook so kind-specific\n * rejections (e.g. Removing a developer-product icon, which the upstream\n * API has no documented unset path for) surface as typed errors before\n * `diff` runs and before any apply-side driver I/O is attempted.\n *\n * Pure and synchronous. Current-only entries (no matching desired) are\n * ignored: their reconciliation is `diff`'s concern, not this seam's.\n *\n * @param desired - Desired state from `buildDesired`.\n * @param current - Prior current state from the state port.\n * @returns `Ok(undefined)` when every paired entry passes its kind-level\n * reconcilability check, or the first `Err` returned by a hook.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, asRobloxAssetId, asSha256Hex, validatePlan } from \"@bedrock-rbx/core\";\n *\n * const result = validatePlan(\n * [\n * {\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * isRegionalPricingEnabled: undefined,\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * price: undefined,\n * storePageEnabled: undefined,\n * },\n * ],\n * [\n * {\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * icon: { \"en-us\": \"assets/gem-pack.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * isRegionalPricingEnabled: undefined,\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * outputs: { productId: asRobloxAssetId(\"9876543210\") },\n * price: undefined,\n * storePageEnabled: undefined,\n * },\n * ],\n * );\n *\n * expect(result.success).toBeFalse();\n * if (!result.success) {\n * expect(result.err.kind).toBe(\"iconRemovalRejected\");\n * }\n * ```\n */\nexport function validatePlan(\n\tdesired: ReadonlyArray<ResourceDesiredState>,\n\tcurrent: ReadonlyArray<ResourceCurrentState>,\n): Result<undefined, BuildDesiredError> {\n\tconst currentByKey = new Map(current.map((entry) => [compositeKey(entry), entry]));\n\tfor (const entry of desired) {\n\t\tconst matched = currentByKey.get(compositeKey(entry));\n\t\tif (matched === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst module = defaultKindRegistry[entry.kind] as ResourceKindModule<ResourceKind>;\n\t\tconst check = module.assertReconcilable?.(matched, entry);\n\t\tif (check !== undefined && !check.success) {\n\t\t\treturn check;\n\t\t}\n\t}\n\n\treturn { data: undefined, success: true };\n}\n\nfunction compositeKey(resource: { readonly key: string; readonly kind: ResourceKind }): string {\n\treturn `${resource.kind}:${resource.key}`;\n}\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\n\nimport type { DistributedOmit } from \"type-fest\";\n\nimport type { Operation } from \"../core/operations.ts\";\nimport type {\n\tDeveloperProductDesiredState,\n\tGamePassDesiredState,\n\tPlaceDesiredState,\n\tResourceCurrentState,\n\tResourceDesiredState,\n\tResourceKind,\n\tUniverseDesiredState,\n} from \"../core/resources.ts\";\nimport type { DriverRegistry, ResourceDriver } from \"../ports/resource-driver.ts\";\nimport type { ResourceKey } from \"../types/ids.ts\";\n\n/**\n * Failure surfaced by `applyOps` when an operation cannot be applied.\n * Plain-data discriminated union; narrow on `kind`, do not `instanceof` it.\n *\n * `appliedSoFar` carries the driver outputs from operations that succeeded\n * before the failing one, in dispatched order. Callers persist this so a\n * follow-up reconcile does not duplicate Roblox-side resources that have\n * already been created or updated.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type ApplyError } from \"@bedrock-rbx/core\";\n *\n * function describe(err: ApplyError): string {\n * switch (err.kind) {\n * case \"driverFailure\": {\n * return `driver failed for ${err.key}: ${err.cause.message}`;\n * }\n * case \"updateUnsupported\": {\n * return `update not supported for ${err.key}`;\n * }\n * }\n * }\n *\n * const err: ApplyError = {\n * key: asResourceKey(\"vip-pass\"),\n * appliedSoFar: [],\n * kind: \"updateUnsupported\",\n * };\n *\n * expect(describe(err)).toBe(\"update not supported for vip-pass\");\n * ```\n */\nexport type ApplyError =\n\t| {\n\t\t\treadonly appliedSoFar: ReadonlyArray<ResourceCurrentState>;\n\t\t\treadonly cause: OpenCloudError;\n\t\t\treadonly key: ResourceKey;\n\t\t\treadonly kind: \"driverFailure\";\n\t }\n\t| {\n\t\t\treadonly appliedSoFar: ReadonlyArray<ResourceCurrentState>;\n\t\t\treadonly key: ResourceKey;\n\t\t\treadonly kind: \"updateUnsupported\";\n\t };\n\ntype NonNoopOp = Exclude<Operation, { readonly type: \"noop\" }>;\ntype DeveloperProductOp = NonNoopOp & { readonly desired: DeveloperProductDesiredState };\ntype GamePassOp = NonNoopOp & { readonly desired: GamePassDesiredState };\ntype PlaceOp = NonNoopOp & { readonly desired: PlaceDesiredState };\ntype UniverseOp = NonNoopOp & { readonly desired: UniverseDesiredState };\n\ntype RawApplyError = DistributedOmit<ApplyError, \"appliedSoFar\">;\n\n/**\n * Dispatch each reconciliation operation to the matching resource driver\n * with first-fail semantics: on the first `Err` (driver failure or\n * `updateUnsupported`), the remaining operations are skipped and the error\n * is returned verbatim.\n *\n * Behaviour:\n * - `create` operations are routed to `registry[op.desired.kind].create`.\n * - `update` operations are routed to `registry[op.desired.kind].update`\n * when the driver exposes it; otherwise they short-circuit to an\n * `updateUnsupported` Err without invoking the driver.\n * - `noop` operations are skipped entirely (no I/O, no dispatch).\n *\n * On success the returned array carries the driver outputs for every\n * non-noop op, in dispatched order. Noops are not represented; callers\n * needing a full post-apply snapshot merge with the pre-apply current\n * state keyed by `ResourceKey`.\n *\n * @param ops - Reconciliation operations produced by `diff`, applied in order.\n * @param registry - Per-kind driver table; dispatch uses `op.desired.kind` as the index.\n * @returns `Ok(state)` when every operation succeeds, where `state` holds\n * driver outputs for each non-noop op in dispatched order; or the first\n * failure encountered.\n * @throws Whatever the dispatched driver rejects with outside its `Result`\n * return. A driver whose injected I/O (file reads, network calls, etc.)\n * throws will surface that rejection here rather than translating it into\n * a `Result` failure; wrap the call site in a try/catch when drivers are\n * not trusted to contain their own rejections.\n * @example\n *\n * ```ts\n * import {\n * applyOps,\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * type DriverRegistry,\n * type Operation,\n * } from \"@bedrock-rbx/core\";\n *\n * const registry: DriverRegistry = {\n * gamePass: {\n * async create(desired) {\n * return {\n * data: {\n * ...desired,\n * outputs: {\n * assetId: asRobloxAssetId(\"9876543210\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"1122334455\") },\n * },\n * },\n * success: true,\n * };\n * },\n * },\n * place: {\n * async create(desired) {\n * return {\n * data: { ...desired, outputs: { versionNumber: 1 } },\n * success: true,\n * };\n * },\n * },\n * universe: {\n * async create(desired) {\n * return {\n * data: { ...desired, outputs: { rootPlaceId: asRobloxAssetId(\"4711\") } },\n * success: true,\n * };\n * },\n * },\n * developerProduct: {\n * async create(desired) {\n * return {\n * data: {\n * ...desired,\n * outputs: { productId: asRobloxAssetId(\"8172635495\") },\n * },\n * success: true,\n * };\n * },\n * },\n * };\n *\n * const ops: ReadonlyArray<Operation> = [\n * {\n * key: asResourceKey(\"vip-pass\"),\n * type: \"create\",\n * desired: {\n * key: asResourceKey(\"vip-pass\"),\n * name: \"VIP Pass\",\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * kind: \"gamePass\",\n * price: 500,\n * },\n * },\n * ];\n *\n * return applyOps(ops, registry).then((result) => {\n * expect(result.success).toBe(true);\n * expect(result.success && result.data).toHaveLength(1);\n * });\n * ```\n */\nexport async function applyOps(\n\tops: ReadonlyArray<Operation>,\n\tregistry: DriverRegistry,\n): Promise<Result<ReadonlyArray<ResourceCurrentState>, ApplyError>> {\n\tconst applied: Array<ResourceCurrentState> = [];\n\n\tfor (const op of ops) {\n\t\tif (op.type === \"noop\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst outcome = await dispatchOp(op, registry);\n\t\tif (!outcome.success) {\n\t\t\treturn { err: { ...outcome.err, appliedSoFar: applied }, success: false };\n\t\t}\n\n\t\tapplied.push(outcome.data);\n\t}\n\n\treturn { data: applied, success: true };\n}\n\nfunction driverFailure(\n\tkey: ResourceKey,\n\tcause: OpenCloudError,\n): Result<ResourceCurrentState, RawApplyError> {\n\treturn { err: { key, cause, kind: \"driverFailure\" }, success: false };\n}\n\nfunction kindMismatch(key: ResourceKey, mismatch: { actual: string; expected: string }): ApiError {\n\treturn new ApiError(\n\t\t`internal: operation kind mismatch for ${key}: expected ${mismatch.expected}, got ${mismatch.actual}`,\n\t\t{ statusCode: 0 },\n\t);\n}\n\nasync function applyOne<K extends ResourceKind>(\n\top: NonNoopOp & { readonly desired: Extract<ResourceDesiredState, { kind: K }> },\n\tdriver: ResourceDriver<K>,\n): Promise<Result<ResourceCurrentState, RawApplyError>> {\n\tif (op.type === \"create\") {\n\t\tconst created = await driver.create(op.desired);\n\t\treturn created.success ? created : driverFailure(op.key, created.err);\n\t}\n\n\tif (driver.update === undefined) {\n\t\treturn { err: { key: op.key, kind: \"updateUnsupported\" }, success: false };\n\t}\n\n\tif (op.current.kind !== op.desired.kind) {\n\t\treturn driverFailure(\n\t\t\top.key,\n\t\t\tkindMismatch(op.key, { actual: op.current.kind, expected: op.desired.kind }),\n\t\t);\n\t}\n\n\tconst updated = await driver.update(op.current as ResourceCurrentState<K>, op.desired);\n\treturn updated.success ? updated : driverFailure(op.key, updated.err);\n}\n\nasync function dispatchOp(\n\top: NonNoopOp,\n\tregistry: DriverRegistry,\n): Promise<Result<ResourceCurrentState, RawApplyError>> {\n\t// Exhaustive switch: adding a new ResourceKind is a compile error here\n\t// until an arm lands. Each arm casts because custom type narrowing does\n\t// not propagate through a non-distributive union.\n\tswitch (op.desired.kind) {\n\t\tcase \"developerProduct\": {\n\t\t\treturn applyOne(op as DeveloperProductOp, registry.developerProduct);\n\t\t}\n\t\tcase \"gamePass\": {\n\t\t\treturn applyOne(op as GamePassOp, registry.gamePass);\n\t\t}\n\t\tcase \"place\": {\n\t\t\treturn applyOne(op as PlaceOp, registry.place);\n\t\t}\n\t\tcase \"universe\": {\n\t\t\treturn applyOne(op as UniverseOp, registry.universe);\n\t\t}\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\nimport { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\nimport { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\nimport { PlacesClient } from \"@bedrock-rbx/ocale/places\";\nimport { UniversesClient } from \"@bedrock-rbx/ocale/universes\";\n\nimport { createDeveloperProductDriver } from \"../adapters/developer-product-driver.ts\";\nimport { createGamePassDriver } from \"../adapters/game-pass-driver.ts\";\nimport { createPlaceDriver } from \"../adapters/place-driver.ts\";\nimport { createUniverseDriver } from \"../adapters/universe-driver.ts\";\nimport type { ResolvedConfig } from \"../core/schema.ts\";\nimport type { DriverRegistry } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId } from \"../types/ids.ts\";\nimport type { MissingCredentialError } from \"./build-state-port.ts\";\n\n/**\n * Failure surfaced when default-constructing a registry needs a config\n * field that is not present. The deploy boundary wraps this in a\n * `DeployError` so the caller sees a typed Result instead of a downstream\n * driver error.\n */\nexport interface RegistryConfigError {\n\t/** Suggested fix routed back to the caller. */\n\treadonly hint: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"registryConfigMissing\";\n\t/** Which config field was missing. */\n\treadonly missing: \"universeId\";\n}\n\n/** Inputs for {@link buildDefaultRegistry}. */\ninterface BuildDefaultRegistryDeps {\n\t/** Resolved project config; supplies `universe.universeId` and is read for nothing else. */\n\treadonly config: ResolvedConfig;\n\t/** Reads an environment variable; injected so tests stay free of `process.env`. */\n\treadonly getEnv: (name: string) => string | undefined;\n\t/** Reader plumbed into kind-specific drivers that ingest file bytes. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n}\n\ninterface AssembleRegistryInputs {\n\treadonly apiKey: string;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly universeId: ReturnType<typeof asRobloxAssetId>;\n}\n\n/**\n * Construct the default `DriverRegistry` from `config.universe.universeId`\n * and `BEDROCK_API_KEY`. Reads the API key via the injected `getEnv` seam\n * and surfaces `missingCredential` or `registryConfigMissing` as typed\n * Results instead of throwing.\n *\n * @example\n *\n * ```ts\n * import { buildDefaultRegistry } from \"@bedrock-rbx/core\";\n *\n * const registry = buildDefaultRegistry({\n * config: {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * universe: { universeId: \"1234567890\" },\n * },\n * getEnv: () => \"rbx-test\",\n * readFile: async () => new Uint8Array(),\n * });\n *\n * expect(registry.success).toBeTrue();\n * ```\n *\n * @param deps - Validated config plus credential and file-reader seams.\n * @returns A `DriverRegistry` on success, or a typed Err describing the\n * missing API key or the missing universe declaration.\n */\nexport function buildDefaultRegistry(\n\tdeps: BuildDefaultRegistryDeps,\n): Result<DriverRegistry, MissingCredentialError | RegistryConfigError> {\n\tconst apiKey = deps.getEnv(\"BEDROCK_API_KEY\");\n\tif (apiKey === undefined) {\n\t\treturn missingApiKey();\n\t}\n\n\tconst rawUniverseId = deps.config.universe?.universeId;\n\tif (rawUniverseId === undefined) {\n\t\treturn missingUniverseId();\n\t}\n\n\treturn {\n\t\tdata: assembleRegistry({\n\t\t\tapiKey,\n\t\t\treadFile: deps.readFile,\n\t\t\tuniverseId: asRobloxAssetId(rawUniverseId),\n\t\t}),\n\t\tsuccess: true,\n\t};\n}\n\nfunction missingApiKey(): Result<DriverRegistry, MissingCredentialError> {\n\treturn {\n\t\terr: {\n\t\t\tkind: \"missingCredential\",\n\t\t\tpurpose: \"registry\",\n\t\t\tvariable: \"BEDROCK_API_KEY\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction missingUniverseId(): Result<DriverRegistry, RegistryConfigError> {\n\treturn {\n\t\terr: {\n\t\t\thint: \"declare universe.universeId in bedrock.config.ts\",\n\t\t\tkind: \"registryConfigMissing\",\n\t\t\tmissing: \"universeId\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction assembleRegistry(inputs: AssembleRegistryInputs): DriverRegistry {\n\tconst { apiKey, readFile, universeId } = inputs;\n\tconst developerProducts = new DeveloperProductsClient({ apiKey });\n\tconst gamePasses = new GamePassesClient({ apiKey });\n\tconst places = new PlacesClient({ apiKey });\n\tconst universes = new UniversesClient({ apiKey });\n\n\treturn {\n\t\tdeveloperProduct: createDeveloperProductDriver({\n\t\t\tclient: developerProducts,\n\t\t\treadFile,\n\t\t\tuniverseId,\n\t\t}),\n\t\tgamePass: createGamePassDriver({ client: gamePasses, readFile, universeId }),\n\t\tplace: createPlaceDriver({ client: places, readFile, universeId }),\n\t\tuniverse: createUniverseDriver({ places, universes }),\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { ResourceDesiredInput } from \"../core/flatten.ts\";\nimport { defaultKindRegistry } from \"../core/kinds/index.ts\";\nimport type { BuildDesiredError, ResourceKindModule } from \"../core/kinds/module.ts\";\nimport type { ResourceDesiredState, ResourceKind } from \"../core/resources.ts\";\n\nexport type { BuildDesiredError } from \"../core/kinds/module.ts\";\n\n/**\n * Layer file I/O onto a flat tagged list of resource inputs to produce\n * `ResourceDesiredState`.\n *\n * For each input, reads the file bytes via the injected `readFile`, computes\n * the SHA-256 hex digest, and assembles the branded desired-state record\n * that `diff` consumes. Entries are processed sequentially so the first\n * failure's attribution is deterministic.\n *\n * @param inputs - Flat tagged resource inputs from `flattenConfig`.\n * @param readFile - Reads file bytes for a given path; rejection becomes a\n * `fileReadFailed` Err.\n * @returns `Ok` with the desired-state array (same length and order as\n * `inputs`), or `Err` with the first I/O failure.\n * @example\n *\n * ```ts\n * import { asResourceKey, buildDesired } from \"@bedrock-rbx/core\";\n *\n * async function readFile(): Promise<Uint8Array> {\n * return new Uint8Array([1, 2, 3]);\n * }\n *\n * return buildDesired(\n * [\n * {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * ],\n * readFile,\n * ).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toHaveLength(1);\n * expect(result.data[0]!.kind).toBe(\"gamePass\");\n * }\n * });\n * ```\n */\nexport async function buildDesired(\n\tinputs: ReadonlyArray<ResourceDesiredInput>,\n\treadFile: (path: string) => Promise<Uint8Array>,\n): Promise<Result<ReadonlyArray<ResourceDesiredState>, BuildDesiredError>> {\n\tconst desired: Array<ResourceDesiredState> = [];\n\tconst io = { readFile };\n\tfor (const input of inputs) {\n\t\t// Registry index returns a union of per-kind modules; widening its\n\t\t// type parameter lets us call normalize without per-kind\n\t\t// discriminator narrowing. Safe because input.kind pins which\n\t\t// module is selected.\n\t\tconst module = defaultKindRegistry[input.kind] as ResourceKindModule<ResourceKind>;\n\t\tconst normalized = await module.normalize(input, io);\n\t\tif (!normalized.success) {\n\t\t\treturn normalized;\n\t\t}\n\n\t\tdesired.push(normalized.data);\n\t}\n\n\treturn { data: desired, success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { createGistStateAdapter, type GistFetch } from \"../adapters/gist-state-adapter.ts\";\nimport { type GistStateConfig, isGistStateConfig, type StateConfig } from \"../core/schema.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\n\n/**\n * Failure surfaced when a default-constructed adapter cannot find a\n * required environment variable. The deploy boundary wraps this in a\n * `DeployError` so the caller sees a typed Result instead of an\n * exception or a confusing downstream HTTP error.\n */\nexport interface MissingCredentialError {\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"missingCredential\";\n\t/** Whether the credential was needed for the state backend or the driver registry. */\n\treadonly purpose: \"registry\" | \"stateBackend\";\n\t/** Environment variable name the default-construction path tried to read. */\n\treadonly variable: string;\n}\n\n/**\n * Failure surfaced when the dispatch helper sees a `state.backend` value\n * it does not recognize. The hint points at `opts.statePort` so the\n * caller can pass a custom adapter as an escape hatch.\n */\nexport interface UnsupportedBackendError {\n\t/** Backend name read from `state.backend`. */\n\treadonly backend: string;\n\t/** Suggested escape hatch routed back to the caller. */\n\treadonly hint: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"unsupportedBackend\";\n}\n\n/** Inputs for {@link buildStatePort}. */\ninterface BuildStatePortDeps {\n\t/** Optional `fetch` seam plumbed through to the gist adapter for tests. */\n\treadonly fetch?: GistFetch | undefined;\n\t/** Reads an environment variable; injected so tests stay free of `process.env`. */\n\treadonly getEnv: (name: string) => string | undefined;\n\t/** Resolved state configuration for the target environment. */\n\treadonly stateConfig: StateConfig;\n}\n\nconst STATE_PORT_HINT = \"pass a custom statePort via opts.statePort\";\n\n/**\n * Construct a `StatePort` from a resolved `StateConfig`. Dispatches on\n * `stateConfig.backend` to the matching builtin adapter; reads the\n * required credential from `getEnv` and surfaces `missingCredential` or\n * `unsupportedBackend` as typed Results.\n *\n * @example\n *\n * ```ts\n * import { buildStatePort } from \"@bedrock-rbx/core\";\n *\n * const port = buildStatePort({\n * fetch: async () =>\n * new Response(JSON.stringify({ files: {} }), { status: 200 }),\n * getEnv: (name) => (name === \"GITHUB_TOKEN\" ? \"ghp_example\" : undefined),\n * stateConfig: { backend: \"gist\", gistId: \"abc123\" },\n * });\n *\n * expect(port.success).toBeTrue();\n * ```\n *\n * @param deps - Resolved state config plus credential-injection seams.\n * @returns A `StatePort` on success, or a typed Err describing the\n * missing credential or the unsupported backend.\n */\nexport function buildStatePort(\n\tdeps: BuildStatePortDeps,\n): Result<StatePort, MissingCredentialError | UnsupportedBackendError> {\n\tif (isGistStateConfig(deps.stateConfig)) {\n\t\treturn buildGistStatePort(deps.stateConfig, deps);\n\t}\n\n\treturn {\n\t\terr: {\n\t\t\tbackend: deps.stateConfig.backend,\n\t\t\thint: STATE_PORT_HINT,\n\t\t\tkind: \"unsupportedBackend\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction buildGistStatePort(\n\tstateConfig: GistStateConfig,\n\tdeps: BuildStatePortDeps,\n): Result<StatePort, MissingCredentialError> {\n\tconst token = deps.getEnv(\"GITHUB_TOKEN\");\n\tif (token === undefined) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkind: \"missingCredential\",\n\t\t\t\tpurpose: \"stateBackend\",\n\t\t\t\tvariable: \"GITHUB_TOKEN\",\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: createGistStateAdapter({\n\t\t\tfetch: deps.fetch,\n\t\t\tgistId: stateConfig.gistId,\n\t\t\ttoken,\n\t\t}),\n\t\tsuccess: true,\n\t};\n}\n","const LUAU_BOOTSTRAP_TEMP_PREFIX = \"bedrock-luau-bootstrap-\";\n\n/**\n * Build the `mkdtempSync` prefix used for Luau bootstrap directories.\n *\n * Embedding `process.pid` scopes the directory to its creator. Concurrent\n * processes share `tmpdir()`, so without a pid component they would observe\n * each other's in-flight bootstrap dirs whenever any caller scans the temp\n * directory.\n * @param pid - Process id to embed in the prefix; pass `process.pid` from\n * production callers.\n * @returns The full prefix to pass to `mkdtempSync`, including the trailing\n * separator that `mkdtempSync` appends its random suffix to.\n */\nexport function bootstrapDirectoryPrefix(pid: number): string {\n\treturn `${LUAU_BOOTSTRAP_TEMP_PREFIX}${pid}-`;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { execFile } from \"node:child_process\";\nimport { mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport process from \"node:process\";\n\nimport type { LuauEvaluationError, LuauEvaluator } from \"../ports/luau-evaluator.ts\";\nimport { bootstrapDirectoryPrefix } from \"../shell/load-config-internal.ts\";\n\nconst SENTINEL_BASE = \"__BEDROCK_LUAU_\";\nconst OK_PREFIX = `${SENTINEL_BASE}OK__`;\nconst ERR_PREFIX = `${SENTINEL_BASE}ERR__`;\n\nconst LUTE_BOOTSTRAP_LUAU = `--!strict\nlocal json = require(\"@std/json\")\nlocal process = require(\"@std/process\")\nlocal io = require(\"@std/io\")\n\nlocal function emit(kind, payload)\n io.write(\"${SENTINEL_BASE}\" .. kind .. \"__\")\n io.write(json.serialize(payload))\nend\n\nlocal userBasename = process.args[2]\n-- The user file lives in a different directory from this bootstrap, so we\n-- require it via the @user alias defined in the .luaurc written alongside.\nlocal req = \"@user/\" .. string.gsub(userBasename, \"%.luau$\", \"\")\n\nlocal loadOk, modOrErr = pcall(require, req)\nif not loadOk then\n emit(\"ERR\", { kind = \"loadFailed\", message = tostring(modOrErr) })\n return\nend\n\nlocal value = if type(modOrErr) == \"function\" then modOrErr() else modOrErr\n\nlocal encOk, encoded = pcall(json.serialize, value)\nif not encOk then\n emit(\"ERR\", { kind = \"serializeFailed\", message = tostring(encoded) })\n return\nend\n\nio.write(\"${OK_PREFIX}\")\nio.write(encoded)\n`;\n\nconst LUAU_RUNTIME_HINT =\n\t\"install lute (e.g. `mise install` with `github:luau-lang/lute`) or set BEDROCK_LUTE_PATH to the binary.\";\n\ninterface LuteRunOptions {\n\treadonly bin: string;\n\treadonly bootstrapPath: string;\n\treadonly userBasename: string;\n}\n\nfunction isEnoentError(error: unknown): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\n// 5s is generous for evaluating a config file: real configs run in\n// milliseconds, and a value this high is meant to catch infinite loops in\n// user code (or a hung lute) without surprising slow-startup environments.\nconst LUTE_BOOTSTRAP_TIMEOUT_MS = 5_000;\n\n/**\n * Build the default `LuauEvaluator` adapter that shells out to the `lute`\n * runtime. Reads `BEDROCK_LUTE_PATH` from `process.env` once per call to pick\n * the binary, so tests can override it via env var without rebuilding the\n * adapter.\n * @returns A `LuauEvaluator` that spawns `lute run` per call.\n */\nexport function createLuteLuauEvaluator(): LuauEvaluator {\n\treturn evaluateLuauWithLute;\n}\n\nfunction setupBootstrapDirectory(userCwd: string): string {\n\tconst bootstrapDirectory = mkdtempSync(join(tmpdir(), bootstrapDirectoryPrefix(process.pid)));\n\twriteFileSync(join(bootstrapDirectory, \"bootstrap.luau\"), LUTE_BOOTSTRAP_LUAU);\n\t// Lute resolves `require` relative to the calling script's directory, not\n\t// the process cwd. The bootstrap lives in a temp dir, so we expose the\n\t// user's directory via a `.luaurc` alias that the bootstrap requires by\n\t// name.\n\twriteFileSync(\n\t\tjoin(bootstrapDirectory, \".luaurc\"),\n\t\tJSON.stringify({ aliases: { user: userCwd } }),\n\t);\n\treturn bootstrapDirectory;\n}\n\nasync function runLuteBootstrap(runOptions: LuteRunOptions): Promise<string> {\n\tconst { bin, bootstrapPath, userBasename } = runOptions;\n\treturn new Promise((resolve, reject) => {\n\t\texecFile(\n\t\t\tbin,\n\t\t\t[\"run\", bootstrapPath, userBasename],\n\t\t\t{ encoding: \"utf8\", timeout: LUTE_BOOTSTRAP_TIMEOUT_MS },\n\t\t\t(error, stdout) => {\n\t\t\t\tif (error instanceof Error) {\n\t\t\t\t\treject(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve(stdout);\n\t\t\t},\n\t\t);\n\t});\n}\n\nfunction parseBootstrapOutput(stdout: string): Record<string, unknown> {\n\tif (stdout.startsWith(ERR_PREFIX)) {\n\t\t// The bootstrap contract pairs ERR_PREFIX with `{ kind, message }`. Pass\n\t\t// the raw envelope through as the error text rather than reach into the\n\t\t// JSON: any unwrapping logic we add here is paranoid with no test\n\t\t// surface (the bootstrap is the only producer and always conforms).\n\t\tthrow new Error(stdout.slice(ERR_PREFIX.length));\n\t}\n\n\t// Stdout that doesn't carry the OK prefix is unsupported; let JSON.parse\n\t// surface a SyntaxError rather than guard a defensive fallback that has\n\t// no observable behaviour.\n\tconst parsed = JSON.parse(stdout.slice(OK_PREFIX.length));\n\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new TypeError(\"Luau config must return a table at the root\");\n\t}\n\n\treturn parsed;\n}\n\nasync function evaluateLuauWithLute(\n\tabsPath: string,\n): Promise<Result<Record<string, unknown>, LuauEvaluationError>> {\n\tconst overridePath = process.env[\"BEDROCK_LUTE_PATH\"];\n\tconst lute = overridePath !== undefined && overridePath.length > 0 ? overridePath : \"lute\";\n\tconst bootstrapDirectory = setupBootstrapDirectory(dirname(absPath));\n\ttry {\n\t\tconst stdout = await runLuteBootstrap({\n\t\t\tbin: lute,\n\t\t\tbootstrapPath: join(bootstrapDirectory, \"bootstrap.luau\"),\n\t\t\tuserBasename: basename(absPath),\n\t\t});\n\t\treturn { data: parseBootstrapOutput(stdout), success: true };\n\t} catch (err) {\n\t\tif (isEnoentError(err)) {\n\t\t\treturn {\n\t\t\t\terr: { hint: LUAU_RUNTIME_HINT, kind: \"missingRuntime\" },\n\t\t\t\tsuccess: false,\n\t\t\t};\n\t\t}\n\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { err: { kind: \"evaluationFailed\", message }, success: false };\n\t} finally {\n\t\trmSync(bootstrapDirectory, { recursive: true });\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { loadConfig as c12LoadConfig } from \"c12\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, resolve as resolvePath } from \"node:path\";\nimport process from \"node:process\";\n\nimport { createLuteLuauEvaluator } from \"../adapters/lute-luau-evaluator.ts\";\nimport type { ConfigError } from \"../core/config-error.ts\";\nimport { type Config, validateConfig } from \"../core/schema.ts\";\nimport type { LuauEvaluationError, LuauEvaluator } from \"../ports/luau-evaluator.ts\";\n\n/**\n * Options for {@link loadConfig}. Matches a subset of c12's loader options;\n * additional fields land with the issues that introduce each flow.\n */\nexport interface LoadConfigOptions {\n\t/**\n\t * Path to a specific config file to load, including its extension.\n\t * Resolved relative to `cwd` when not absolute. Loaded as-is with no\n\t * extension search; if the file does not exist at the given path,\n\t * `loadConfig` returns `fileNotFound`. When omitted, `loadConfig`\n\t * discovers `bedrock.config.{ts,js,...}` from `cwd`.\n\t */\n\treadonly configFile?: string;\n\t/**\n\t * Directory to search from. Defaults to `process.cwd()` at call time, so\n\t * each invocation sees the current working directory.\n\t */\n\treadonly cwd?: string;\n}\n\ninterface LoadConfigDeps {\n\treadonly evaluator: LuauEvaluator;\n}\n\ninterface LuauResolveResult {\n\t/* eslint-disable-next-line flawless/naming-convention -- c12 reads `_configFile` to detect that a config file was found. */\n\treadonly _configFile: string;\n\treadonly config: Record<string, unknown>;\n\treadonly configFile: string;\n\treadonly cwd: string;\n}\n\n/**\n * Internal entrypoint that lets tests inject a fake `LuauEvaluator`. The\n * public {@link loadConfig} wraps this with the real lute adapter; the rest\n * of the loader pipeline is identical.\n *\n * @param deps - Injected dependencies. Only the evaluator is configurable.\n * @param options - Same loader options accepted by {@link loadConfig}.\n * @returns Same `Result<Config, ConfigError>` shape as `loadConfig`.\n */\nexport async function loadConfigWith(\n\tdeps: LoadConfigDeps,\n\toptions?: LoadConfigOptions,\n): Promise<Result<Config, ConfigError>> {\n\tconst cwd = options?.cwd ?? process.cwd();\n\tconst configFile =\n\t\toptions?.configFile === undefined ? undefined : resolveConfigPath(cwd, options.configFile);\n\tif (configFile !== undefined && !isExistingFile(configFile)) {\n\t\treturn { err: { kind: \"fileNotFound\", searchedFrom: cwd }, success: false };\n\t}\n\n\tlet resolved: Awaited<ReturnType<typeof c12LoadConfig<Record<string, unknown>>>>;\n\ttry {\n\t\tresolved = await c12LoadConfig<Record<string, unknown>>({\n\t\t\tname: \"bedrock\",\n\t\t\tcwd,\n\t\t\tresolve: makeLuauResolver({\n\t\t\t\tcallerConfigFile: configFile,\n\t\t\t\tdefaultCwd: cwd,\n\t\t\t\tevaluator: deps.evaluator,\n\t\t\t}),\n\t\t\t...(configFile === undefined ? {} : { configFile }),\n\t\t});\n\t} catch (err) {\n\t\treturn { err: attributeLoadError(err, cwd), success: false };\n\t}\n\n\tif (resolved._configFile === undefined) {\n\t\treturn { err: { kind: \"fileNotFound\", searchedFrom: cwd }, success: false };\n\t}\n\n\treturn validateConfig(resolved.config, resolved._configFile);\n}\n\n/**\n * Discover, parse, and validate the project config.\n *\n * Looks for `bedrock.config.{ts,js,mjs,cjs,yaml,yml,json}`, `.bedrockrc*`,\n * and `package.json#bedrock` starting at `options.cwd` (or the current\n * working directory). Returns a fresh, mutable `Config` on every call so\n * long-running scripts see up-to-date values.\n *\n * When the exported default is a function (sync or async), `loadConfig`\n * invokes it with an empty `ConfigContext` and awaits the result before\n * validating.\n *\n * Errors return via `Result`:\n * - `fileNotFound` - no config file was discovered under the search path.\n * - `parseFailed` - a config file was found but could not be parsed (for\n * example, malformed YAML or JSON).\n * - `validationFailed` - a file was found and parsed, but its content did\n * not satisfy the runtime schema.\n * - `configFunctionFailed` - a function-form config threw or its returned\n * promise rejected while being invoked.\n *\n * @param options - Loader options.\n * @returns `Ok` with the validated `Config`, or `Err` with a `ConfigError`.\n * @example\n *\n * ```ts\n * import { loadConfig } from \"@bedrock-rbx/core\";\n *\n * return loadConfig({\n * configFile: \"bedrock.staging.config.yaml\",\n * cwd: \"/path/that/does/not/have/a/config\",\n * }).then((result) => {\n * expect(result.success).toBeFalse();\n * if (!result.success) {\n * expect(result.err.kind).toBe(\"fileNotFound\");\n * }\n * });\n * ```\n */\nexport async function loadConfig(\n\toptions?: LoadConfigOptions,\n): Promise<Result<Config, ConfigError>> {\n\treturn loadConfigWith({ evaluator: createLuteLuauEvaluator() }, options);\n}\n\nfunction resolveConfigPath(cwd: string, configFile: string): string {\n\treturn isAbsolute(configFile) ? configFile : join(cwd, configFile);\n}\n\nfunction isExistingFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Resolve a c12 `resolve` source string to an absolute path of a Luau\n * config file, if one applies. Handles two shapes:\n *\n * - Explicit `.luau` paths (`extends: \"./base.luau\"` or absolute) - always\n * ours when the file exists.\n * - The `\".\"` auto-discovery source - claim only when `bedrock.config.luau`\n * is present in the search directory.\n *\n * Returns `undefined` for every other shape so c12 falls through to its\n * built-in resolution.\n * @param source - The c12 `resolve` source string (`\".\"` for auto-discovery,\n * or a relative or absolute path for explicit `extends` references).\n * @param cwd - The directory to resolve relative paths against and to search\n * for `bedrock.config.luau` in.\n * @returns The absolute path of the matched Luau config, or `undefined` to\n * defer to c12's built-in resolution.\n */\nfunction locateLuauConfig(source: string, cwd: string): string | undefined {\n\tif (source.endsWith(\".luau\")) {\n\t\tconst candidate = isAbsolute(source) ? source : resolvePath(cwd, source);\n\t\treturn existsSync(candidate) ? candidate : undefined;\n\t}\n\n\tif (source === \".\") {\n\t\t// Defer to c12's built-in resolution when a native-format config sits\n\t\t// alongside the Luau one. This matches the documented precedence:\n\t\t// TypeScript / JavaScript / JSON / YAML beat Luau when both are\n\t\t// present. Only claim the Luau file when it's the sole candidate.\n\t\tif (\n\t\t\tNATIVE_CONFIG_EXTENSIONS.some((extension) =>\n\t\t\t\texistsSync(join(cwd, `bedrock.config.${extension}`)),\n\t\t\t)\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst candidate = join(cwd, LUAU_CONFIG_BASENAME);\n\t\treturn existsSync(candidate) ? candidate : undefined;\n\t}\n\n\treturn undefined;\n}\n\nconst NATIVE_CONFIG_EXTENSIONS = [\n\t\"ts\",\n\t\"mts\",\n\t\"cts\",\n\t\"js\",\n\t\"mjs\",\n\t\"cjs\",\n\t\"json\",\n\t\"yaml\",\n\t\"yml\",\n] as const;\n\ninterface PickLuauTargetContext {\n\treadonly callerConfigFile: string | undefined;\n\treadonly cwd: string;\n}\n\ninterface LuauResolverDeps {\n\treadonly callerConfigFile: string | undefined;\n\treadonly defaultCwd: string;\n\treadonly evaluator: LuauEvaluator;\n}\n\n/**\n * Internal-only wrapper used at the c12 boundary: makeLuauResolver maps an\n * evaluator `Err` into this throwable, which `attributeLoadError` unwraps\n * directly. This keeps the port on the `Result` contract per ADR-009 while\n * still satisfying c12's exception-based `resolve` callback.\n */\nclass EvaluatorThrow extends Error {\n\tpublic readonly configError: ConfigError;\n\n\tconstructor(configError: ConfigError) {\n\t\tsuper();\n\t\tthis.configError = configError;\n\t}\n}\n\n/**\n * Decide which Luau file the resolver should evaluate for a given c12 source,\n * or `undefined` to defer to c12's built-in loaders.\n *\n * When the caller named a specific configFile, c12 always invokes us with\n * source === \".\" for the main config (and normalizes its own\n * options.configFile to \"bedrock.config\", so we cannot inspect it). Route\n * the explicit path through our evaluator if it points at a `.luau` file;\n * otherwise defer.\n * @param source - The c12 `resolve` source string.\n * @param context - Caller-side state: the resolved cwd to search in, and the\n * caller's configFile path (or undefined when no explicit path was supplied).\n * @returns The absolute path of the Luau file to evaluate, or `undefined`.\n */\nfunction pickLuauTarget(source: string, context: PickLuauTargetContext): string | undefined {\n\tconst { callerConfigFile, cwd } = context;\n\tif (source === \".\" && callerConfigFile !== undefined) {\n\t\treturn callerConfigFile.endsWith(\".luau\") ? callerConfigFile : undefined;\n\t}\n\n\treturn locateLuauConfig(source, cwd);\n}\n\nfunction evaluationErrorToConfigError(err: LuauEvaluationError, sourceFile: string): ConfigError {\n\tif (err.kind === \"missingRuntime\") {\n\t\treturn { hint: err.hint, kind: \"luauRuntimeMissing\", sourceFile };\n\t}\n\n\treturn { kind: \"parseFailed\", message: err.message, sourceFile };\n}\n\nfunction makeLuauResolver(\n\tdeps: LuauResolverDeps,\n): (\n\tsource: string,\n\tc12Options: { readonly cwd?: string },\n) => Promise<LuauResolveResult | undefined> {\n\treturn async (source, c12Options) => {\n\t\tconst cwd = c12Options.cwd ?? deps.defaultCwd;\n\t\tconst luauPath = pickLuauTarget(source, { callerConfigFile: deps.callerConfigFile, cwd });\n\t\tif (luauPath === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await deps.evaluator(luauPath);\n\t\tif (!result.success) {\n\t\t\tthrow new EvaluatorThrow(evaluationErrorToConfigError(result.err, luauPath));\n\t\t}\n\n\t\treturn {\n\t\t\t_configFile: luauPath,\n\t\t\tconfig: result.data,\n\t\t\tconfigFile: luauPath,\n\t\t\tcwd,\n\t\t};\n\t};\n}\n\nconst LUAU_CONFIG_BASENAME = \"bedrock.config.luau\";\n\n// `.luau` is intentionally absent: Luau errors travel through the evaluator\n// adapter's ERR sentinel, not JS stack frames, so the file path is attributed\n// by `discoverConfigFile` rather than scraped from the throw site.\nconst CONFIG_FILE_IN_FRAME = /[^\\s():\"']*bedrock\\.config\\.(?:ts|js|mjs|cjs|yaml|yml|json)/;\n\nfunction extractConfigFileFromStack(err: unknown): string | undefined {\n\tif (!(err instanceof Error) || err.stack === undefined) {\n\t\treturn undefined;\n\t}\n\n\tfor (const rawLine of err.stack.split(\"\\n\")) {\n\t\tconst line = rawLine.trimStart();\n\t\tif (!line.startsWith(\"at \")) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst match = CONFIG_FILE_IN_FRAME.exec(line);\n\t\tif (match !== null) {\n\t\t\treturn match[0];\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction discoverConfigFile(cwd: string): string | undefined {\n\tlet entries: ReadonlyArray<string>;\n\ttry {\n\t\tentries = readdirSync(cwd);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst match = entries.toSorted().find((entry) => entry.startsWith(\"bedrock.config.\"));\n\treturn match === undefined ? undefined : join(cwd, match);\n}\n\nfunction attributeLoadError(err: unknown, cwd: string): ConfigError {\n\tif (err instanceof EvaluatorThrow) {\n\t\treturn err.configError;\n\t}\n\n\tconst message = err instanceof Error ? err.message : String(err);\n\tconst frameFile = extractConfigFileFromStack(err);\n\tif (frameFile !== undefined) {\n\t\treturn { kind: \"configFunctionFailed\", message, sourceFile: frameFile };\n\t}\n\n\treturn { kind: \"parseFailed\", message, sourceFile: discoverConfigFile(cwd) ?? cwd };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { readFile as nodeReadFile } from \"node:fs/promises\";\nimport process from \"node:process\";\n\nimport type { GistFetch } from \"../adapters/gist-state-adapter.ts\";\nimport type { ConfigError } from \"../core/config-error.ts\";\nimport { diff } from \"../core/diff.ts\";\nimport { flattenConfig } from \"../core/flatten.ts\";\nimport { resolveStateConfig, type StateNotConfiguredError } from \"../core/resolve-state-config.ts\";\nimport type { ResourceCurrentState } from \"../core/resources.ts\";\nimport type { Config, ResolvedConfig } from \"../core/schema.ts\";\nimport {\n\ttype IncompletePlaceEntryError,\n\ttype IncompleteUniverseEntryError,\n\tselectEnvironment,\n\ttype UnknownEnvironmentError,\n} from \"../core/select-environment.ts\";\nimport type { BedrockState, StateError } from \"../core/state.ts\";\nimport { validatePlan } from \"../core/validate-plan.ts\";\nimport type { DriverRegistry } from \"../ports/resource-driver.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\nimport { type ApplyError, applyOps } from \"./apply-ops.ts\";\nimport { buildDefaultRegistry, type RegistryConfigError } from \"./build-default-registry.ts\";\nimport { buildDesired, type BuildDesiredError } from \"./build-desired.ts\";\nimport {\n\tbuildStatePort,\n\ttype MissingCredentialError,\n\ttype UnsupportedBackendError,\n} from \"./build-state-port.ts\";\nimport { loadConfig as defaultLoadConfig, type LoadConfigOptions } from \"./load-config.ts\";\n\n/**\n * Inputs for `deploy`. Every field except `environment` is optional;\n * omitted dependencies are default-constructed from the project config\n * and the environment variables `GITHUB_TOKEN` and `BEDROCK_API_KEY`.\n */\nexport interface DeployOptions {\n\t/** Pre-loaded, optionally-mutated project config. Omit to call `loadConfig()` automatically. */\n\treadonly config?: Config;\n\t/** Environment name; threaded into `StatePort.read` and the persisted snapshot. */\n\treadonly environment: string;\n\t/** `fetch` override plumbed into the default-constructed gist adapter when `statePort` is omitted. */\n\treadonly fetch?: GistFetch;\n\t/** Reads an environment variable; defaults to `(name) => process.env[name]`. */\n\treadonly getEnv?: (name: string) => string | undefined;\n\t/** Loader invoked when `config` is omitted; defaults to `loadConfig` from this package. */\n\treadonly loadConfig?: (options?: LoadConfigOptions) => Promise<Result<Config, ConfigError>>;\n\t/** Reads file bytes for resources that have file-backed inputs. Defaults to `node:fs/promises.readFile`. */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n\t/** Per-kind driver table consulted for create / update dispatch. Default-constructed from `BEDROCK_API_KEY` when omitted. */\n\treadonly registry?: DriverRegistry;\n\t/** Backend used to read the prior snapshot and persist the new one. Default-constructed from `config.state` and `GITHUB_TOKEN` when omitted. */\n\treadonly statePort?: StatePort;\n}\n\n/**\n * Failure surfaced by `deploy`. Stage-tagged so callers can branch on\n * `kind` to distinguish reconciliation failures (`stateReadFailed`,\n * `applyFailed`, ...) from default-construction failures\n * (`configLoadFailed`, `stateNotConfigured`, `unknownEnvironment`,\n * `incompletePlaceEntry`, `incompleteUniverseEntry`, `missingCredential`,\n * `unsupportedBackend`, `registryConfigMissing`).\n */\nexport type DeployError =\n\t| IncompletePlaceEntryError\n\t| IncompleteUniverseEntryError\n\t| MissingCredentialError\n\t| RegistryConfigError\n\t| StateNotConfiguredError\n\t| UnknownEnvironmentError\n\t| UnsupportedBackendError\n\t| { readonly cause: ApplyError; readonly kind: \"applyFailed\" }\n\t| { readonly cause: BuildDesiredError; readonly kind: \"buildDesiredFailed\" }\n\t| { readonly cause: ConfigError; readonly kind: \"configLoadFailed\" }\n\t| { readonly cause: StateError; readonly kind: \"stateReadFailed\" }\n\t| {\n\t\t\treadonly cause: StateError;\n\t\t\treadonly kind: \"stateWriteFailed\";\n\t\t\treadonly unsavedState: BedrockState;\n\t };\n\ninterface SnapshotInputs {\n\treadonly applied: Result<ReadonlyArray<ResourceCurrentState>, ApplyError>;\n\treadonly environment: string;\n\treadonly priorResources: ReadonlyArray<ResourceCurrentState>;\n}\n\ninterface FinalizeInputs {\n\treadonly applied: Result<ReadonlyArray<ResourceCurrentState>, ApplyError>;\n\treadonly merged: BedrockState;\n\treadonly written: Result<void, StateError>;\n}\n\ninterface ResolvedDeps {\n\treadonly config: ResolvedConfig;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly registry: DriverRegistry;\n\treadonly statePort: StatePort;\n}\n\ninterface PickRegistryInputs {\n\treadonly config: ResolvedConfig;\n\treadonly options: DeployOptions;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n}\n\n/**\n * Run a full reconcile end-to-end. Default-constructs missing deps from\n * the project config and the environment variables `GITHUB_TOKEN` and\n * `BEDROCK_API_KEY`; never reads `process.env` when `statePort`,\n * `registry`, and `config` are all supplied explicitly.\n *\n * @param options - Target environment plus optional overrides.\n * @returns The persisted `BedrockState` on success, or a stage-tagged\n * `DeployError` on failure.\n * @example\n *\n * ```ts\n * import { deploy } from \"@bedrock-rbx/core\";\n *\n * return deploy({ environment: \"production\" }).then((result) => {\n * expect(result.success).toBeFalse();\n * if (!result.success) {\n * expect([\"configLoadFailed\", \"stateNotConfigured\"]).toContain(result.err.kind);\n * }\n * });\n * ```\n *\n * @example\n *\n * ```ts\n * import { deploy, type BedrockState, type DriverRegistry, type StatePort } from \"@bedrock-rbx/core\";\n *\n * const store = new Map<string, BedrockState>();\n * const statePort: StatePort = {\n * async read(environment) {\n * return { data: store.get(environment), success: true };\n * },\n * async write(state) {\n * store.set(state.environment, state);\n * return { data: undefined, success: true };\n * },\n * };\n * const registry: DriverRegistry = {\n * developerProduct: {\n * create: async () => { throw new Error(\"unreachable: empty config\"); },\n * },\n * gamePass: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * place: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * universe: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * };\n *\n * return deploy({\n * config: {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * passes: {},\n * },\n * environment: \"production\",\n * registry,\n * statePort,\n * }).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.environment).toBe(\"production\");\n * expect(result.data.resources).toBeEmpty();\n * }\n * });\n * ```\n */\nexport async function deploy(options: DeployOptions): Promise<Result<BedrockState, DeployError>> {\n\tconst resolved = await resolveDeps(options);\n\tif (!resolved.success) {\n\t\treturn resolved;\n\t}\n\n\treturn runReconcile(options.environment, resolved.data);\n}\n\nasync function pickConfig(options: DeployOptions): Promise<Result<Config, DeployError>> {\n\tif (options.config !== undefined) {\n\t\treturn { data: options.config, success: true };\n\t}\n\n\tconst loader = options.loadConfig ?? defaultLoadConfig;\n\tconst loaded = await loader();\n\tif (!loaded.success) {\n\t\treturn { err: { cause: loaded.err, kind: \"configLoadFailed\" }, success: false };\n\t}\n\n\treturn { data: loaded.data, success: true };\n}\n\nfunction readProcessEnvironment(name: string): string | undefined {\n\treturn process.env[name];\n}\n\nfunction getEnvironmentOf(options: DeployOptions): (name: string) => string | undefined {\n\treturn options.getEnv ?? readProcessEnvironment;\n}\n\nfunction pickStatePort(\n\toptions: DeployOptions,\n\tconfig: ResolvedConfig,\n): Result<StatePort, DeployError> {\n\tif (options.statePort !== undefined) {\n\t\treturn { data: options.statePort, success: true };\n\t}\n\n\tconst stateConfig = resolveStateConfig(config, options.environment);\n\tif (!stateConfig.success) {\n\t\treturn { err: stateConfig.err, success: false };\n\t}\n\n\treturn buildStatePort({\n\t\tfetch: options.fetch,\n\t\tgetEnv: getEnvironmentOf(options),\n\t\tstateConfig: stateConfig.data,\n\t});\n}\n\nfunction pickRegistry(inputs: PickRegistryInputs): Result<DriverRegistry, DeployError> {\n\tif (inputs.options.registry !== undefined) {\n\t\treturn { data: inputs.options.registry, success: true };\n\t}\n\n\treturn buildDefaultRegistry({\n\t\tconfig: inputs.config,\n\t\tgetEnv: getEnvironmentOf(inputs.options),\n\t\treadFile: inputs.readFile,\n\t});\n}\n\nasync function resolveDeps(options: DeployOptions): Promise<Result<ResolvedDeps, DeployError>> {\n\tconst config = await pickConfig(options);\n\tif (!config.success) {\n\t\treturn config;\n\t}\n\n\tconst selected = selectEnvironment(config.data, options.environment);\n\tif (!selected.success) {\n\t\treturn { err: selected.err, success: false };\n\t}\n\n\tconst effective = selected.data;\n\tconst readFile = options.readFile ?? nodeReadFile;\n\n\tconst statePort = pickStatePort(options, effective);\n\tif (!statePort.success) {\n\t\treturn statePort;\n\t}\n\n\tconst registry = pickRegistry({ config: effective, options, readFile });\n\tif (!registry.success) {\n\t\treturn registry;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tconfig: effective,\n\t\t\treadFile,\n\t\t\tregistry: registry.data,\n\t\t\tstatePort: statePort.data,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction mergeResources(\n\tpre: ReadonlyArray<ResourceCurrentState>,\n\tapplied: ReadonlyArray<ResourceCurrentState>,\n): ReadonlyArray<ResourceCurrentState> {\n\tconst byKey = new Map<string, ResourceCurrentState>();\n\tfor (const resource of pre) {\n\t\tbyKey.set(`${resource.kind}:${resource.key}`, resource);\n\t}\n\n\tfor (const resource of applied) {\n\t\tbyKey.set(`${resource.kind}:${resource.key}`, resource);\n\t}\n\n\treturn [...byKey.values()];\n}\n\nfunction buildSnapshot(inputs: SnapshotInputs): BedrockState {\n\tconst appliedResources = inputs.applied.success\n\t\t? inputs.applied.data\n\t\t: inputs.applied.err.appliedSoFar;\n\treturn {\n\t\tenvironment: inputs.environment,\n\t\tresources: mergeResources(inputs.priorResources, appliedResources),\n\t\tversion: 1,\n\t};\n}\n\nfunction finalize(inputs: FinalizeInputs): Result<BedrockState, DeployError> {\n\tif (!inputs.applied.success) {\n\t\treturn { err: { cause: inputs.applied.err, kind: \"applyFailed\" }, success: false };\n\t}\n\n\tif (!inputs.written.success) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tcause: inputs.written.err,\n\t\t\t\tkind: \"stateWriteFailed\",\n\t\t\t\tunsavedState: inputs.merged,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: inputs.merged, success: true };\n}\n\nasync function runReconcile(\n\tenvironment: string,\n\tdeps: ResolvedDeps,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst desired = await buildDesired(flattenConfig(deps.config), deps.readFile);\n\tif (!desired.success) {\n\t\treturn { err: { cause: desired.err, kind: \"buildDesiredFailed\" }, success: false };\n\t}\n\n\tconst prior = await deps.statePort.read(environment);\n\tif (!prior.success) {\n\t\treturn { err: { cause: prior.err, kind: \"stateReadFailed\" }, success: false };\n\t}\n\n\tconst priorResources = prior.data?.resources ?? [];\n\tconst validated = validatePlan(desired.data, priorResources);\n\tif (!validated.success) {\n\t\treturn { err: { cause: validated.err, kind: \"buildDesiredFailed\" }, success: false };\n\t}\n\n\tconst ops = diff(desired.data, priorResources);\n\tconst applied = await applyOps(ops, deps.registry);\n\tconst merged = buildSnapshot({ applied, environment, priorResources });\n\n\tconst written = await deps.statePort.write(merged);\n\treturn finalize({ applied, merged, written });\n}\n","import { asResourceKey, asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport {\n\ttype ResourceCurrentState,\n\tUNIVERSE_SINGLETON_KEY,\n\ttype UniverseOutputs,\n} from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport type { BedrockState } from \"../state.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { PassFoldEntry } from \"./fold-passes.ts\";\nimport type { PlaceFoldEntry } from \"./fold-places.ts\";\nimport type { ProductFoldEntry } from \"./fold-products.ts\";\n\n/**\n * Inputs to {@link buildState}. Bundled into one object because the\n * call site already groups these per-environment values together (the\n * shell threads them in lockstep) and named fields read better than a\n * three-positional-argument signature.\n */\ninterface BuildStateInputs {\n\t/** Environment name; written verbatim onto the state. */\n\treadonly environment: string;\n\t/** Per-kind fold results for this environment. */\n\treadonly folded: EnvironmentFoldResult;\n\t/**\n\t * Per-pass-key locale-keyed icon hashes recomputed from disk by the\n\t * shell. Keys absent from the map fall back to `mantleIconFileHashes`\n\t * from the matching fold entry.\n\t */\n\treadonly passIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\t/**\n\t * Per-product-key locale-keyed icon hashes recomputed from disk by the\n\t * shell. Keys absent from the map fall back to `mantleIconFileHashes`\n\t * from the matching fold entry; products without any icon partner emit\n\t * resources without `iconFileHashes`.\n\t */\n\treadonly productIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n}\n\ninterface UniverseResourceInputs {\n\treadonly entry: ResolvedUniverseEntry;\n\treadonly outputs: UniverseOutputs;\n}\n\n/**\n * Compose one environment's folded data into the on-disk `BedrockState`\n * snapshot the migrator's caller writes per environment.\n *\n * The resulting state carries one `kind: \"universe\"` resource (when an\n * experience folded), followed by one `kind: \"place\"` resource per\n * matched place pair, then one `kind: \"gamePass\"` resource per folded\n * pass entry, then one `kind: \"developerProduct\"` resource per folded\n * product entry, in declaration order. Each resource's declared fields\n * mirror its fold output; pass and product resources receive their\n * `iconFileHashes` from the matching `*IconHashesByKey` map (computed by\n * the shell from the icon file's bytes) and fall back to the\n * Mantle-recorded hashes when the map omits the key. Product resources\n * without an icon partner omit `icon` and `iconFileHashes` entirely. The\n * `outputs` field carries the Mantle-recorded identifiers (universe\n * `rootPlaceId`, place `versionNumber`, pass `assetId` and `iconAssetIds`,\n * product `productId` and optional `iconImageAssetId`).\n *\n * @param inputs - Folded data plus recomputed hashes for this environment.\n * @returns A `BedrockState` populated with one resource per folded kind.\n */\nexport function buildState(inputs: BuildStateInputs): BedrockState {\n\treturn {\n\t\tenvironment: inputs.environment,\n\t\tresources: composeResources(inputs),\n\t\tversion: 1,\n\t};\n}\n\nfunction universeResource(inputs: UniverseResourceInputs): ResourceCurrentState<\"universe\"> {\n\tconst { entry, outputs } = inputs;\n\treturn {\n\t\tkey: UNIVERSE_SINGLETON_KEY,\n\t\tconsoleEnabled: entry.consoleEnabled,\n\t\tdesktopEnabled: entry.desktopEnabled,\n\t\tdisplayName: entry.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: entry.mobileEnabled,\n\t\toutputs,\n\t\ttabletEnabled: entry.tabletEnabled,\n\t\tuniverseId: asRobloxAssetId(entry.universeId),\n\t\tvoiceChatEnabled: entry.voiceChatEnabled,\n\t\tvrEnabled: entry.vrEnabled,\n\t};\n}\n\nfunction placeResource(key: string, fold: PlaceFoldEntry): ResourceCurrentState<\"place\"> {\n\treturn {\n\t\tkey: asResourceKey(key),\n\t\tdescription: fold.entry.description,\n\t\tdisplayName: fold.entry.displayName,\n\t\tfileHash: fold.fileHash,\n\t\tfilePath: fold.entry.filePath,\n\t\tkind: \"place\",\n\t\toutputs: fold.outputs,\n\t\tplaceId: asRobloxAssetId(fold.placeId),\n\t\tserverSize: fold.entry.serverSize,\n\t};\n}\n\nfunction passResource(\n\tfold: PassFoldEntry,\n\ticonFileHashes: Record<\"en-us\", Sha256Hex>,\n): ResourceCurrentState<\"gamePass\"> {\n\treturn {\n\t\tkey: fold.key,\n\t\tname: fold.entry.name,\n\t\tdescription: fold.entry.description,\n\t\ticon: fold.entry.icon,\n\t\ticonFileHashes,\n\t\tkind: \"gamePass\",\n\t\toutputs: fold.outputs,\n\t\tprice: fold.entry.price,\n\t};\n}\n\nfunction productResource(\n\tfold: ProductFoldEntry,\n\tproductIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>,\n): ResourceCurrentState<\"developerProduct\"> {\n\tconst base: ResourceCurrentState<\"developerProduct\"> = {\n\t\tkey: fold.key,\n\t\tname: fold.entry.name,\n\t\tdescription: fold.entry.description,\n\t\tisRegionalPricingEnabled: undefined,\n\t\tkind: \"developerProduct\",\n\t\toutputs: fold.outputs,\n\t\tprice: fold.entry.price,\n\t\tstorePageEnabled: undefined,\n\t};\n\n\tif (fold.entry.icon === undefined || fold.mantleIconFileHashes === undefined) {\n\t\treturn base;\n\t}\n\n\treturn {\n\t\t...base,\n\t\ticon: fold.entry.icon,\n\t\ticonFileHashes: productIconHashesByKey.get(fold.key) ?? fold.mantleIconFileHashes,\n\t};\n}\n\nfunction composeResources(inputs: BuildStateInputs): ReadonlyArray<ResourceCurrentState> {\n\tconst { folded, passIconHashesByKey, productIconHashesByKey } = inputs;\n\tconst universeResources: ReadonlyArray<ResourceCurrentState> =\n\t\tfolded.universe === undefined\n\t\t\t? []\n\t\t\t: [\n\t\t\t\t\tuniverseResource({\n\t\t\t\t\t\tentry: folded.universe.entry,\n\t\t\t\t\t\toutputs: folded.universe.outputs,\n\t\t\t\t\t}),\n\t\t\t\t];\n\n\tconst placeResources: ReadonlyArray<ResourceCurrentState> = [...folded.places.entries()].map(\n\t\t([key, entry]) => placeResource(key, entry),\n\t);\n\n\tconst passResources: ReadonlyArray<ResourceCurrentState> = folded.passes.map((entry) => {\n\t\treturn passResource(\n\t\t\tentry,\n\t\t\tpassIconHashesByKey.get(entry.key) ?? entry.mantleIconFileHashes,\n\t\t);\n\t});\n\n\tconst productResources: ReadonlyArray<ResourceCurrentState> = folded.products.map((entry) => {\n\t\treturn productResource(entry, productIconHashesByKey);\n\t});\n\n\treturn [...universeResources, ...placeResources, ...passResources, ...productResources];\n}\n","/**\n * Result of inspecting a display name for a Mantle-style `[LABEL]` prefix.\n * `label` is `undefined` when the input does not match the bracketed-prefix\n * shape Mantle emits; in that case `body` echoes the input unchanged.\n */\ninterface ExtractedDisplayName {\n\t/** Display name with the bracketed prefix and trailing whitespace stripped, or the input verbatim when no prefix matched. */\n\treadonly body: string;\n\t/** Captured label from the bracketed prefix, or `undefined` when the input did not match. */\n\treadonly label: string | undefined;\n}\n\n/**\n * Lift the leading bracketed prefix off a display name produced by Mantle's\n * environment-name stamping. A match requires `[LABEL] body` with a\n * non-empty label, mandatory whitespace after the closing bracket, and a\n * non-empty body. When the input does not match, `label` is `undefined` and\n * `body` is the input verbatim, so callers can round-trip arbitrary\n * display-name shapes without losing data.\n *\n * @param value - Raw display name to inspect.\n * @returns The captured label (or `undefined`) and the unprefixed body.\n */\nexport function extractDisplayNamePrefix(value: string): ExtractedDisplayName {\n\tconst passthrough: ExtractedDisplayName = { body: value, label: undefined };\n\tif (!value.startsWith(\"[\")) {\n\t\treturn passthrough;\n\t}\n\n\tconst closeIndex = value.indexOf(\"]\");\n\tif (closeIndex < 2) {\n\t\treturn passthrough;\n\t}\n\n\tconst afterBracket = value.slice(closeIndex + 1);\n\tconst body = afterBracket.trimStart();\n\tif (body === afterBracket) {\n\t\treturn passthrough;\n\t}\n\n\tif (body === \"\") {\n\t\treturn passthrough;\n\t}\n\n\treturn { body, label: value.slice(1, closeIndex) };\n}\n","import { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\n\n/**\n * Compute the environment-label candidate that round-trips a Mantle-style\n * `[LABEL]` prefix through bedrock's deploy-time prefix system. Inspects\n * every present `displayName` on the env's universe and place folds; if all\n * extractions agree on the same captured label the function returns it\n * lowercased. Any disagreement (mixed prefixes, prefixed alongside\n * unprefixed, or all unprefixed) yields `undefined`, signalling that the\n * caller should keep raw per-environment displayName overrides instead of\n * promoting a label.\n *\n * @param fold - Per-environment fold result to inspect.\n * @returns Lowercased label string when every displayName shares one\n * bracketed prefix; `undefined` otherwise.\n */\nexport function computeEnvironmentLabel(fold: EnvironmentFoldResult): string | undefined {\n\tconst displayNames = [\n\t\tfold.universe?.entry.displayName,\n\t\t...[...fold.places.values()].map(({ entry }) => entry.displayName),\n\t].filter((displayName): displayName is string => displayName !== undefined);\n\tconst labels = new Set(\n\t\tdisplayNames.map((displayName) => extractDisplayNamePrefix(displayName).label),\n\t);\n\tif (labels.size !== 1) {\n\t\treturn undefined;\n\t}\n\n\tconst [label] = labels;\n\treturn label?.toLowerCase();\n}\n","import type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\n\nconst RESOURCE_MISSING_RULE = \"factorize-environments/resource-missing-from-env\";\n\n/**\n * Inputs to {@link collectMissingResourceWarnings}.\n */\ninterface MissingResourceInputs {\n\t/** Per-environment fold results, keyed by environment name. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/** The chosen primary environment, used as the symmetry baseline. */\n\treadonly primary: { readonly fold: EnvironmentFoldResult; readonly name: string };\n}\n\ninterface AsymmetryContext {\n\treadonly environmentName: string;\n\treadonly fold: EnvironmentFoldResult;\n\treadonly primary: EnvironmentFoldResult;\n}\n\ninterface MissingResourcePaths {\n\treadonly bedrockSegment: string;\n\treadonly environmentName: string;\n\treadonly mantleSegment: string;\n}\n\ninterface KeyedAsymmetrySpec {\n\treadonly bedrockPrefix: string;\n\treadonly mantlePrefix: string;\n\treadonly readKeys: (fold: EnvironmentFoldResult) => ReadonlyArray<string>;\n}\n\nconst KEYED_ASYMMETRY_SPECS: ReadonlyArray<KeyedAsymmetrySpec> = [\n\t{\n\t\tbedrockPrefix: \"places\",\n\t\tmantlePrefix: \"place_\",\n\t\treadKeys: (fold) => [...fold.places.keys()],\n\t},\n\t{\n\t\tbedrockPrefix: \"passes\",\n\t\tmantlePrefix: \"pass_\",\n\t\treadKeys: (fold) => fold.passes.map(({ key }) => key),\n\t},\n\t{\n\t\tbedrockPrefix: \"products\",\n\t\tmantlePrefix: \"product_\",\n\t\treadKeys: (fold) => fold.products.map(({ key }) => key),\n\t},\n];\n\n/**\n * Walk every environment fold and emit one `interpretive` warning per\n * resource the environment lacks relative to the chosen primary, and per\n * resource the environment introduces that the primary lacks. The walk\n * covers the universe singleton plus every keyed kind in\n * {@link KEYED_ASYMMETRY_SPECS}.\n *\n * @param inputs - Per-environment folds and the resolved primary.\n * @returns Flat list of resource-asymmetry warnings.\n */\nexport function collectMissingResourceWarnings(\n\tinputs: MissingResourceInputs,\n): ReadonlyArray<MigrationWarning> {\n\tconst primaryFold = inputs.primary.fold;\n\treturn [...inputs.folds.entries()].flatMap(([name, fold]): ReadonlyArray<MigrationWarning> => {\n\t\tconst context: AsymmetryContext = { environmentName: name, fold, primary: primaryFold };\n\t\treturn [\n\t\t\t...universeAsymmetryWarnings(context),\n\t\t\t...KEYED_ASYMMETRY_SPECS.flatMap((spec) => keyedAsymmetryWarnings(context, spec)),\n\t\t];\n\t});\n}\n\nfunction asymmetricKeys(\n\tenvironmentKeys: ReadonlyArray<string>,\n\tprimaryKeys: ReadonlyArray<string>,\n): ReadonlyArray<string> {\n\tconst environmentSet = new Set(environmentKeys);\n\tconst primarySet = new Set(primaryKeys);\n\tconst onlyInEnvironment = environmentKeys.filter((key) => !primarySet.has(key));\n\tconst onlyInPrimary = primaryKeys.filter((key) => !environmentSet.has(key));\n\treturn [...onlyInEnvironment, ...onlyInPrimary];\n}\n\nfunction missingResourceWarning(paths: MissingResourcePaths): MigrationWarning {\n\treturn {\n\t\tbedrockPath: `environments.${paths.environmentName}.${paths.bedrockSegment}`,\n\t\tkind: \"interpretive\",\n\t\tmantlePath: `${paths.environmentName}.${paths.mantleSegment}`,\n\t\trule: RESOURCE_MISSING_RULE,\n\t};\n}\n\nfunction keyedAsymmetryWarnings(\n\tcontext: AsymmetryContext,\n\tspec: KeyedAsymmetrySpec,\n): ReadonlyArray<MigrationWarning> {\n\treturn asymmetricKeys(spec.readKeys(context.fold), spec.readKeys(context.primary)).map(\n\t\t(key) => {\n\t\t\treturn missingResourceWarning({\n\t\t\t\tbedrockSegment: `${spec.bedrockPrefix}.${key}`,\n\t\t\t\tenvironmentName: context.environmentName,\n\t\t\t\tmantleSegment: `${spec.mantlePrefix}${key}`,\n\t\t\t});\n\t\t},\n\t);\n}\n\nfunction universeAsymmetryWarnings(context: AsymmetryContext): ReadonlyArray<MigrationWarning> {\n\tconst hasEnvironmentUniverse = context.fold.universe !== undefined;\n\tconst hasPrimaryUniverse = context.primary.universe !== undefined;\n\tif (hasEnvironmentUniverse === hasPrimaryUniverse) {\n\t\treturn [];\n\t}\n\n\treturn [\n\t\tmissingResourceWarning({\n\t\t\tbedrockSegment: \"universe\",\n\t\t\tenvironmentName: context.environmentName,\n\t\t\tmantleSegment: \"experience_singleton\",\n\t\t}),\n\t];\n}\n","import type { EnvironmentEntry, PlaceEntry } from \"../schema.ts\";\nimport { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { PlaceFoldEntry } from \"./fold-places.ts\";\n\n/**\n * Inputs to {@link buildRootPlaces}.\n */\ninterface BuildRootPlacesInputs {\n\t/** Per-environment fold results, keyed by environment name. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/**\n\t * Per-environment label map. An environment whose label is `undefined`\n\t * contributes raw `displayName` values to consensus; otherwise the value\n\t * is stripped via {@link extractDisplayNamePrefix} before comparison.\n\t */\n\treadonly labels: ReadonlyMap<string, string | undefined>;\n\t/** The chosen primary environment's fold; supplies `filePath` for every place entry on root. */\n\treadonly primaryFold: EnvironmentFoldResult;\n}\n\n/**\n * Inputs to {@link buildPlacesOverlay}.\n */\ninterface BuildPlacesOverlayInputs {\n\t/** The per-environment fold whose places are being overlaid. */\n\treadonly fold: EnvironmentFoldResult;\n\t/** The environment's label (or `undefined`); enables prefix-stripping on the overlay's `displayName`. */\n\treadonly label: string | undefined;\n\t/** The already-built root `places` block; matching field values suppress overlay entries. */\n\treadonly rootPlaces: Record<string, PlaceEntry> | undefined;\n}\n\ntype PlaceOverlayEntry = NonNullable<EnvironmentEntry[\"places\"]>[string];\n\ntype OptionalPlaceField = \"description\" | \"serverSize\";\n\ninterface LabeledFold {\n\treadonly fold: EnvironmentFoldResult;\n\treadonly label: string | undefined;\n}\n\ninterface ConsensusInputs<F extends OptionalPlaceField> {\n\treadonly field: F;\n\treadonly folds: ReadonlyArray<LabeledFold>;\n\treadonly placeKey: string;\n}\n\ninterface DisplayNameConsensusInputs {\n\treadonly folds: ReadonlyArray<LabeledFold>;\n\treadonly placeKey: string;\n}\n\ninterface BuildPlaceOverlayEntryInputs {\n\treadonly fold: PlaceFoldEntry;\n\treadonly label: string | undefined;\n\treadonly rootEntry: PlaceEntry | undefined;\n}\n\n/**\n * Project the per-environment place folds into bedrock's root `places`\n * block, giving each optional metadata field (`description`, `displayName`,\n * `serverSize`) a value only when every environment that owns the place\n * key agrees. `displayName` consensus runs on the unprefixed body when an\n * environment carries a label, so a Mantle-stamped `[ENV] Foo` in one\n * environment matches a plain `Foo` in another. Divergent or partially-set\n * fields are omitted from root and surface on each environment's overlay\n * via {@link buildPlacesOverlay}.\n *\n * @param inputs - Per-environment folds, label map, and primary fold.\n * @returns The root `places` block, or `undefined` when the primary has\n * no place folds.\n */\nexport function buildRootPlaces(\n\tinputs: BuildRootPlacesInputs,\n): Record<string, PlaceEntry> | undefined {\n\tconst { folds, labels, primaryFold } = inputs;\n\tif (primaryFold.places.size === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst folded: ReadonlyArray<LabeledFold> = [...folds.entries()].map(([name, fold]) => {\n\t\treturn { fold, label: labels.get(name) };\n\t});\n\treturn Object.fromEntries(\n\t\t[...primaryFold.places.entries()].map(([placeKey, primary]) => [\n\t\t\tplaceKey,\n\t\t\tbuildRootPlaceEntry(primary, { folds: folded, placeKey }),\n\t\t]),\n\t);\n}\n\n/**\n * Build the per-environment overlay for `places`, carrying each field only\n * when the environment's value diverges from the resolved root entry. When\n * the environment has a label, the overlay stores the unprefixed\n * `displayName` body so the deploy-time prefix system reapplies the bracket\n * cleanly without double-prefixing. Fields the environment omits are absent\n * from the overlay so the consumer's defu merge resolves them to the root's\n * (also absent) value rather than overwriting with `undefined`.\n *\n * @param inputs - Fold, label, and the already-built root `places` block.\n * @returns The overlay `places` block, or `undefined` when the fold has\n * no place entries.\n */\nexport function buildPlacesOverlay(\n\tinputs: BuildPlacesOverlayInputs,\n): Record<string, PlaceOverlayEntry> | undefined {\n\tconst { fold, label, rootPlaces } = inputs;\n\tif (fold.places.size === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(\n\t\t[...fold.places.entries()].map(([placeKey, foldEntry]) => {\n\t\t\treturn [\n\t\t\t\tplaceKey,\n\t\t\t\tbuildPlaceOverlayEntry({\n\t\t\t\t\tfold: foldEntry,\n\t\t\t\t\tlabel,\n\t\t\t\t\trootEntry: rootPlaces?.[placeKey],\n\t\t\t\t}),\n\t\t\t];\n\t\t}),\n\t);\n}\n\nfunction placeFieldConsensus<F extends OptionalPlaceField>(\n\tinputs: ConsensusInputs<F>,\n): PlaceEntry[F] | undefined {\n\tconst values = inputs.folds.flatMap(({ fold }) => {\n\t\tconst entry = fold.places.get(inputs.placeKey)?.entry;\n\t\treturn entry === undefined ? [] : [entry[inputs.field]];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => Object.is(first, value)) ? first : undefined;\n}\n\nfunction resolveDisplayName(\n\tdisplayName: string | undefined,\n\tlabel: string | undefined,\n): string | undefined {\n\tif (displayName === undefined || label === undefined) {\n\t\treturn displayName;\n\t}\n\n\treturn extractDisplayNamePrefix(displayName).body;\n}\n\nfunction displayNameConsensus(inputs: DisplayNameConsensusInputs): string | undefined {\n\tconst values = inputs.folds.flatMap(({ fold, label }): ReadonlyArray<string | undefined> => {\n\t\tconst entry = fold.places.get(inputs.placeKey)?.entry;\n\t\treturn entry === undefined ? [] : [resolveDisplayName(entry.displayName, label)];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => Object.is(first, value)) ? first : undefined;\n}\n\nfunction buildRootPlaceEntry(\n\tprimary: PlaceFoldEntry,\n\tconsensusBase: {\n\t\treadonly folds: ReadonlyArray<LabeledFold>;\n\t\treadonly placeKey: string;\n\t},\n): PlaceEntry {\n\tconst description = placeFieldConsensus({ ...consensusBase, field: \"description\" });\n\tconst displayName = displayNameConsensus(consensusBase);\n\tconst serverSize = placeFieldConsensus({ ...consensusBase, field: \"serverSize\" });\n\treturn {\n\t\tfilePath: primary.entry.filePath,\n\t\t...(description !== undefined && { description }),\n\t\t...(displayName !== undefined && { displayName }),\n\t\t...(serverSize !== undefined && { serverSize }),\n\t};\n}\n\nfunction buildPlaceOverlayEntry(inputs: BuildPlaceOverlayEntryInputs): PlaceOverlayEntry {\n\tconst { fold, label, rootEntry } = inputs;\n\tconst { description, displayName: rawDisplayName, filePath, serverSize } = fold.entry;\n\tconst displayName = resolveDisplayName(rawDisplayName, label);\n\treturn {\n\t\tplaceId: fold.placeId,\n\t\t...(filePath !== rootEntry?.filePath && { filePath }),\n\t\t...(!Object.is(rootEntry?.description, description) && { description }),\n\t\t...(!Object.is(rootEntry?.displayName, displayName) && { displayName }),\n\t\t...(!Object.is(rootEntry?.serverSize, serverSize) && { serverSize }),\n\t};\n}\n","import type { DeveloperProductEntry, EnvironmentEntry } from \"../schema.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { ProductFoldEntry } from \"./fold-products.ts\";\n\ntype ProductOverlayEntry = NonNullable<EnvironmentEntry[\"products\"]>[string];\n\ntype OptionalProductField = \"icon\" | \"isRegionalPricingEnabled\" | \"price\" | \"storePageEnabled\";\n\ninterface ConsensusInputs<F extends OptionalProductField> {\n\treadonly field: F;\n\treadonly folds: ReadonlyArray<EnvironmentFoldResult>;\n\treadonly productKey: string;\n}\n\ninterface FieldEqualInputs {\n\treadonly field: OptionalProductField;\n\treadonly left: DeveloperProductEntry[OptionalProductField];\n\treadonly right: DeveloperProductEntry[OptionalProductField];\n}\n\n/**\n * Project the per-environment product folds into bedrock's root `products`\n * block, giving each optional field (`icon`, `price`, `isRegionalPricingEnabled`,\n * `storePageEnabled`) a value only when every environment that owns the\n * product key agrees. Required fields (`name`, `description`) fall back to\n * the primary's values; divergence on those surfaces in each environment's\n * overlay. Optional fields that diverge stay off root so an environment that\n * omits an optional field does not silently inherit the primary's value\n * through defu's \"undefined treated as empty\" merge.\n *\n * @param folds - Per-environment fold results, keyed by environment name.\n * @param primaryFold - The chosen primary environment's fold; supplies the\n * product key list and the `name` / `description` fallbacks.\n * @returns The root `products` block, or `undefined` when the primary has\n * no product folds.\n */\nexport function buildRootProducts(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tprimaryFold: EnvironmentFoldResult,\n): Record<string, DeveloperProductEntry> | undefined {\n\tif (primaryFold.products.length === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst folded = [...folds.values()];\n\treturn Object.fromEntries(\n\t\tprimaryFold.products.map(({ key, entry }) => [\n\t\t\tkey,\n\t\t\tbuildRootProductEntry(entry, { folds: folded, productKey: key }),\n\t\t]),\n\t);\n}\n\n/**\n * Build the per-environment overlay for `products`, carrying each field only\n * when the environment's value diverges from the resolved root entry. Fields\n * the environment omits are absent from the overlay so the consumer's defu\n * merge resolves them to the root's (also absent) value rather than\n * inheriting a primary value the environment never had.\n *\n * @param fold - The per-environment fold whose products are being overlaid.\n * @param rootProducts - The already-built root `products` block; per-product\n * field values from this map suppress overlay entries that match.\n * @returns The overlay `products` block, or `undefined` when no product\n * diverges from the root.\n */\nexport function buildProductsOverlay(\n\tfold: EnvironmentFoldResult,\n\trootProducts: Record<string, DeveloperProductEntry> | undefined,\n): Record<string, ProductOverlayEntry> | undefined {\n\tconst overlay: Record<string, ProductOverlayEntry> = {};\n\tfor (const { key, entry } of fold.products) {\n\t\tconst productOverlay = buildProductOverlayEntry(entry, rootProducts?.[key]);\n\t\tif (Object.keys(productOverlay).length > 0) {\n\t\t\toverlay[key] = productOverlay;\n\t\t}\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction entryForKey(\n\tfold: EnvironmentFoldResult,\n\tproductKey: string,\n): DeveloperProductEntry | undefined {\n\treturn fold.products.find(({ key }) => key === productKey)?.entry;\n}\n\nfunction asIcon(\n\tvalue: DeveloperProductEntry[OptionalProductField],\n): Record<\"en-us\", string> | undefined {\n\treturn typeof value === \"object\" ? value : undefined;\n}\n\nfunction optionalFieldEqual(inputs: FieldEqualInputs): boolean {\n\tif (inputs.field === \"icon\") {\n\t\treturn Object.is(asIcon(inputs.left)?.[\"en-us\"], asIcon(inputs.right)?.[\"en-us\"]);\n\t}\n\n\treturn Object.is(inputs.left, inputs.right);\n}\n\nfunction productOptionalFieldConsensus<F extends OptionalProductField>(\n\tinputs: ConsensusInputs<F>,\n): DeveloperProductEntry[F] | undefined {\n\tconst values = inputs.folds.flatMap((fold): ReadonlyArray<DeveloperProductEntry[F]> => {\n\t\tconst entry = entryForKey(fold, inputs.productKey);\n\t\treturn entry === undefined ? [] : [entry[inputs.field]];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => {\n\t\treturn optionalFieldEqual({ field: inputs.field, left: first, right: value });\n\t})\n\t\t? first\n\t\t: undefined;\n}\n\nfunction buildRootProductEntry(\n\tprimaryEntry: ProductFoldEntry[\"entry\"],\n\tconsensusBase: {\n\t\treadonly folds: ReadonlyArray<EnvironmentFoldResult>;\n\t\treadonly productKey: string;\n\t},\n): DeveloperProductEntry {\n\tconst icon = productOptionalFieldConsensus({ ...consensusBase, field: \"icon\" });\n\tconst isRegionalPricingEnabled = productOptionalFieldConsensus({\n\t\t...consensusBase,\n\t\tfield: \"isRegionalPricingEnabled\",\n\t});\n\tconst price = productOptionalFieldConsensus({ ...consensusBase, field: \"price\" });\n\tconst isStorePageEnabled = productOptionalFieldConsensus({\n\t\t...consensusBase,\n\t\tfield: \"storePageEnabled\",\n\t});\n\n\treturn {\n\t\tname: primaryEntry.name,\n\t\tdescription: primaryEntry.description,\n\t\t...(icon !== undefined && { icon }),\n\t\t...(isRegionalPricingEnabled !== undefined && { isRegionalPricingEnabled }),\n\t\t...(price !== undefined && { price }),\n\t\t...(isStorePageEnabled !== undefined && { storePageEnabled: isStorePageEnabled }),\n\t};\n}\n\nfunction buildProductOverlayEntry(\n\tentry: ProductFoldEntry[\"entry\"],\n\trootEntry: DeveloperProductEntry | undefined,\n): ProductOverlayEntry {\n\treturn {\n\t\t...(entry.name !== rootEntry?.name && { name: entry.name }),\n\t\t...(entry.description !== rootEntry?.description && { description: entry.description }),\n\t\t...(!Object.is(rootEntry?.icon?.[\"en-us\"], entry.icon?.[\"en-us\"]) && { icon: entry.icon }),\n\t\t...(!Object.is(rootEntry?.price, entry.price) && { price: entry.price }),\n\t\t...(!Object.is(rootEntry?.isRegionalPricingEnabled, entry.isRegionalPricingEnabled) && {\n\t\t\tisRegionalPricingEnabled: entry.isRegionalPricingEnabled,\n\t\t}),\n\t\t...(!Object.is(rootEntry?.storePageEnabled, entry.storePageEnabled) && {\n\t\t\tstorePageEnabled: entry.storePageEnabled,\n\t\t}),\n\t};\n}\n","import type { EnvironmentEntry, UniverseEntry } from \"../schema.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\n\ntype UniverseOverlay = NonNullable<EnvironmentEntry[\"universe\"]>;\n\ninterface UniverseOverlayContext {\n\treadonly fold: EnvironmentFoldResult;\n\treadonly hasDivergentUniverseIds: boolean;\n}\n\n/**\n * Decide whether a fold map carries more than one distinct universeId,\n * which forces the migrator to omit `universeId` from the root universe\n * block and write it on every environment overlay (the schema-level XOR\n * rule rejects the same `universeId` appearing in both places).\n *\n * @param folds - Per-environment fold results.\n * @returns `true` when at least two folds carry different universeIds.\n */\nexport function hasDivergentUniverseIds(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): boolean {\n\tconst distinctIds = new Set(\n\t\t[...folds.values()].flatMap((fold) => {\n\t\t\treturn fold.universe === undefined ? [] : [fold.universe.entry.universeId];\n\t\t}),\n\t);\n\treturn distinctIds.size > 1;\n}\n\n/**\n * Project the chosen primary environment's universe entry onto bedrock's\n * root `universe` block. Strips `universeId` when universes diverge across\n * environments so the schema-level XOR rule stays satisfied; the per-env\n * overlay carries the id in that case (see {@link buildUniverseOverlay}).\n *\n * @param primaryFold - The chosen primary environment's fold; supplies\n * the universe entry whose shared fields land on root.\n * @param shouldOmitUniverseId - `true` when universes diverge across the\n * input fold map (typically the output of {@link hasDivergentUniverseIds}).\n * @returns The root `universe` block, or `undefined` when the primary has\n * no universe fold or when divergent universes leave nothing to land\n * at root.\n */\nexport function buildRootUniverse(\n\tprimaryFold: EnvironmentFoldResult,\n\tshouldOmitUniverseId: boolean,\n): undefined | UniverseEntry {\n\tconst primaryUniverse = primaryFold.universe?.entry;\n\tif (primaryUniverse === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (!shouldOmitUniverseId) {\n\t\treturn primaryUniverse;\n\t}\n\n\tconst { universeId: _omittedUniverseId, ...sharedFields } = primaryUniverse;\n\tif (Object.keys(sharedFields).length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn sharedFields;\n}\n\n/**\n * Build the per-environment overlay for `universe`. Returns the env's\n * `universeId` when universes diverge across environments (schema-level\n * XOR demands one universeId source per env); otherwise returns\n * `undefined` so the env inherits the root universe block.\n *\n * @param context - The fold to overlay onto and the divergence flag.\n * @returns The overlay, or `undefined` when no per-env overlay is needed.\n */\nexport function buildUniverseOverlay(context: UniverseOverlayContext): undefined | UniverseOverlay {\n\tconst foldUniverse = context.fold.universe;\n\tif (foldUniverse === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (context.hasDivergentUniverseIds) {\n\t\treturn { universeId: foldUniverse.entry.universeId };\n\t}\n\n\treturn undefined;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type {\n\tConfig,\n\tDeveloperProductEntry,\n\tEnvironmentEntry,\n\tGamePassEntry,\n\tPlaceEntry,\n\tUniverseEntry,\n} from \"../schema.ts\";\nimport { computeEnvironmentLabel } from \"./environment-label.ts\";\nimport { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport { collectMissingResourceWarnings } from \"./factorize-environments-warnings.ts\";\nimport { buildPlacesOverlay, buildRootPlaces } from \"./factorize-places.ts\";\nimport { buildProductsOverlay, buildRootProducts } from \"./factorize-products.ts\";\nimport {\n\tbuildRootUniverse,\n\tbuildUniverseOverlay,\n\thasDivergentUniverseIds,\n} from \"./factorize-universe.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { MigrateError, MigrationWarning } from \"./migration-report.ts\";\n\ninterface FactorizeInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly primaryEnvironment: string | undefined;\n}\n\ninterface FactorizeResult {\n\treadonly config: Config;\n\treadonly primaryEnvironment: string;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ntype PassOverlayEntry = NonNullable<EnvironmentEntry[\"passes\"]>[string];\n\ninterface ResolvedPrimary {\n\treadonly name: string;\n\treadonly fold: EnvironmentFoldResult;\n}\n\ninterface OverlayContext {\n\treadonly hasDivergentUniverseIds: boolean;\n\treadonly labels: ReadonlyMap<string, string | undefined>;\n\treadonly primary: EnvironmentFoldResult | undefined;\n\treadonly rootPlaces: Record<string, PlaceEntry> | undefined;\n\treadonly rootProducts: Record<string, DeveloperProductEntry> | undefined;\n}\n\ninterface EnvironmentEntryInputs {\n\treadonly context: OverlayContext;\n\treadonly fold: EnvironmentFoldResult;\n\treadonly label: string | undefined;\n}\n\ninterface BuildConfigInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly primaryFold: EnvironmentFoldResult;\n\treadonly primaryName: string;\n}\n\ninterface LooseConfigForBuild {\n\tenvironments: Record<string, EnvironmentEntry>;\n\tpasses?: Record<string, GamePassEntry>;\n\tplaces?: Record<string, PlaceEntry>;\n\tproducts?: Record<string, DeveloperProductEntry>;\n\tuniverse?: UniverseEntry;\n}\n\n/**\n * Project per-environment fold results into a single bedrock `Config` by\n * factoring the chosen primary environment's resolved values up to the root\n * and recording per-environment overlays for fields that diverge from the\n * primary. Pure: no I/O.\n *\n * Primary selection: a single-environment input auto-picks; any other shape\n * requires `inputs.primaryEnvironment` and returns\n * `Err({ kind: \"primaryEnvironmentRequired\" })` when omitted, or\n * `Err({ kind: \"primaryEnvironmentNotFound\" })` when the supplied name is\n * not in the fold map.\n *\n * @param inputs - Per-environment folds and the optional primary hint.\n * @returns `Ok` with the factorized config on success, or `Err` with a\n * discriminated `MigrateError` on primary-selection failure.\n */\nexport function factorizeEnvironments(\n\tinputs: FactorizeInputs,\n): Result<FactorizeResult, MigrateError> {\n\tconst primaryResult = pickPrimary(inputs.folds, inputs.primaryEnvironment);\n\tif (!primaryResult.success) {\n\t\treturn primaryResult;\n\t}\n\n\tconst primary = primaryResult.data;\n\treturn {\n\t\tdata: {\n\t\t\tconfig: buildConfig({\n\t\t\t\tfolds: inputs.folds,\n\t\t\t\tprimaryFold: primary.fold,\n\t\t\t\tprimaryName: primary.name,\n\t\t\t}),\n\t\t\tprimaryEnvironment: primary.name,\n\t\t\twarnings: collectMissingResourceWarnings({ folds: inputs.folds, primary }),\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction pickPrimary(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tprimary: string | undefined,\n): Result<ResolvedPrimary, MigrateError> {\n\tconst available = [...folds.keys()];\n\tconst requested = primary ?? (available.length === 1 ? available[0] : undefined);\n\tif (requested === undefined) {\n\t\treturn { err: { available, kind: \"primaryEnvironmentRequired\" }, success: false };\n\t}\n\n\tconst fold = folds.get(requested);\n\tif (fold === undefined) {\n\t\treturn {\n\t\t\terr: { available, kind: \"primaryEnvironmentNotFound\", primary: requested },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: { name: requested, fold }, success: true };\n}\n\nfunction buildRootPasses(\n\tprimaryFold: EnvironmentFoldResult,\n): Record<string, GamePassEntry> | undefined {\n\tif (primaryFold.passes.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(primaryFold.passes.map(({ key, entry }) => [key, entry]));\n}\n\nfunction buildPassOverlayEntry(\n\tentry: GamePassEntry,\n\tprimary: GamePassEntry,\n): PassOverlayEntry | undefined {\n\tconst overlay: PassOverlayEntry = {};\n\tif (!Object.is(primary.name, entry.name)) {\n\t\toverlay.name = entry.name;\n\t}\n\n\tif (!Object.is(primary.description, entry.description)) {\n\t\toverlay.description = entry.description;\n\t}\n\n\tif (!Object.is(primary.icon[\"en-us\"], entry.icon[\"en-us\"])) {\n\t\toverlay.icon = entry.icon;\n\t}\n\n\tif (!Object.is(primary.price, entry.price)) {\n\t\toverlay.price = entry.price;\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction buildPassesOverlay(\n\tfold: EnvironmentFoldResult,\n\tprimary: EnvironmentFoldResult | undefined,\n): Record<string, PassOverlayEntry> | undefined {\n\tconst primaryByKey = new Map<string, GamePassEntry>(\n\t\tprimary?.passes.map(({ key, entry }) => [key, entry]),\n\t);\n\tconst overlay: Record<string, PassOverlayEntry> = {};\n\tfor (const { key, entry } of fold.passes) {\n\t\tconst primaryEntry = primaryByKey.get(key);\n\t\tconst passOverlay =\n\t\t\tprimaryEntry === undefined ? { ...entry } : buildPassOverlayEntry(entry, primaryEntry);\n\t\tif (passOverlay !== undefined) {\n\t\t\toverlay[key] = passOverlay;\n\t\t}\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction buildEnvironmentEntry(inputs: EnvironmentEntryInputs): EnvironmentEntry {\n\tconst { context, fold, label } = inputs;\n\tconst passes = buildPassesOverlay(fold, context.primary);\n\tconst places = buildPlacesOverlay({ fold, label, rootPlaces: context.rootPlaces });\n\tconst products = buildProductsOverlay(fold, context.rootProducts);\n\tconst universe = buildUniverseOverlay({\n\t\tfold,\n\t\thasDivergentUniverseIds: context.hasDivergentUniverseIds,\n\t});\n\treturn {\n\t\t...(label !== undefined && { label }),\n\t\t...(passes !== undefined && { passes }),\n\t\t...(places !== undefined && { places }),\n\t\t...(products !== undefined && { products }),\n\t\t...(universe !== undefined && { universe }),\n\t};\n}\n\nfunction buildEnvironmentEntries(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tcontext: OverlayContext,\n): Record<string, EnvironmentEntry> {\n\treturn Object.fromEntries(\n\t\t[...folds].map(([name, fold]) => [\n\t\t\tname,\n\t\t\tbuildEnvironmentEntry({ context, fold, label: context.labels.get(name) }),\n\t\t]),\n\t);\n}\n\nfunction buildEnvironmentLabels(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): ReadonlyMap<string, string | undefined> {\n\treturn new Map([...folds].map(([name, fold]) => [name, computeEnvironmentLabel(fold)]));\n}\n\nfunction stripDisplayNamePrefix(\n\tuniverse: undefined | UniverseEntry,\n\tlabel: string | undefined,\n): undefined | UniverseEntry {\n\tif (universe === undefined || label === undefined || universe.displayName === undefined) {\n\t\treturn universe;\n\t}\n\n\treturn { ...universe, displayName: extractDisplayNamePrefix(universe.displayName).body };\n}\n\nfunction buildConfig(inputs: BuildConfigInputs): Config {\n\tconst { folds, primaryFold, primaryName } = inputs;\n\tconst labels = buildEnvironmentLabels(folds);\n\tconst places = buildRootPlaces({ folds, labels, primaryFold });\n\tconst products = buildRootProducts(folds, primaryFold);\n\tconst shouldOmitRootUniverseId = hasDivergentUniverseIds(folds);\n\tconst environments = buildEnvironmentEntries(folds, {\n\t\thasDivergentUniverseIds: shouldOmitRootUniverseId,\n\t\tlabels,\n\t\tprimary: primaryFold,\n\t\trootPlaces: places,\n\t\trootProducts: products,\n\t});\n\tconst passes = buildRootPasses(primaryFold);\n\tconst universe = stripDisplayNamePrefix(\n\t\tbuildRootUniverse(primaryFold, shouldOmitRootUniverseId),\n\t\tlabels.get(primaryName),\n\t);\n\tconst config: LooseConfigForBuild = {\n\t\tenvironments,\n\t\t...(passes !== undefined && { passes }),\n\t\t...(places !== undefined && { places }),\n\t\t...(products !== undefined && { products }),\n\t\t...(universe !== undefined && { universe }),\n\t};\n\t// Precondition for the cast: `hasDivergentUniverseIds` decides\n\t// `shouldOmitRootUniverseId` and `buildRootUniverse` and\n\t// `buildUniverseOverlay` honour it, so the constructed config always\n\t// lands in one arm of the discriminated `Config` union (root has\n\t// `universeId` and no env carries one, or every env carries one and\n\t// root omits it).\n\treturn config as unknown as Config;\n}\n","import { asResourceKey, asRobloxAssetId, asSha256Hex, isSha256Hex } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport type { GamePassOutputs } from \"../resources.ts\";\nimport type { GamePassEntry } from \"../schema.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PASS_KIND = \"pass\";\n\n/**\n * Folded representation of one Mantle `pass_<k>` resource.\n *\n * `entry` carries the bedrock `Config.passes[<k>]` shape (omitting\n * `iconFileHashes` because the hashes are recomputed from disk by the shell\n * before they land on state). `outputs` carries the Roblox-assigned\n * identifiers for the pass and its icon. `mantleIconFileHashes` preserves\n * the hashes recorded by Mantle so the shell can fall back to them when the\n * icon file is missing on disk. `mantlePath` roots warnings at the\n * resource so the report is searchable.\n */\nexport interface PassFoldEntry {\n\t/** User-supplied Mantle key, branded as a bedrock `ResourceKey`. */\n\treadonly key: ResourceKey;\n\t/** Bedrock `Config.passes[<k>]` block populated from the pass resource. */\n\treadonly entry: GamePassEntry;\n\t/** Locale-keyed Mantle-recorded icon hashes; retained as a fallback for hash recomputation. */\n\treadonly mantleIconFileHashes: Record<\"en-us\", Sha256Hex>;\n\t/** Resource-rooted Mantle path (`pass_<k>`) used to anchor warnings. */\n\treadonly mantlePath: string;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: GamePassOutputs;\n}\n\n/**\n * Output of folding the `pass_<k>` Mantle resources of one environment\n * into bedrock-shaped game-pass entries.\n *\n * `passes` carries one record per well-formed `pass_<k>` resource;\n * malformed resources are dropped silently in this slice (warning-emitting\n * variants land alongside their consuming rules). `warnings` accumulates\n * per-rule diagnostics; the slice emits an empty list because no\n * interpretive rules have been wired yet.\n */\ninterface PassesFoldResult {\n\t/** One folded entry per well-formed Mantle `pass_<k>` resource. */\n\treadonly passes: ReadonlyArray<PassFoldEntry>;\n\t/** Per-rule diagnostics; empty in this slice, populated as rules land. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface PassInputs {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly iconFileHash: Sha256Hex;\n\treadonly iconFilePath: string;\n\treadonly price: number | undefined;\n}\n\ninterface PassOutputsRaw {\n\treadonly assetId: string;\n\treadonly iconAssetId: string;\n}\n\n/**\n * Fold the `pass_<k>` Mantle resources of one environment into a list\n * of bedrock game-pass entries plus matching outputs.\n *\n * Each well-formed `pass_<k>.inputs.{name, description, iconFilePath,\n * price}` becomes the corresponding `Config.passes[<k>]` entry; the\n * matching `pass_<k>.outputs.{assetId, iconAssetId}` becomes the\n * `BedrockState` resource's `outputs`. The Mantle-recorded\n * `iconFileHash` is preserved on the result so the shell can fall back\n * to it when the icon file is missing on disk.\n *\n * Resources whose payload is malformed (non-object, missing required\n * string field, non-`Sha256Hex` hash, missing output IDs) are dropped\n * silently in this slice; warning-emitting variants land alongside the\n * specific interpretive rules that need them.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded pass entries plus an aggregated warnings list.\n */\nexport function foldPasses(resources: ReadonlyArray<MantleResource>): PassesFoldResult {\n\tconst passes = resources.flatMap((resource): ReadonlyArray<PassFoldEntry> => {\n\t\tif (resource.kind !== PASS_KIND) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst folded = foldOnePass(resource);\n\t\treturn folded === undefined ? [] : [folded];\n\t});\n\n\treturn { passes, warnings: [] };\n}\n\nfunction buildEntry(inputs: PassInputs): GamePassEntry {\n\tconst base = {\n\t\tname: inputs.name,\n\t\tdescription: inputs.description,\n\t\ticon: { \"en-us\": inputs.iconFilePath },\n\t};\n\n\treturn inputs.price === undefined ? base : { ...base, price: inputs.price };\n}\n\nfunction isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPrice(raw: Record<string, unknown>): number | undefined {\n\tconst candidate = raw[\"price\"];\n\treturn typeof candidate === \"number\" ? candidate : undefined;\n}\n\nfunction readPassInputs(raw: unknown): PassInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst name = readString(raw[\"name\"]);\n\tconst description = readString(raw[\"description\"]);\n\tconst iconFilePath = readString(raw[\"iconFilePath\"]);\n\tconst iconFileHashRaw = readString(raw[\"iconFileHash\"]);\n\tif (\n\t\tname === undefined ||\n\t\tdescription === undefined ||\n\t\ticonFilePath === undefined ||\n\t\ticonFileHashRaw === undefined ||\n\t\t!isSha256Hex(iconFileHashRaw)\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\ticonFileHash: asSha256Hex(iconFileHashRaw),\n\t\ticonFilePath,\n\t\tprice: readPrice(raw),\n\t};\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readPassOutputs(raw: unknown): PassOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tconst iconAssetId = coerceRobloxId(raw[\"iconAssetId\"]);\n\tif (assetId === undefined || iconAssetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId, iconAssetId };\n}\n\nfunction foldOnePass(resource: MantleResource): PassFoldEntry | undefined {\n\tconst inputs = readPassInputs(resource.inputs);\n\tif (inputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readPassOutputs(resource.outputs);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tkey: asResourceKey(resource.key),\n\t\tentry: buildEntry(inputs),\n\t\tmantleIconFileHashes: { \"en-us\": inputs.iconFileHash },\n\t\tmantlePath: `${PASS_KIND}_${resource.key}`,\n\t\toutputs: {\n\t\t\tassetId: asRobloxAssetId(outputs.assetId),\n\t\t\ticonAssetIds: { \"en-us\": asRobloxAssetId(outputs.iconAssetId) },\n\t\t},\n\t};\n}\n","import type { UniverseOutputs } from \"../resources.ts\";\nimport type { UniverseEntry } from \"../schema.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\n\n/**\n * Output of one fold rule contributing to the eventual `UniverseEntry`.\n * The orchestrator (`foldUniverse`) composes fragments by spreading\n * `entryFragment` into the final entry, spreading `outputsFragment` into\n * the matching `BedrockState` resource's `outputs`, and concatenating\n * `warnings`. Most rules contribute only to the entry; rules that surface\n * Roblox-assigned identifiers populate `outputsFragment`. The\n * `mergeFragment` helper, used inside individual fold modules to combine\n * sub-fragments, only merges `entryFragment` and `warnings` because no\n * rule that uses it currently produces outputs.\n */\nexport interface FoldFragment {\n\t/**\n\t * Subset of universe fields this rule populated (empty when the rule\n\t * did not fire). `universeId` is excluded because the orchestrator\n\t * seeds it from the experience resource's outputs; no fold rule\n\t * contributes to it.\n\t */\n\treadonly entryFragment: UniverseEntryFragment;\n\t/** Subset of universe outputs this rule populated; absent when the rule contributes no Roblox-assigned identifiers. */\n\treadonly outputsFragment?: Partial<UniverseOutputs>;\n\t/** Diagnostics this rule emitted. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Subset of {@link UniverseEntry} that any individual fold rule may\n * populate. `universeId` is excluded because the orchestrator seeds it\n * from the experience resource's outputs; no fold rule contributes to it.\n */\ntype UniverseEntryFragment = {\n\treadonly [Key in Exclude<keyof UniverseEntry, \"universeId\">]?: UniverseEntry[Key];\n};\n\n/** Sentinel value for fold rules that produced neither output nor warnings. */\nexport const EMPTY_FRAGMENT: FoldFragment = { entryFragment: {}, warnings: [] };\n\n/**\n * One row in a static blocked-field table: the Mantle field name and the\n * upstream limitation reason that the migrator surfaces verbatim in the\n * warning's `reason` slot. Used by folds that emit one `blocked`\n * `MigrationWarning` per non-`undefined` legacy-only field.\n */\nexport interface BlockedFieldRule {\n\t/** Mantle input key to test for non-`undefined`. */\n\treadonly field: string;\n\t/** Human-readable reason naming the upstream limitation. */\n\treadonly reason: string;\n}\n\n/**\n * Required fields for an `interpretive` warning, gathered as a single\n * argument so this builder stays under the project's max-params cap.\n */\ninterface InterpretiveWarningSpec {\n\t/** Path the migrator wrote in the bedrock config. */\n\treadonly bedrockPath: string;\n\t/** Resource-rooted Mantle path the rule consumed. */\n\treadonly mantlePath: string;\n\t/** Stable rule identifier audited in the migration report. */\n\treadonly rule: string;\n}\n\n/**\n * Narrow a value to a plain object payload (rejects arrays, null, primitives).\n *\n * @param value - Value to test.\n * @returns `true` when `value` is a plain object record.\n */\nexport function isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\n/**\n * Coerce a Mantle-recorded Roblox identifier to a string. YAML can carry\n * IDs as either a quoted string or a bare integer; both are accepted.\n * Anything else (null, float, object) returns `undefined`.\n *\n * @param value - Raw value pulled from a Mantle resource's outputs.\n * @returns The stringified ID, or `undefined` when the value is not a valid wire shape.\n */\nexport function coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Combine two fragments, with the right-hand side overriding overlapping\n * `entryFragment` keys and its `warnings` appended after the left's.\n * Does not merge `outputsFragment`; outputs composition is handled by\n * `foldUniverse` across the whole fragment list, not by this helper.\n *\n * @param left - Earlier fragment.\n * @param right - Later fragment whose entry-fragment keys win on conflict.\n * @returns The merged fragment.\n */\nexport function mergeFragment(left: FoldFragment, right: FoldFragment): FoldFragment {\n\treturn {\n\t\tentryFragment: { ...left.entryFragment, ...right.entryFragment },\n\t\twarnings: [...left.warnings, ...right.warnings],\n\t};\n}\n\n/**\n * Build an `interpretive` `MigrationWarning`. Used by every fold rule that\n * applies a documented mapping; the report consumer narrows on `kind`.\n *\n * @param spec - The bedrock path, mantle path, and rule identifier.\n * @returns A `MigrationWarning` with `kind: \"interpretive\"`.\n */\nexport function interpretiveWarning(spec: InterpretiveWarningSpec): MigrationWarning {\n\treturn {\n\t\tbedrockPath: spec.bedrockPath,\n\t\tkind: \"interpretive\",\n\t\tmantlePath: spec.mantlePath,\n\t\trule: spec.rule,\n\t};\n}\n\n/**\n * Build a `blocked` `MigrationWarning`. Used when no Open Cloud writable\n * endpoint exists for the field the migrator encountered.\n *\n * @param mantlePath - Resource-rooted Mantle path of the blocked field.\n * @param reason - Human-readable reason describing what is unsupported.\n * @returns A `MigrationWarning` with `kind: \"blocked\"`.\n */\nexport function blockedWarning(mantlePath: string, reason: string): MigrationWarning {\n\treturn { kind: \"blocked\", mantlePath, reason };\n}\n\n/**\n * Build an `ambiguous` `MigrationWarning`. Used when a value is mappable\n * but unsafe to act on without user input; the hint guides the next step.\n *\n * @param mantlePath - Resource-rooted Mantle path of the ambiguous field.\n * @param hint - Human-readable suggestion for resolving the ambiguity.\n * @returns A `MigrationWarning` with `kind: \"ambiguous\"`.\n */\nexport function ambiguousWarning(mantlePath: string, hint: string): MigrationWarning {\n\treturn { hint, kind: \"ambiguous\", mantlePath };\n}\n","import { type BlockedFieldRule, blockedWarning, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\n/**\n * `placeConfiguration_<k>` fields with no Open Cloud writable endpoint.\n * `name`, `description`, and `maxPlayerCount` are intentionally omitted:\n * `foldDisplayName` and `foldPlaces` fold them into the bedrock config.\n */\nconst BLOCKED_FIELDS: ReadonlyArray<BlockedFieldRule> = [\n\t{\n\t\tfield: \"allowCopying\",\n\t\treason: \"placeConfiguration.allowCopying has no Open Cloud equivalent\",\n\t},\n\t{\n\t\tfield: \"socialSlotType\",\n\t\treason: \"placeConfiguration social-slot config has no Open Cloud equivalent\",\n\t},\n\t{\n\t\tfield: \"customSocialSlotsCount\",\n\t\treason: \"placeConfiguration social-slot config has no Open Cloud equivalent\",\n\t},\n];\n\n/**\n * Emit one `blocked` `MigrationWarning` per non-`undefined` legacy-only\n * field on every `placeConfiguration_<k>` resource. The Mantle null\n * sentinel (`~`) is normalized to `undefined` by `parseState`, so absent\n * and null fields both skip.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns One warning per blocked field present, ordered by resource\n * list then by the static field table.\n */\nexport function foldBlockedPlaceFields(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\treturn resources.flatMap((resource) => {\n\t\tif (resource.kind !== PLACE_CONFIGURATION_KIND || !isObjectPayload(resource.inputs)) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst { key, inputs } = resource;\n\t\treturn BLOCKED_FIELDS.flatMap((rule) => {\n\t\t\tif (inputs[rule.field] === undefined) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\tblockedWarning(`${PLACE_CONFIGURATION_KIND}_${key}.${rule.field}`, rule.reason),\n\t\t\t];\n\t\t});\n\t});\n}\n","import { isSha256Hex, type Sha256Hex } from \"../../types/ids.ts\";\nimport type { PlaceOutputs } from \"../resources.ts\";\nimport type { PlaceEntry } from \"../schema.ts\";\nimport { foldBlockedPlaceFields } from \"./fold-blocked-place-fields.ts\";\nimport { interpretiveWarning, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_KIND = \"place\";\nconst PLACE_FILE_KIND = \"placeFile\";\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\n/**\n * Folded place data for one Mantle resource key.\n *\n * `entry` populates the bedrock root `Config.places.<key>` block (currently\n * just `filePath`). `placeId` is the Roblox-assigned place ID Mantle stored\n * under `place_<k>.outputs.assetId`; the migrator threads it onto the\n * matching per-environment overlay (`environments[env].places.<key>`).\n * `fileHash` is the SHA-256 digest Mantle recorded under\n * `placeFile_<k>.inputs.fileHash`; the shell consumes it as the fallback\n * value when the migrator cannot recompute the hash from disk. `outputs`\n * carries the auto-incrementing publish version number Mantle stored under\n * `placeFile_<k>.outputs.version`.\n */\nexport interface PlaceFoldEntry {\n\t/** Bedrock root `Config.places.<key>` body (currently `filePath` only). */\n\treadonly entry: PlaceEntry;\n\t/** Mantle-recorded SHA-256 hex digest of the place file (fallback for hash recomputation). */\n\treadonly fileHash: Sha256Hex;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: PlaceOutputs;\n\t/** Roblox-assigned place ID copied from `place_<key>.outputs.assetId`. */\n\treadonly placeId: string;\n}\n\n/**\n * Output of folding the place-related Mantle resources of one environment\n * into bedrock's `places` shape. Each Mantle place key produces one entry\n * in `entries`; orphans (a `place_<k>` without a `placeFile_<k>`, or vice\n * versa) emit one `ambiguous` warning each instead of being projected into\n * `entries`.\n */\ninterface PlaceFoldResult {\n\t/** Folded entries keyed by Mantle's place key (the suffix after the first `_`). */\n\treadonly entries: ReadonlyMap<string, PlaceFoldEntry>;\n\t/** Per-rule diagnostics: orphan resources surface as `ambiguous` warnings. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface PlaceOutputsRaw {\n\treadonly assetId: string;\n}\n\ninterface PlaceFileInputsRaw {\n\treadonly fileHash: Sha256Hex;\n\treadonly filePath: string;\n}\n\ninterface PlaceFileOutputsRaw {\n\treadonly version: number;\n}\n\ninterface PlaceConfigFoldResult {\n\treadonly entry: PlaceFoldEntry;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ApplyPlaceConfigFieldsInputs {\n\treadonly key: string;\n\treadonly configResource: MantleResource | undefined;\n\treadonly folded: PlaceFoldEntry;\n\treadonly isStart: boolean;\n}\n\ninterface PlaceConfigFragmentRule {\n\treadonly bedrockField: string;\n\treadonly mantleField: string;\n\treadonly rule: string;\n}\n\ninterface PlaceConfigFragment {\n\treadonly entry: Partial<PlaceEntry>;\n\treadonly warnings: ReadonlyArray<PlaceConfigFragmentRule>;\n}\n\ninterface PlaceConfigRule {\n\treadonly bedrockField: keyof PlaceEntry;\n\treadonly mantleField: string;\n\treadonly read: (value: unknown) => number | string | undefined;\n\treadonly rule: string;\n}\n\ninterface PlaceBuckets {\n\treadonly placeConfigurations: Map<string, MantleResource>;\n\treadonly placeFiles: Map<string, MantleResource>;\n\treadonly places: Map<string, MantleResource>;\n}\n\n/**\n * Fold the place-related Mantle resources of one environment into a map of\n * `PlaceFoldEntry` plus accompanying warnings. Pairs each `place_<k>`\n * with the matching `placeFile_<k>` by key; an unmatched resource on\n * either side emits an `ambiguous` warning carrying the orphan's\n * `mantlePath` and a hint instructing the user to verify their Mantle\n * state.\n *\n * Matched pairs whose payloads fail shape validation (missing or\n * non-numeric `assetId`, missing `filePath`, malformed `fileHash`, etc.)\n * are dropped silently rather than surfacing as warnings, mirroring the\n * malformed-input handling in `foldUniverse`. A `malformed` warning kind\n * spanning the per-kind folds is left to a follow-up slice.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded entries plus orphan warnings.\n */\nexport function foldPlaces(resources: ReadonlyArray<MantleResource>): PlaceFoldResult {\n\tconst buckets = bucketByKind(resources);\n\tconst matched = collectMatchedFolds(buckets);\n\tconst orphans = collectOrphanWarnings(buckets);\n\treturn {\n\t\tentries: matched.entries,\n\t\twarnings: [...matched.warnings, ...orphans, ...foldBlockedPlaceFields(resources)],\n\t};\n}\n\nfunction collectMatchedFolds(buckets: PlaceBuckets): PlaceFoldResult {\n\tconst { placeConfigurations, placeFiles, places } = buckets;\n\tconst entries = new Map<string, PlaceFoldEntry>();\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const [key, placeResource] of places) {\n\t\tconst fileResource = placeFiles.get(key);\n\t\tif (fileResource === undefined) {\n\t\t\twarnings.push(orphanWarning(PLACE_KIND, key));\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst folded = mergeMatchedPair(placeResource, fileResource);\n\t\tif (folded === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst configFold = applyPlaceConfigFields({\n\t\t\tkey,\n\t\t\tconfigResource: placeConfigurations.get(key),\n\t\t\tfolded,\n\t\t\tisStart: isStartPlace(placeResource),\n\t\t});\n\t\tentries.set(key, configFold.entry);\n\t\twarnings.push(...configFold.warnings);\n\t}\n\n\treturn { entries, warnings };\n}\n\nfunction collectOrphanWarnings(buckets: PlaceBuckets): ReadonlyArray<MigrationWarning> {\n\tconst { placeConfigurations, placeFiles, places } = buckets;\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const [key] of placeFiles) {\n\t\tif (!places.has(key)) {\n\t\t\twarnings.push(orphanWarning(PLACE_FILE_KIND, key));\n\t\t}\n\t}\n\n\tfor (const [key] of placeConfigurations) {\n\t\tif (!places.has(key) || !placeFiles.has(key)) {\n\t\t\twarnings.push(placeConfigOrphanWarning(key));\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\nfunction bucketByKind(resources: ReadonlyArray<MantleResource>): PlaceBuckets {\n\tconst places = new Map<string, MantleResource>();\n\tconst placeFiles = new Map<string, MantleResource>();\n\tconst placeConfigurations = new Map<string, MantleResource>();\n\tfor (const resource of resources) {\n\t\tswitch (resource.kind) {\n\t\t\tcase PLACE_CONFIGURATION_KIND: {\n\t\t\t\tplaceConfigurations.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase PLACE_FILE_KIND: {\n\t\t\t\tplaceFiles.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase PLACE_KIND: {\n\t\t\t\tplaces.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// No default\n\t\t}\n\t}\n\n\treturn { placeConfigurations, placeFiles, places };\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPositiveInteger(value: unknown): number | undefined {\n\tif (typeof value !== \"number\" || !Number.isInteger(value) || value <= 0) {\n\t\treturn undefined;\n\t}\n\n\treturn value;\n}\n\nconst ALWAYS_RULES: ReadonlyArray<PlaceConfigRule> = [\n\t{\n\t\tbedrockField: \"description\",\n\t\tmantleField: \"description\",\n\t\tread: readString,\n\t\trule: \"place-description\",\n\t},\n\t{\n\t\tbedrockField: \"serverSize\",\n\t\tmantleField: \"maxPlayerCount\",\n\t\tread: readPositiveInteger,\n\t\trule: \"max-player-count-to-server-size\",\n\t},\n];\n\nconst NON_START_RULES: ReadonlyArray<PlaceConfigRule> = [\n\t{\n\t\tbedrockField: \"displayName\",\n\t\tmantleField: \"name\",\n\t\tread: readString,\n\t\trule: \"place-name-to-display-name\",\n\t},\n];\n\nfunction placeConfigRules(isStart: boolean): ReadonlyArray<PlaceConfigRule> {\n\treturn isStart ? ALWAYS_RULES : [...ALWAYS_RULES, ...NON_START_RULES];\n}\n\nfunction readPlaceConfigFragment(\n\tinputs: Record<string, unknown>,\n\trules: ReadonlyArray<PlaceConfigRule>,\n): PlaceConfigFragment {\n\treturn rules.reduce<{\n\t\tentry: Partial<PlaceEntry>;\n\t\twarnings: Array<PlaceConfigFragmentRule>;\n\t}>(\n\t\t(accumulator, rule) => {\n\t\t\tconst value = rule.read(inputs[rule.mantleField]);\n\t\t\tif (value === undefined) {\n\t\t\t\treturn accumulator;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tentry: { ...accumulator.entry, [rule.bedrockField]: value },\n\t\t\t\twarnings: [\n\t\t\t\t\t...accumulator.warnings,\n\t\t\t\t\t{\n\t\t\t\t\t\tbedrockField: rule.bedrockField,\n\t\t\t\t\t\tmantleField: rule.mantleField,\n\t\t\t\t\t\trule: rule.rule,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t},\n\t\t{ entry: {}, warnings: [] },\n\t);\n}\n\nfunction applyPlaceConfigFields(inputs: ApplyPlaceConfigFieldsInputs): PlaceConfigFoldResult {\n\tconst { key, configResource, folded, isStart } = inputs;\n\tif (configResource === undefined || !isObjectPayload(configResource.inputs)) {\n\t\treturn { entry: folded, warnings: [] };\n\t}\n\n\tconst fragment = readPlaceConfigFragment(configResource.inputs, placeConfigRules(isStart));\n\tconst entry: PlaceFoldEntry = {\n\t\t...folded,\n\t\tentry: { ...folded.entry, ...fragment.entry },\n\t};\n\n\treturn {\n\t\tentry,\n\t\twarnings: fragment.warnings.map((rule) => {\n\t\t\treturn interpretiveWarning({\n\t\t\t\tbedrockPath: `places.${key}.${rule.bedrockField}`,\n\t\t\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${key}.${rule.mantleField}`,\n\t\t\t\trule: rule.rule,\n\t\t\t});\n\t\t}),\n\t};\n}\n\nfunction isStartPlace(resource: MantleResource): boolean {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\treturn resource.inputs[\"isStart\"] === true;\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readPlaceOutputs(resource: MantleResource): PlaceOutputsRaw | undefined {\n\tconst { outputs } = resource;\n\tif (!isObjectPayload(outputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(outputs[\"assetId\"]);\n\tif (assetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId };\n}\n\nfunction readPlaceFileInputs(resource: MantleResource): PlaceFileInputsRaw | undefined {\n\tconst { inputs } = resource;\n\tif (!isObjectPayload(inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { fileHash, filePath } = inputs;\n\tif (typeof filePath !== \"string\" || typeof fileHash !== \"string\" || !isSha256Hex(fileHash)) {\n\t\treturn undefined;\n\t}\n\n\treturn { fileHash, filePath };\n}\n\nfunction readPlaceFileOutputs(resource: MantleResource): PlaceFileOutputsRaw | undefined {\n\tconst { outputs } = resource;\n\tif (!isObjectPayload(outputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { version } = outputs;\n\tif (typeof version !== \"number\") {\n\t\treturn undefined;\n\t}\n\n\treturn { version };\n}\n\nfunction mergeMatchedPair(\n\tplaceResource: MantleResource,\n\tfileResource: MantleResource,\n): PlaceFoldEntry | undefined {\n\tconst placeOutputs = readPlaceOutputs(placeResource);\n\tconst fileInputs = readPlaceFileInputs(fileResource);\n\tconst fileOutputs = readPlaceFileOutputs(fileResource);\n\tif (placeOutputs === undefined || fileInputs === undefined || fileOutputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tentry: { filePath: fileInputs.filePath },\n\t\tfileHash: fileInputs.fileHash,\n\t\toutputs: { versionNumber: fileOutputs.version },\n\t\tplaceId: placeOutputs.assetId,\n\t};\n}\n\nfunction orphanWarning(kind: string, key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each place_<k> resource must be paired with a matching placeFile_<k>, and vice versa.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${kind}_${key}`,\n\t};\n}\n\nfunction placeConfigOrphanWarning(key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each placeConfiguration_<k> requires a matching place_<k>+placeFile_<k> pair to fold its data into the bedrock config.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${key}`,\n\t};\n}\n","import { asResourceKey, asRobloxAssetId, asSha256Hex, isSha256Hex } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport type { DeveloperProductOutputs } from \"../resources.ts\";\nimport type { DeveloperProductEntry } from \"../schema.ts\";\nimport { coerceRobloxId, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PRODUCT_KIND = \"product\";\nconst PRODUCT_ICON_KIND = \"productIcon\";\n\n/**\n * Folded representation of one Mantle `product_<k>` resource.\n *\n * `entry` carries the bedrock `Config.products[<k>]` shape; when a matching\n * `productIcon_<k>` resource is present it also carries the locale-keyed icon\n * path. `outputs` carries the Roblox-assigned identifiers for the product and\n * its optional icon. `mantleIconFileHashes` preserves the hash recorded by\n * Mantle so the shell can fall back to it when the icon file is missing on\n * disk; absent when no icon partner is found. `mantlePath` roots warnings at\n * the resource so the report is searchable.\n */\nexport interface ProductFoldEntry {\n\t/** User-supplied Mantle key, branded as a bedrock `ResourceKey`. */\n\treadonly key: ResourceKey;\n\t/** Bedrock `Config.products[<k>]` block populated from the product resource. */\n\treadonly entry: DeveloperProductEntry;\n\t/** Locale-keyed Mantle-recorded icon hashes; absent when no productIcon partner is paired. */\n\treadonly mantleIconFileHashes?: Record<\"en-us\", Sha256Hex>;\n\t/** Resource-rooted Mantle path (`product_<k>`) used to anchor warnings. */\n\treadonly mantlePath: string;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: DeveloperProductOutputs;\n}\n\n/**\n * Output of folding the `product_<k>` and `productIcon_<k>` Mantle resources\n * of one environment into bedrock-shaped developer-product entries.\n *\n * `products` carries one record per well-formed `product_<k>` resource;\n * malformed resources are dropped silently. `warnings` carries one\n * `ambiguous` warning per orphan `productIcon_<k>` (no matching product).\n */\ninterface ProductsFoldResult {\n\t/** One folded entry per well-formed Mantle `product_<k>` resource. */\n\treadonly products: ReadonlyArray<ProductFoldEntry>;\n\t/** Per-rule diagnostics: orphan productIcon resources surface as `ambiguous` warnings. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ProductInputs {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly price: number | undefined;\n}\n\ninterface ProductOutputsRaw {\n\treadonly productId: string;\n}\n\ninterface ProductIconInputs {\n\treadonly fileHash: Sha256Hex;\n\treadonly filePath: string;\n}\n\ninterface ProductIconOutputsRaw {\n\treadonly assetId: string;\n}\n\ninterface ProductIconParts {\n\treadonly icon: Record<\"en-us\", string>;\n\treadonly iconImageAssetId: string;\n\treadonly mantleIconFileHashes: Record<\"en-us\", Sha256Hex>;\n}\n\n/**\n * Fold the `product_<k>` (and matching `productIcon_<k>`) Mantle resources\n * of one environment into a list of bedrock developer-product entries plus\n * matching outputs. Pairs each `product_<k>` with the optional\n * `productIcon_<k>` resource sharing the same key; when the icon partner is\n * present and well-formed, the locale-keyed icon path lands on the entry\n * and the Roblox-assigned `iconImageAssetId` lands on the outputs.\n *\n * Resources whose payload is malformed (non-object, missing required string\n * field, missing `productId`, malformed `fileHash`) are dropped silently.\n * Orphan `productIcon_<k>` resources (no matching product) emit one\n * `ambiguous` warning each.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded product entries plus per-rule diagnostics.\n */\nexport function foldProducts(resources: ReadonlyArray<MantleResource>): ProductsFoldResult {\n\tconst { productIcons, products } = bucketByKind(resources);\n\tconst folded = products.flatMap((resource): ReadonlyArray<ProductFoldEntry> => {\n\t\tconst iconResource = productIcons.get(resource.key);\n\t\tconst entry = foldOneProduct(resource, iconResource);\n\t\treturn entry === undefined ? [] : [entry];\n\t});\n\n\tconst productKeys = new Set(products.map((resource) => resource.key));\n\tconst warnings = [...productIcons.keys()]\n\t\t.filter((key) => !productKeys.has(key))\n\t\t.map((key) => orphanIconWarning(key));\n\n\treturn { products: folded, warnings };\n}\n\nfunction bucketByKind(resources: ReadonlyArray<MantleResource>): {\n\treadonly productIcons: Map<string, MantleResource>;\n\treadonly products: ReadonlyArray<MantleResource>;\n} {\n\tconst products: Array<MantleResource> = [];\n\tconst productIcons = new Map<string, MantleResource>();\n\tfor (const resource of resources) {\n\t\tif (resource.kind === PRODUCT_KIND) {\n\t\t\tproducts.push(resource);\n\t\t} else if (resource.kind === PRODUCT_ICON_KIND) {\n\t\t\tproductIcons.set(resource.key, resource);\n\t\t}\n\t}\n\n\treturn { productIcons, products };\n}\n\nfunction buildEntry(\n\tinputs: ProductInputs,\n\ticon: Record<\"en-us\", string> | undefined,\n): DeveloperProductEntry {\n\tconst base: DeveloperProductEntry = {\n\t\tname: inputs.name,\n\t\tdescription: inputs.description,\n\t};\n\tconst withPrice = inputs.price === undefined ? base : { ...base, price: inputs.price };\n\treturn icon === undefined ? withPrice : { ...withPrice, icon };\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPrice(raw: Record<string, unknown>): number | undefined {\n\tconst candidate = raw[\"price\"];\n\treturn typeof candidate === \"number\" ? candidate : undefined;\n}\n\nfunction readProductInputs(raw: unknown): ProductInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst name = readString(raw[\"name\"]);\n\tconst description = readString(raw[\"description\"]);\n\tif (name === undefined || description === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { name, description, price: readPrice(raw) };\n}\n\nfunction readProductOutputs(raw: unknown): ProductOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst productId = coerceRobloxId(raw[\"productId\"]);\n\tif (productId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { productId };\n}\n\nfunction readProductIconInputs(raw: unknown): ProductIconInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst filePath = readString(raw[\"filePath\"]);\n\tconst fileHash = readString(raw[\"fileHash\"]);\n\tif (filePath === undefined || fileHash === undefined || !isSha256Hex(fileHash)) {\n\t\treturn undefined;\n\t}\n\n\treturn { fileHash: asSha256Hex(fileHash), filePath };\n}\n\nfunction readProductIconOutputs(raw: unknown): ProductIconOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tif (assetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId };\n}\n\nfunction readProductIconParts(resource: MantleResource): ProductIconParts | undefined {\n\tconst inputs = readProductIconInputs(resource.inputs);\n\tconst outputs = readProductIconOutputs(resource.outputs);\n\tif (inputs === undefined || outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\ticon: { \"en-us\": inputs.filePath },\n\t\ticonImageAssetId: outputs.assetId,\n\t\tmantleIconFileHashes: { \"en-us\": inputs.fileHash },\n\t};\n}\n\nfunction foldOneProduct(\n\tresource: MantleResource,\n\ticonResource: MantleResource | undefined,\n): ProductFoldEntry | undefined {\n\tconst inputs = readProductInputs(resource.inputs);\n\tif (inputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readProductOutputs(resource.outputs);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst iconParts = iconResource === undefined ? undefined : readProductIconParts(iconResource);\n\tconst productOutputs: DeveloperProductOutputs =\n\t\ticonParts === undefined\n\t\t\t? { iconImageAssetId: undefined, productId: asRobloxAssetId(outputs.productId) }\n\t\t\t: {\n\t\t\t\t\ticonImageAssetId: asRobloxAssetId(iconParts.iconImageAssetId),\n\t\t\t\t\tproductId: asRobloxAssetId(outputs.productId),\n\t\t\t\t};\n\n\tconst base: ProductFoldEntry = {\n\t\tkey: asResourceKey(resource.key),\n\t\tentry: buildEntry(inputs, iconParts?.icon),\n\t\tmantlePath: `${PRODUCT_KIND}_${resource.key}`,\n\t\toutputs: productOutputs,\n\t};\n\n\treturn iconParts === undefined\n\t\t? base\n\t\t: { ...base, mantleIconFileHashes: iconParts.mantleIconFileHashes };\n}\n\nfunction orphanIconWarning(key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each productIcon_<k> resource must be paired with a matching product_<k>.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${PRODUCT_ICON_KIND}_${key}`,\n\t};\n}\n","import {\n\ttype BlockedFieldRule,\n\tblockedWarning,\n\ttype FoldFragment,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_KIND = \"experience\";\nconst EXPERIENCE_CONFIGURATION_KIND = \"experienceConfiguration\";\nconst UNIVERSE_AVATAR_PREFIX = \"universeAvatar\";\nconst UNIVERSE_AVATAR_REASON = \"avatar configuration has no Open Cloud equivalent\";\nconst GROUP_ID_REASON =\n\t\"Mantle used `groupId` to set the owning group when creating the experience. Bedrock requires pre-existing universe and place IDs and does not use this field.\";\n\n/**\n * `experienceConfiguration_singleton` fields with no Open Cloud writable\n * endpoint.\n */\nconst BLOCKED_FIELDS: ReadonlyArray<BlockedFieldRule> = [\n\t{ field: \"genre\", reason: \"Roblox does not expose `genre` via Open Cloud\" },\n\t{ field: \"isForSale\", reason: \"paid-access flag has no Open Cloud equivalent\" },\n\t{ field: \"price\", reason: \"paid-access flag has no Open Cloud equivalent\" },\n\t{\n\t\tfield: \"studioAccessToApisAllowed\",\n\t\treason: \"Open Cloud does not currently expose a write endpoint for studio API access\",\n\t},\n\t{\n\t\tfield: \"permissions\",\n\t\treason: \"experienceConfiguration.permissions has no Open Cloud equivalent\",\n\t},\n\t{ field: \"isArchived\", reason: \"isArchived has no Open Cloud equivalent\" },\n\t{ field: \"isFriendsOnly\", reason: \"isFriendsOnly has no Open Cloud equivalent\" },\n];\n\n/**\n * Emit one `blocked` `MigrationWarning` per non-`undefined` legacy-only\n * field on the experience-side Mantle resources: every entry in the\n * static `BLOCKED_FIELDS` table on `experienceConfiguration_singleton`,\n * any key under the `universeAvatar` prefix, and `groupId` on\n * `experience_singleton`. The Mantle null sentinel (`~`) is normalized\n * to `undefined` by `parseState`, so absent and null fields both skip.\n *\n * The fragment's `entryFragment` is always empty; this fold contributes\n * only warnings, and the `warnings` array is empty when none of the\n * watched resources have a populated blocked field.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns A fragment whose warnings list every blocked field present.\n */\nexport function foldBlockedExperienceFields(\n\tresources: ReadonlyArray<MantleResource>,\n): FoldFragment {\n\treturn {\n\t\tentryFragment: {},\n\t\twarnings: [...configurationWarnings(resources), ...groupIdWarnings(resources)],\n\t};\n}\n\nfunction groupIdWarnings(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\tconst experience = resources.find((resource) => resource.kind === EXPERIENCE_KIND);\n\tif (\n\t\texperience === undefined ||\n\t\t!isObjectPayload(experience.inputs) ||\n\t\texperience.inputs[\"groupId\"] === undefined\n\t) {\n\t\treturn [];\n\t}\n\n\treturn [blockedWarning(\"experience_singleton.groupId\", GROUP_ID_REASON)];\n}\n\nfunction staticFieldWarnings(inputs: Record<string, unknown>): ReadonlyArray<MigrationWarning> {\n\treturn BLOCKED_FIELDS.flatMap((rule) => {\n\t\tif (inputs[rule.field] === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn [blockedWarning(`experienceConfiguration_singleton.${rule.field}`, rule.reason)];\n\t});\n}\n\nfunction avatarFieldWarnings(inputs: Record<string, unknown>): ReadonlyArray<MigrationWarning> {\n\treturn Object.keys(inputs)\n\t\t.filter((key) => key.startsWith(UNIVERSE_AVATAR_PREFIX) && inputs[key] !== undefined)\n\t\t.map((key) =>\n\t\t\tblockedWarning(`experienceConfiguration_singleton.${key}`, UNIVERSE_AVATAR_REASON),\n\t\t);\n}\n\nfunction configurationWarnings(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn [];\n\t}\n\n\treturn [...staticFieldWarnings(config.inputs), ...avatarFieldWarnings(config.inputs)];\n}\n","import {\n\tambiguousWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_KIND = \"place\";\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\ninterface NamedPlaceConfig {\n\treadonly key: string;\n\treadonly name: string;\n}\n\nfunction readPlaceConfigName(resource: MantleResource): NamedPlaceConfig | undefined {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { name } = resource.inputs;\n\treturn typeof name === \"string\" ? { key: resource.key, name } : undefined;\n}\n\nfunction isStartPlace(resource: MantleResource): boolean {\n\tif (resource.kind !== PLACE_KIND || !isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\treturn resource.inputs[\"isStart\"] === true;\n}\n\nfunction startKeys(resources: ReadonlyArray<MantleResource>): ReadonlyArray<string> {\n\treturn resources.filter(isStartPlace).map((resource) => resource.key);\n}\n\nfunction interpretiveFragment(named: NamedPlaceConfig): FoldFragment {\n\treturn {\n\t\tentryFragment: { displayName: named.name },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.displayName\",\n\t\t\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${named.key}.name`,\n\t\t\t\trule: \"start-place-name-to-display-name\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nconst AMBIGUOUS_MULTIPLE_STARTS: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tambiguousWarning(\n\t\t\t\"place_*.isStart\",\n\t\t\t\"Multiple place_<k> resources have inputs.isStart=true; pick one as the canonical start place.\",\n\t\t),\n\t],\n};\n\n/**\n * Fold the start place's `placeConfiguration_<k>.name` into\n * `universe.displayName`. Non-start places' names are folded onto each\n * place's `displayName` by `foldPlaces`; multiple `isStart: true` places\n * emit one `ambiguous` warning and skip the displayName mapping.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded displayName plus per-rule diagnostics.\n */\nexport function foldDisplayName(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst starts = startKeys(resources);\n\tif (starts.length >= 2) {\n\t\treturn AMBIGUOUS_MULTIPLE_STARTS;\n\t}\n\n\tconst [startKey] = starts;\n\tconst startConfigResource = resources.findLast((resource) => {\n\t\treturn resource.kind === PLACE_CONFIGURATION_KIND && resource.key === startKey;\n\t});\n\tif (startConfigResource === undefined) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst named = readPlaceConfigName(startConfigResource);\n\treturn named === undefined ? EMPTY_FRAGMENT : interpretiveFragment(named);\n}\n","import {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_ICON_KIND = \"experienceIcon\";\n\nconst BLOCKED_REASON =\n\t\"Open Cloud has no route to set a universe's source-language game icon; configure it via the Roblox creator portal.\";\n\n/**\n * Surface every Mantle `experienceIcon_<key>` resource as a `blocked`\n * migration warning. Bedrock has no `UniverseEntry.icon` field today\n * because no Open Cloud endpoint accepts a source-language game icon, so\n * the migrator emits one warning per legacy resource (rather than the\n * first matching entry only) so the operator can audit each affected\n * environment before reconfiguring the icon by hand.\n *\n * Resources whose payload is malformed (non-object inputs/outputs,\n * non-string `filePath`) are skipped silently, matching the\n * malformed-payload behaviour of the other fold rules.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns A fragment whose `warnings` carries one `blocked` entry per\n * legacy experience-icon resource, or {@link EMPTY_FRAGMENT} when none\n * are present.\n */\nexport function foldExperienceIcon(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst warnings = resources\n\t\t.filter(\n\t\t\t(resource) => resource.kind === EXPERIENCE_ICON_KIND && hasReadablePayload(resource),\n\t\t)\n\t\t.map((resource) =>\n\t\t\tblockedWarning(`${EXPERIENCE_ICON_KIND}_${resource.key}`, BLOCKED_REASON),\n\t\t);\n\n\tif (warnings.length === 0) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn { entryFragment: {}, warnings };\n}\n\nfunction hasReadablePayload(resource: MantleResource): boolean {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\tconst { filePath } = resource.inputs;\n\treturn typeof filePath === \"string\";\n}\n","import type { SocialLinkField } from \"../resources.ts\";\nimport {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n\tmergeFragment,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst SOCIAL_LINK_KIND = \"socialLink\";\n\nconst SOCIAL_LINK_DOMAIN_TO_FIELD: Readonly<Record<string, SocialLinkField>> = {\n\t\"discord.gg\": \"discordSocialLink\",\n\t\"facebook.com\": \"facebookSocialLink\",\n\t\"guilded.gg\": \"guildedSocialLink\",\n\t\"roblox.com\": \"robloxGroupSocialLink\",\n\t\"twitch.tv\": \"twitchSocialLink\",\n\t\"twitter.com\": \"twitterSocialLink\",\n\t\"www.roblox.com\": \"robloxGroupSocialLink\",\n\t\"youtube.com\": \"youtubeSocialLink\",\n};\n\ninterface KnownSocialLink {\n\treadonly field: SocialLinkField;\n\treadonly mantlePath: string;\n\treadonly title: string;\n\treadonly url: string;\n}\n\n/**\n * Fold every `socialLink_<domain>` Mantle resource into the matching\n * `universe.<field>SocialLink` entry. Unknown domains emit a `blocked`\n * warning and are dropped from the output. Malformed payloads (non-string\n * `title` or `url`) drop silently per the established convention.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded social-link entries plus per-rule diagnostics.\n */\nexport function foldSocialLinks(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\treturn resources\n\t\t.filter((resource) => resource.kind === SOCIAL_LINK_KIND)\n\t\t.reduce<FoldFragment>(\n\t\t\t(accumulator, resource) => mergeFragment(accumulator, mapSocialLink(resource)),\n\t\t\tEMPTY_FRAGMENT,\n\t\t);\n}\n\nfunction knownFragment(known: KnownSocialLink): FoldFragment {\n\treturn {\n\t\tentryFragment: { [known.field]: { title: known.title, uri: known.url } },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: `universe.${known.field}`,\n\t\t\t\tmantlePath: known.mantlePath,\n\t\t\t\trule: \"domain-to-field\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction mapSocialLink(resource: MantleResource): FoldFragment {\n\tconst mantlePath = `${SOCIAL_LINK_KIND}_${resource.key}`;\n\tconst field = SOCIAL_LINK_DOMAIN_TO_FIELD[resource.key];\n\tif (field === undefined) {\n\t\treturn {\n\t\t\tentryFragment: {},\n\t\t\twarnings: [blockedWarning(mantlePath, `Unknown socialLink domain: ${resource.key}`)],\n\t\t};\n\t}\n\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { title, url } = resource.inputs;\n\tif (typeof title !== \"string\" || typeof url !== \"string\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn knownFragment({ field, mantlePath, title, url });\n}\n","import { asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { UniverseOutputs } from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport { foldBlockedExperienceFields } from \"./fold-blocked-experience-fields.ts\";\nimport { foldDisplayName } from \"./fold-display-name.ts\";\nimport { foldExperienceIcon } from \"./fold-experience-icon.ts\";\nimport { foldSocialLinks } from \"./fold-social-links.ts\";\nimport {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n\tmergeFragment,\n} from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_KIND = \"experience\";\nconst EXPERIENCE_ACTIVATION_KIND = \"experienceActivation\";\nconst EXPERIENCE_CONFIGURATION_KIND = \"experienceConfiguration\";\nconst SPATIAL_VOICE_KIND = \"spatialVoice\";\n\nconst PLAYABLE_DEVICE_TO_FLAG: Readonly<\n\tRecord<string, \"consoleEnabled\" | \"desktopEnabled\" | \"mobileEnabled\" | \"tabletEnabled\">\n> = {\n\tComputer: \"desktopEnabled\",\n\tConsole: \"consoleEnabled\",\n\tPhone: \"mobileEnabled\",\n\tTablet: \"tabletEnabled\",\n};\n\n/**\n * Output of folding the experience-related Mantle resources of one\n * environment into Bedrock's `universe` shape.\n *\n * `entry` populates the bedrock `Config.universe` block. `outputs`\n * populates the matching `BedrockState` resource's `outputs` field.\n * `warnings` carries per-rule diagnostics that the migration report\n * presents to the user.\n */\ninterface UniverseFoldResult {\n\t/**\n\t * Bedrock universe entry populated from the experience resource. Carries\n\t * `universeId` because the Mantle state always knows which universe the\n\t * environment targets; downstream consumers rely on the post-merge\n\t * invariant that `universeId` is present.\n\t */\n\treadonly entry: ResolvedUniverseEntry;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: UniverseOutputs;\n\t/** Per-rule diagnostics emitted while folding this environment's resources. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ExperienceOutputs {\n\treadonly assetId: string;\n\treadonly startPlaceId: string;\n}\n\n/**\n * Fold the universe-contributing Mantle resources of one environment\n * into a `UniverseEntry` plus matching `UniverseOutputs`.\n *\n * Returns `undefined` when no `experience_singleton` resource is present;\n * the caller treats that as \"this environment has no universe to migrate\"\n * and omits the `universe` field from the resulting `Config`.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded universe data plus warnings, or `undefined` when\n * no `experience_singleton` is present.\n */\nexport function foldUniverse(\n\tresources: ReadonlyArray<MantleResource>,\n): undefined | UniverseFoldResult {\n\tconst experience = resources.find((resource) => resource.kind === EXPERIENCE_KIND);\n\tif (experience === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readExperienceOutputs(experience);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst fragments = collectUniverseFragments(resources);\n\n\tconst entry: ResolvedUniverseEntry = fragments.reduce<ResolvedUniverseEntry>(\n\t\t(accumulator, fragment) => ({ ...accumulator, ...fragment.entryFragment }),\n\t\t{ universeId: outputs.assetId },\n\t);\n\n\tconst universeOutputs: UniverseOutputs = fragments.reduce<UniverseOutputs>(\n\t\t(accumulator, fragment) => ({ ...accumulator, ...fragment.outputsFragment }),\n\t\t{ rootPlaceId: asRobloxAssetId(outputs.startPlaceId) },\n\t);\n\n\tconst warnings = fragments.flatMap((fragment) => fragment.warnings);\n\n\treturn {\n\t\tentry,\n\t\toutputs: universeOutputs,\n\t\twarnings,\n\t};\n}\n\nfunction collectUniverseFragments(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<FoldFragment> {\n\treturn [\n\t\tfoldPlayableDevices(resources),\n\t\tfoldPrivateServers(resources),\n\t\tfoldVoiceChat(resources),\n\t\tfoldExperienceActivation(resources),\n\t\tfoldSocialLinks(resources),\n\t\tfoldDisplayName(resources),\n\t\tfoldExperienceIcon(resources),\n\t\tfoldBlockedExperienceFields(resources),\n\t];\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readExperienceOutputs(resource: MantleResource): ExperienceOutputs | undefined {\n\tconst raw = resource.outputs;\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tconst startPlaceId = coerceRobloxId(raw[\"startPlaceId\"]);\n\tif (assetId === undefined || startPlaceId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId, startPlaceId };\n}\n\nconst PLAYABLE_DEVICES_PATH = \"experienceConfiguration_singleton.playableDevices\";\n\nfunction mapPlayableDevice(raw: unknown): FoldFragment {\n\tconst flag = typeof raw === \"string\" ? PLAYABLE_DEVICE_TO_FLAG[raw] : undefined;\n\tif (flag === undefined) {\n\t\treturn {\n\t\t\tentryFragment: {},\n\t\t\twarnings: [\n\t\t\t\tblockedWarning(\n\t\t\t\t\tPLAYABLE_DEVICES_PATH,\n\t\t\t\t\t`Unknown playableDevices value: ${String(raw)}`,\n\t\t\t\t),\n\t\t\t],\n\t\t};\n\t}\n\n\treturn {\n\t\tentryFragment: { [flag]: true },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: `universe.${flag}`,\n\t\t\t\tmantlePath: PLAYABLE_DEVICES_PATH,\n\t\t\t\trule: \"list-to-flag\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction foldPlayableDevices(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { playableDevices } = config.inputs;\n\tif (!Array.isArray(playableDevices)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn playableDevices.reduce<FoldFragment>(\n\t\t(accumulator, raw) => mergeFragment(accumulator, mapPlayableDevice(raw)),\n\t\tEMPTY_FRAGMENT,\n\t);\n}\n\nconst PRIVATE_SERVERS_DISABLED: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tinterpretiveWarning({\n\t\t\tbedrockPath: \"universe.privateServerPriceRobux\",\n\t\t\tmantlePath: \"experienceConfiguration_singleton.allowPrivateServers\",\n\t\t\trule: \"private-servers-disabled-omitted\",\n\t\t}),\n\t],\n};\n\nfunction pricedPrivateServersFragment(price: number): FoldFragment {\n\treturn {\n\t\tentryFragment: { privateServerPriceRobux: price },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.privateServerPriceRobux\",\n\t\t\t\tmantlePath: \"experienceConfiguration_singleton.privateServerPrice\",\n\t\t\t\trule: \"private-servers-priced\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nconst EXPERIENCE_ACTIVATION_BLOCKED: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tblockedWarning(\n\t\t\t\"experienceActivation_singleton.isActive\",\n\t\t\t\"isActive has no Open Cloud equivalent\",\n\t\t),\n\t],\n};\n\nfunction readIsActive(resources: ReadonlyArray<MantleResource>): boolean | undefined {\n\tconst activation = resources.find((resource) => resource.kind === EXPERIENCE_ACTIVATION_KIND);\n\tif (activation === undefined || !isObjectPayload(activation.inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { isActive } = activation.inputs;\n\treturn typeof isActive === \"boolean\" ? isActive : undefined;\n}\n\nfunction foldExperienceActivation(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tif (readIsActive(resources) === undefined) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn EXPERIENCE_ACTIVATION_BLOCKED;\n}\n\nfunction foldVoiceChat(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst voice = resources.find((resource) => resource.kind === SPATIAL_VOICE_KIND);\n\tif (voice === undefined || !isObjectPayload(voice.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { enabled } = voice.inputs;\n\tif (typeof enabled !== \"boolean\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn {\n\t\tentryFragment: { voiceChatEnabled: enabled },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.voiceChatEnabled\",\n\t\t\t\tmantlePath: \"spatialVoice_singleton.enabled\",\n\t\t\t\trule: \"voice-chat-enabled\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction foldPrivateServers(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { allowPrivateServers, privateServerPrice } = config.inputs;\n\tif (allowPrivateServers === false) {\n\t\treturn PRIVATE_SERVERS_DISABLED;\n\t}\n\n\tif (allowPrivateServers !== true || typeof privateServerPrice !== \"number\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn pricedPrivateServersFragment(privateServerPrice);\n}\n","import type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\n/**\n * Mantle resource kinds bedrock plans to support but does not yet model.\n * Maps the wire-form discriminator to the human-readable noun used in\n * each emitted `deferred` warning's `reason`. The reason string is\n * shared across all resources of the same kind.\n */\nconst DEFERRED_KIND_REASONS: Readonly<Record<string, string>> = {\n\tassetAlias: \"asset aliases\",\n\taudioAsset: \"audio assets\",\n\tbadge: \"badges\",\n\tbadgeIcon: \"badge icons\",\n\texperienceThumbnail: \"experience thumbnails\",\n\texperienceThumbnailOrder: \"experience thumbnail ordering\",\n\timageAsset: \"image assets\",\n\tnotification: \"experience notifications\",\n};\n\n/**\n * Emit one `deferred` `MigrationWarning` per Mantle resource whose kind\n * bedrock has reserved for a future slice. The warning's `mantlePath` is\n * resource-rooted (`<kind>_<key>`). Resources whose kind is not in the\n * deferred set are passed over silently.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns One warning per deferred-kind resource, in the input order.\n */\nexport function foldUnsupported(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\treturn resources.flatMap((resource): ReadonlyArray<MigrationWarning> => {\n\t\tconst humanName = DEFERRED_KIND_REASONS[resource.kind];\n\t\tif (humanName === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: \"deferred\",\n\t\t\t\tmantlePath: `${resource.kind}_${resource.key}`,\n\t\t\t\treason: `${humanName}: not yet modeled in bedrock; will surface once the relevant kind ships`,\n\t\t\t},\n\t\t];\n\t});\n}\n","import type { UniverseOutputs } from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport { foldPasses, type PassFoldEntry } from \"./fold-passes.ts\";\nimport { foldPlaces, type PlaceFoldEntry } from \"./fold-places.ts\";\nimport { foldProducts, type ProductFoldEntry } from \"./fold-products.ts\";\nimport { foldUniverse } from \"./fold-universe.ts\";\nimport { foldUnsupported } from \"./fold-unsupported.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\n/**\n * Result of folding one Mantle environment's resource list into bedrock\n * shapes. A Mantle environment without an experience resource yields\n * `universe: undefined`, signalling that the bedrock `Config` for this\n * environment carries no `universe` block. `places`, `passes`, and\n * `products` are always present (potentially empty) so the orchestrator\n * does not have to special-case \"no <kind>\" against \"<kind> not yet\n * wired\"; orphan place resources surface as `ambiguous` warnings, and\n * resources of kinds bedrock plans to support but does not yet model\n * surface as `deferred` warnings.\n */\nexport interface EnvironmentFoldResult {\n\t/** Folded pass entries for this environment, in declaration order. */\n\treadonly passes: ReadonlyArray<PassFoldEntry>;\n\t/** Folded place entries for this environment, keyed by Mantle's place key. */\n\treadonly places: ReadonlyMap<string, PlaceFoldEntry>;\n\t/** Folded developer-product entries for this environment, in declaration order. */\n\treadonly products: ReadonlyArray<ProductFoldEntry>;\n\t/** Folded universe data for this environment, or `undefined` when no experience is present. */\n\treadonly universe:\n\t\t| undefined\n\t\t| {\n\t\t\t\treadonly entry: ResolvedUniverseEntry;\n\t\t\t\treadonly outputs: UniverseOutputs;\n\t\t };\n\t/** Per-rule diagnostics aggregated across all per-kind folds. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Orchestrate the per-kind folds for one Mantle environment, gathering\n * the results and warnings into a single `EnvironmentFoldResult`. Pure;\n * delegates to `foldUniverse`, `foldPlaces`, `foldPasses`, `foldProducts`,\n * and `foldUnsupported`. Emitted warning paths are resource-rooted.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded per-kind data plus aggregated warnings.\n */\nexport function foldEnvironment(resources: ReadonlyArray<MantleResource>): EnvironmentFoldResult {\n\tconst universeResult = foldUniverse(resources);\n\tconst universe =\n\t\tuniverseResult === undefined\n\t\t\t? undefined\n\t\t\t: { entry: universeResult.entry, outputs: universeResult.outputs };\n\n\tconst placesResult = foldPlaces(resources);\n\tconst passesResult = foldPasses(resources);\n\tconst productsResult = foldProducts(resources);\n\n\tconst warnings = [\n\t\t...(universeResult?.warnings ?? []),\n\t\t...placesResult.warnings,\n\t\t...passesResult.warnings,\n\t\t...productsResult.warnings,\n\t\t...foldUnsupported(resources),\n\t];\n\n\treturn {\n\t\tpasses: passesResult.passes,\n\t\tplaces: placesResult.entries,\n\t\tproducts: productsResult.products,\n\t\tuniverse,\n\t\twarnings,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { parseYAML } from \"confbox\";\n\nimport type { MigrateError } from \"./migration-report.ts\";\nimport type { MantleResource, MantleStateV6 } from \"./types.ts\";\n\nconst SUPPORTED_VERSIONS = [\"6\"] as const;\n\n/**\n * Parse the contents of a `.mantle-state.yml` file into a typed\n * `MantleStateV6` envelope. Pure: takes a string, returns a `Result`.\n *\n * The parser also normalizes JSON `null` values to `undefined` recursively\n * across each resource's `inputs` and `outputs` payloads (per the project\n * type convention) and splits each resource's `id` field into the\n * `(kind, key)` pair carried on `MantleResource`.\n *\n * Surfaces three discriminated `MigrateError` kinds:\n * - `stateParseFailed` when the YAML parser refuses the file or its\n * structural shape (missing `environments`, malformed resource entry,\n * non-string `id`, etc.) does not match the v6 envelope.\n * - `unsupportedMantleStateVersion` when the `version` field decodes but\n * is not one of the values in `SUPPORTED_VERSIONS`.\n *\n * @param raw - Raw YAML text decoded from the state file.\n * @param sourceFile - Path or identifier of the source file, threaded into\n * any returned `MigrateError` for diagnostics.\n * @returns `Ok` with the parsed envelope, or `Err` with a discriminated\n * `MigrateError` describing why the file was rejected.\n */\nexport function parseState(raw: string, sourceFile: string): Result<MantleStateV6, MigrateError> {\n\tlet envelope: Record<string, unknown>;\n\ttry {\n\t\tconst parsed = parseYAML(raw);\n\t\tenvelope = expectObject(parsed);\n\t} catch (err) {\n\t\treturn parseFailedFrom(err, sourceFile);\n\t}\n\n\tconst versionResult = parseVersion(envelope[\"version\"]);\n\tif (!versionResult.success) {\n\t\treturn versionResult;\n\t}\n\n\tlet environments: Readonly<Record<string, ReadonlyArray<MantleResource>>>;\n\ttry {\n\t\tenvironments = parseEnvironments(envelope[\"environments\"]);\n\t} catch (err) {\n\t\treturn parseFailedFrom(err, sourceFile);\n\t}\n\n\treturn {\n\t\tdata: { environments, version: versionResult.data },\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseVersion(raw: unknown): Result<\"6\", MigrateError> {\n\tconst found = typeof raw === \"string\" ? raw : String(raw);\n\tif (found !== \"6\") {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tfound,\n\t\t\t\tkind: \"unsupportedMantleStateVersion\",\n\t\t\t\tsupported: SUPPORTED_VERSIONS,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: \"6\", success: true };\n}\n\nfunction isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction describe(value: unknown): string {\n\tif (value === null) {\n\t\treturn \"null\";\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn \"array\";\n\t}\n\n\treturn typeof value;\n}\n\nfunction expectObject(value: unknown): Record<string, unknown> {\n\tif (!isObjectPayload(value)) {\n\t\tthrow new TypeError(`expected object, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction expectArray(value: unknown): ReadonlyArray<unknown> {\n\tif (!Array.isArray(value)) {\n\t\tthrow new TypeError(`expected array, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction expectString(value: unknown, label: string): string {\n\tif (typeof value !== \"string\") {\n\t\tthrow new TypeError(`expected ${label} to be a string, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction stripNulls(value: unknown): unknown {\n\tif (value === null) {\n\t\treturn undefined;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn value.map(stripNulls);\n\t}\n\n\tif (isObjectPayload(value)) {\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(value).map(([key, child]) => [key, stripNulls(child)]),\n\t\t);\n\t}\n\n\treturn value;\n}\n\nfunction parseResource(value: unknown): MantleResource {\n\tconst resource = expectObject(value);\n\tconst id = expectString(resource[\"id\"], \"id\");\n\tconst underscoreIndex = id.indexOf(\"_\");\n\tif (underscoreIndex <= 0 || underscoreIndex === id.length - 1) {\n\t\tthrow new Error(`expected id of form <kind>_<key>, got \"${id}\"`);\n\t}\n\n\tconst kind = id.slice(0, underscoreIndex);\n\tconst key = id.slice(underscoreIndex + 1);\n\n\tconst inputsObject = expectObject(resource[\"inputs\"]);\n\tconst inputs = stripNulls(inputsObject[kind]);\n\n\tconst outputsRaw = resource[\"outputs\"];\n\tconst outputs = isObjectPayload(outputsRaw) ? stripNulls(outputsRaw[kind]) : undefined;\n\n\tconst dependencies = expectArray(resource[\"dependencies\"]).map((dep) => {\n\t\treturn expectString(dep, \"dependency\");\n\t});\n\n\treturn { key, dependencies, inputs, kind, outputs };\n}\n\nfunction parseEnvironments(raw: unknown): Readonly<Record<string, ReadonlyArray<MantleResource>>> {\n\tconst environmentsObject = expectObject(raw);\n\treturn Object.fromEntries(\n\t\tObject.entries(environmentsObject).map(([name, value]) => [\n\t\t\tname,\n\t\t\texpectArray(value).map(parseResource),\n\t\t]),\n\t);\n}\n\nfunction parseFailedFrom(err: unknown, sourceFile: string): Result<MantleStateV6, MigrateError> {\n\tconst reason = err instanceof Error ? err.message : String(err);\n\treturn {\n\t\terr: { kind: \"stateParseFailed\", path: sourceFile, reason },\n\t\tsuccess: false,\n\t};\n}\n","import { stringifyYAML } from \"confbox\";\n\nimport type { Config } from \"../schema.ts\";\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * Inputs for {@link serializeConfig}. Format dispatch lives on\n * `configFormat` so a single function shape covers both TypeScript and\n * YAML output.\n */\nexport interface SerializeConfigOptions {\n\t/** Validated bedrock config to render. */\n\treadonly config: Config;\n\t/** Output format. TypeScript emits `defineConfig({...})`; YAML emits a `bedrock.config.yaml` body. */\n\treadonly configFormat: \"typescript\" | \"yaml\";\n}\n\n/**\n * Render a bedrock `Config` as the source text of a `bedrock.config.{ts,yaml}`\n * file the user can write straight to disk.\n *\n * The TypeScript branch emits a hand-authored object literal: tab-indented,\n * with double-quoted string values and unquoted keys for valid identifier\n * names (quoted otherwise). The YAML branch delegates to `stringifyYAML`,\n * which already drops `undefined`-valued properties so the output never\n * surfaces `null` or `~` for absent managed fields. Both shapes round-trip\n * through `loadConfig` cleanly.\n *\n * @param options - Render inputs.\n * @returns A UTF-8 source string ending with a trailing newline.\n */\nexport function serializeConfig(options: SerializeConfigOptions): string {\n\tif (options.configFormat === \"yaml\") {\n\t\treturn `${stringifyYAML(options.config)}\\n`;\n\t}\n\n\tconst body = renderObjectLiteral(options.config, 0);\n\treturn [\n\t\t'import { defineConfig } from \"@bedrock-rbx/core/config\";',\n\t\t\"\",\n\t\t`export default defineConfig(${body});`,\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nfunction renderValue(value: unknown, depth: number): string {\n\tif (Array.isArray(value)) {\n\t\treturn renderArrayLiteral(value, depth);\n\t}\n\n\tif (value !== null && typeof value === \"object\") {\n\t\treturn renderObjectLiteral(value, depth);\n\t}\n\n\tif (typeof value === \"string\") {\n\t\treturn JSON.stringify(value);\n\t}\n\n\treturn String(value);\n}\n\nfunction renderObjectLiteral(value: object, depth: number): string {\n\tconst entries = Object.entries(value).filter(([, fieldValue]) => fieldValue !== undefined);\n\tif (entries.length === 0) {\n\t\treturn \"{}\";\n\t}\n\n\tconst innerIndent = \"\\t\".repeat(depth + 1);\n\tconst closeIndent = \"\\t\".repeat(depth);\n\tconst lines = entries.map(([key, fieldValue]) => {\n\t\tconst renderedKey = IDENTIFIER_PATTERN.test(key) ? key : JSON.stringify(key);\n\t\treturn `${innerIndent}${renderedKey}: ${renderValue(fieldValue, depth + 1)}`;\n\t});\n\treturn `{\\n${lines.join(\",\\n\")}\\n${closeIndent}}`;\n}\n\nfunction renderArrayLiteral(value: ReadonlyArray<unknown>, depth: number): string {\n\tif (value.length === 0) {\n\t\treturn \"[]\";\n\t}\n\n\tconst innerIndent = \"\\t\".repeat(depth + 1);\n\tconst closeIndent = \"\\t\".repeat(depth);\n\tconst lines = value.map((item) => `${innerIndent}${renderValue(item, depth + 1)}`);\n\treturn `[\\n${lines.join(\",\\n\")}\\n${closeIndent}]`;\n}\n","import type { MigrationSummary, MigrationWarning } from \"./migration-report.ts\";\n\nconst ZERO_SUMMARY: MigrationSummary = {\n\tambiguousCount: 0,\n\tblockedCount: 0,\n\tdeferredCount: 0,\n\tinterpretiveCount: 0,\n};\n\n/**\n * Fold a `MigrationWarning` array into a `MigrationSummary` so the\n * report's aggregate counts are derived from the warning list rather\n * than maintained in parallel. Future warning-emitting slices thread\n * through this helper instead of having to remember to update the\n * summary at every emission site.\n *\n * @param warnings - Warnings to aggregate; the empty array yields a\n * zeroed summary.\n * @returns Per-kind counts.\n */\nexport function summarizeWarnings(warnings: ReadonlyArray<MigrationWarning>): MigrationSummary {\n\treturn warnings.reduce<MigrationSummary>((accumulator, warning) => {\n\t\treturn {\n\t\t\t...accumulator,\n\t\t\t[`${warning.kind}Count`]: accumulator[`${warning.kind}Count`] + 1,\n\t\t};\n\t}, ZERO_SUMMARY);\n}\n","import { join } from \"node:path\";\n\nimport { sha256Hex } from \"../core/kinds/hash.ts\";\nimport type { EnvironmentFoldResult } from \"../core/migrate/fold-environment.ts\";\nimport type { PassFoldEntry } from \"../core/migrate/fold-passes.ts\";\nimport type { ProductFoldEntry } from \"../core/migrate/fold-products.ts\";\nimport type { MigrationWarning } from \"../core/migrate/migration-report.ts\";\nimport { asSha256Hex, type ResourceKey, type Sha256Hex } from \"../types/ids.ts\";\n\n/**\n * Result of walking each environment's pass and product entries and\n * recomputing the SHA-256 digest of every locale-keyed icon path from\n * disk. `passHashesByEnvironment` and `productHashesByEnvironment` carry\n * the recomputed digests keyed by environment then by resource key; the\n * inner record mirrors `GamePassDesiredState.iconFileHashes` /\n * `DeveloperProductDesiredState.iconFileHashes`. `warnings` carries one\n * `kind: \"ambiguous\"` warning per resource whose icon could not be read so\n * the caller can fall back to the Mantle-recorded hashes without losing\n * the diagnostic.\n */\nexport interface IconHashRecomputation {\n\t/** Recomputed pass-icon digests keyed by environment then by pass key. */\n\treadonly passHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\t/** Recomputed product-icon digests keyed by environment then by product key. */\n\treadonly productHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\t/** One ambiguous warning per resource whose icon could not be read. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/** Inputs for {@link recomputeIconHashes}. */\ninterface RecomputeIconHashesInputs {\n\t/** Per-environment fold results carrying pass and product entries to walk. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/** Reads file bytes; same shape as `MigrateMantleStateDeps.readFile`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Directory the state file lives in; relative icon paths resolve against it. */\n\treadonly stateFileDirectory: string;\n}\n\ninterface IconWalkEntry {\n\treadonly key: ResourceKey;\n\treadonly iconPath: string;\n\treadonly mantlePath: string;\n}\n\ninterface IconWalkInputs {\n\treadonly entries: ReadonlyArray<IconWalkEntry>;\n\treadonly environmentName: string;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly stateFileDirectory: string;\n}\n\ninterface IconWalkResult {\n\treadonly perKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface AmbiguousIconWarningInputs {\n\treadonly environmentName: string;\n\treadonly mantlePath: string;\n\treadonly resolvedPath: string;\n}\n\ninterface WalkEnvironmentInputs {\n\treadonly environmentName: string;\n\treadonly folded: EnvironmentFoldResult;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly stateFileDirectory: string;\n}\n\ninterface WalkEnvironmentResult {\n\treadonly passHashes: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly productHashes: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Walk each environment's folded pass and product entries, resolve the\n * locale-keyed icon paths against `stateFileDirectory`, read the bytes via\n * the injected `readFile`, and compute the SHA-256 hex digest. Files that\n * cannot be read surface as `ambiguous` `MigrationWarning`s with the\n * environment-prefixed `mantlePath` and a hint pointing at the resolved\n * path; the caller carries the Mantle-recorded hashes forward as a\n * fallback. Products without an icon partner are silently skipped.\n *\n * @param inputs - Per-environment fold results plus I/O dependencies.\n * @returns Per-environment recomputed pass and product hashes plus\n * accumulated ambiguous warnings.\n */\nexport async function recomputeIconHashes(\n\tinputs: RecomputeIconHashesInputs,\n): Promise<IconHashRecomputation> {\n\tconst walked = await Promise.all(\n\t\t[...inputs.folds.entries()].map(async ([environment, folded]) => {\n\t\t\tconst result = await walkEnvironment({\n\t\t\t\tenvironmentName: environment,\n\t\t\t\tfolded,\n\t\t\t\treadFile: inputs.readFile,\n\t\t\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t\t\t});\n\t\t\treturn [environment, result] as const;\n\t\t}),\n\t);\n\n\treturn collectRecomputation(walked);\n}\n\nfunction collectRecomputation(\n\twalked: ReadonlyArray<readonly [string, WalkEnvironmentResult]>,\n): IconHashRecomputation {\n\treturn {\n\t\tpassHashesByEnvironment: new Map(\n\t\t\twalked.map(([environment, walk]) => [environment, walk.passHashes]),\n\t\t),\n\t\tproductHashesByEnvironment: new Map(\n\t\t\twalked.map(([environment, walk]) => [environment, walk.productHashes]),\n\t\t),\n\t\twarnings: walked.flatMap(([, walk]) => walk.warnings),\n\t};\n}\n\nfunction passWalkEntry(entry: PassFoldEntry): IconWalkEntry {\n\treturn {\n\t\tkey: entry.key,\n\t\ticonPath: entry.entry.icon[\"en-us\"],\n\t\tmantlePath: entry.mantlePath,\n\t};\n}\n\nfunction productWalkEntries(entry: ProductFoldEntry): ReadonlyArray<IconWalkEntry> {\n\tif (entry.entry.icon === undefined) {\n\t\treturn [];\n\t}\n\n\treturn [\n\t\t{\n\t\t\tkey: entry.key,\n\t\t\ticonPath: entry.entry.icon[\"en-us\"],\n\t\t\tmantlePath: entry.mantlePath,\n\t\t},\n\t];\n}\n\nasync function tryRecomputeHash(\n\treadFile: (path: string) => Promise<Uint8Array>,\n\tpath: string,\n): Promise<Sha256Hex | undefined> {\n\ttry {\n\t\tconst bytes = await readFile(path);\n\t\treturn asSha256Hex(await sha256Hex(bytes));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction buildAmbiguousIconWarning(inputs: AmbiguousIconWarningInputs): MigrationWarning {\n\treturn {\n\t\thint: `Could not read icon file at ${inputs.resolvedPath}; verify the file's location relative to the state file or correct the icon entry before re-running.`,\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${inputs.environmentName}.${inputs.mantlePath}`,\n\t};\n}\n\nasync function walkIconEntries(inputs: IconWalkInputs): Promise<IconWalkResult> {\n\tconst perKey = new Map<ResourceKey, Record<\"en-us\", Sha256Hex>>();\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const entry of inputs.entries) {\n\t\tconst resolved = join(inputs.stateFileDirectory, entry.iconPath);\n\t\tconst recomputed = await tryRecomputeHash(inputs.readFile, resolved);\n\t\tif (recomputed === undefined) {\n\t\t\twarnings.push(\n\t\t\t\tbuildAmbiguousIconWarning({\n\t\t\t\t\tenvironmentName: inputs.environmentName,\n\t\t\t\t\tmantlePath: entry.mantlePath,\n\t\t\t\t\tresolvedPath: resolved,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tperKey.set(entry.key, { \"en-us\": recomputed });\n\t\t}\n\t}\n\n\treturn { perKey, warnings };\n}\n\nasync function walkEnvironment(inputs: WalkEnvironmentInputs): Promise<WalkEnvironmentResult> {\n\tconst passWalk = await walkIconEntries({\n\t\tentries: inputs.folded.passes.map(passWalkEntry),\n\t\tenvironmentName: inputs.environmentName,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t});\n\tconst productWalk = await walkIconEntries({\n\t\tentries: inputs.folded.products.flatMap(productWalkEntries),\n\t\tenvironmentName: inputs.environmentName,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t});\n\treturn {\n\t\tpassHashes: passWalk.perKey,\n\t\tproductHashes: productWalk.perKey,\n\t\twarnings: [...passWalk.warnings, ...productWalk.warnings],\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { readFile as nodeReadFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport { buildState } from \"../core/migrate/build-state.ts\";\nimport { factorizeEnvironments } from \"../core/migrate/factorize-environments.ts\";\nimport { type EnvironmentFoldResult, foldEnvironment } from \"../core/migrate/fold-environment.ts\";\nimport type {\n\tMigrateError,\n\tMigrationReport,\n\tMigrationWarning,\n} from \"../core/migrate/migration-report.ts\";\nimport { parseState } from \"../core/migrate/parse-state.ts\";\nimport { serializeConfig } from \"../core/migrate/serialize-config.ts\";\nimport { summarizeWarnings } from \"../core/migrate/summarize-warnings.ts\";\nimport type { MantleStateV6 } from \"../core/migrate/types.ts\";\nimport { type Config, validateConfig } from \"../core/schema.ts\";\nimport type { BedrockState } from \"../core/state.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../types/ids.ts\";\nimport { type IconHashRecomputation, recomputeIconHashes } from \"./recompute-icon-hashes.ts\";\n\ntype ConfigFormat = \"typescript\" | \"yaml\";\n\nconst FILE_MISSING_CODES = new Set([\"ENOENT\"]);\n\n/**\n * Inputs for `migrateMantleState`. The state file is read via\n * `readFile` (defaults to `node:fs/promises.readFile`) so callers can\n * inject in-memory fixtures from tests and the JSDoc `@example` block\n * stays self-contained.\n *\n * `configFormat` selects the output shape: `\"typescript\"` emits a\n * `bedrock.config.ts` with `defineConfig({...})`; `\"yaml\"` emits a\n * `bedrock.config.yaml` body. Both shapes round-trip through\n * `loadConfig` cleanly.\n */\nexport interface MigrateMantleStateDeps {\n\t/**\n\t * Output format for the emitted bedrock config file. `\"typescript\"`\n\t * produces a `defineConfig({...})` module; `\"yaml\"` produces a YAML\n\t * body whose keys match the `Config` schema.\n\t */\n\treadonly configFormat: ConfigFormat;\n\t/**\n\t * Environment in the input state file whose resolved values seed\n\t * the root config. Required when the state file declares more than\n\t * one environment; ignored when only one environment is present.\n\t */\n\treadonly primaryEnvironment?: string;\n\t/**\n\t * Reads file bytes; defaults to `node:fs/promises.readFile`. Kept\n\t * `Uint8Array`-typed to match `deploy`, `buildDesired`, and\n\t * `buildDefaultRegistry`. UTF-8 decoding happens inside the migrator\n\t * before YAML parsing.\n\t */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n\t/** Absolute path to the `.mantle-state.yml` file to migrate. */\n\treadonly stateFilePath: string;\n}\n\ninterface FinalizeReportInputs {\n\treadonly config: Config;\n\treadonly configFormat: ConfigFormat;\n\treadonly factorizeWarnings: ReadonlyArray<MigrationWarning>;\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly iconRecomputation: IconHashRecomputation;\n}\n\ninterface AssembleReportInputs {\n\treadonly configFormat: ConfigFormat;\n\treadonly primaryEnvironment: string | undefined;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly state: MantleStateV6;\n\treadonly stateFilePath: string;\n}\n\n/**\n * Read a Mantle state file and produce a `MigrationReport` containing a\n * bedrock config, per-environment `BedrockState`s, and a structured list\n * of fields that did not migrate verbatim.\n *\n * Skeleton: handles single-environment or multi-environment states with\n * universe, place, and game-pass resources. The primary environment\n * auto-picks when only one environment is present; multi-environment\n * inputs without an explicit `primaryEnvironment` return\n * `Err({ kind: \"primaryEnvironmentRequired\", available })` so the\n * migrator never silently picks a winner. Future slices add social\n * links and the deferred / blocked warning categories.\n *\n * @param deps - Inputs for the migration.\n * @returns `Ok` with a `MigrationReport` on success, or `Err` with a\n * discriminated `MigrateError` on failure.\n * @rejects Re-thrown `readFile` failure when the underlying error code is\n * not in the recognized \"missing file\" set; surfaced so callers see\n * permission or filesystem outages instead of having them coerced to\n * `stateFileNotFound`.\n * @example\n *\n * ```ts\n * import { migrateMantleState } from \"@bedrock-rbx/core\";\n *\n * const yaml = [\n * 'version: \"6\"',\n * \"environments:\",\n * \" production:\",\n * \" - id: experience_singleton\",\n * \" inputs:\",\n * \" experience:\",\n * \" groupId: ~\",\n * \" outputs:\",\n * \" experience:\",\n * \" assetId: 6031475575\",\n * \" startPlaceId: 17613681043\",\n * \" dependencies: []\",\n * \"\",\n * ].join(\"\\n\");\n *\n * async function readFile(): Promise<Uint8Array> {\n * return new TextEncoder().encode(yaml);\n * }\n *\n * return migrateMantleState({\n * configFormat: \"typescript\",\n * readFile,\n * stateFilePath: \".mantle-state.yml\",\n * }).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.config.universe?.universeId).toBe(\"6031475575\");\n * }\n * });\n * ```\n */\nexport async function migrateMantleState(\n\tdeps: MigrateMantleStateDeps,\n): Promise<Result<MigrationReport, MigrateError>> {\n\tconst readFile = deps.readFile ?? nodeReadFile;\n\n\tlet bytes: Uint8Array;\n\ttry {\n\t\tbytes = await readFile(deps.stateFilePath);\n\t} catch (err) {\n\t\tif (isFileMissing(err)) {\n\t\t\treturn {\n\t\t\t\terr: { kind: \"stateFileNotFound\", path: deps.stateFilePath },\n\t\t\t\tsuccess: false,\n\t\t\t};\n\t\t}\n\n\t\tthrow err;\n\t}\n\n\tconst raw = new TextDecoder(\"utf-8\").decode(bytes);\n\tconst parsed = parseState(raw, deps.stateFilePath);\n\tif (!parsed.success) {\n\t\treturn parsed;\n\t}\n\n\treturn assembleReport({\n\t\tconfigFormat: deps.configFormat,\n\t\tprimaryEnvironment: deps.primaryEnvironment,\n\t\treadFile,\n\t\tstate: parsed.data,\n\t\tstateFilePath: deps.stateFilePath,\n\t});\n}\n\nconst EMPTY_HASHES: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>> = new Map();\n\ninterface BuildStatesByEnvironmentInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly passHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\treadonly productHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n}\n\nfunction buildStatesByEnvironment(\n\tinputs: BuildStatesByEnvironmentInputs,\n): Readonly<Record<string, BedrockState>> {\n\treturn Object.fromEntries(\n\t\t[...inputs.folds.entries()].map(([name, folded]): [string, BedrockState] => {\n\t\t\treturn [\n\t\t\t\tname,\n\t\t\t\tbuildState({\n\t\t\t\t\tenvironment: name,\n\t\t\t\t\tfolded,\n\t\t\t\t\tpassIconHashesByKey: inputs.passHashesByEnvironment.get(name) ?? EMPTY_HASHES,\n\t\t\t\t\tproductIconHashesByKey:\n\t\t\t\t\t\tinputs.productHashesByEnvironment.get(name) ?? EMPTY_HASHES,\n\t\t\t\t}),\n\t\t\t];\n\t\t}),\n\t);\n}\n\nfunction prefixMantlePath(warning: MigrationWarning, environmentName: string): MigrationWarning {\n\treturn { ...warning, mantlePath: `${environmentName}.${warning.mantlePath}` };\n}\n\nfunction collectFoldWarnings(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): ReadonlyArray<MigrationWarning> {\n\treturn [...folds.entries()].flatMap(([name, fold]) => {\n\t\treturn fold.warnings.map((warning) => prefixMantlePath(warning, name));\n\t});\n}\n\nfunction buildReport(inputs: FinalizeReportInputs, validated: Config): MigrationReport {\n\tconst {\n\t\tpassHashesByEnvironment,\n\t\tproductHashesByEnvironment,\n\t\twarnings: iconWarnings,\n\t} = inputs.iconRecomputation;\n\tconst warnings = [\n\t\t...collectFoldWarnings(inputs.folds),\n\t\t...inputs.factorizeWarnings,\n\t\t...iconWarnings,\n\t];\n\treturn {\n\t\tconfig: validated,\n\t\tconfigFileContent: serializeConfig({\n\t\t\tconfig: validated,\n\t\t\tconfigFormat: inputs.configFormat,\n\t\t}),\n\t\tstatesByEnvironment: buildStatesByEnvironment({\n\t\t\tfolds: inputs.folds,\n\t\t\tpassHashesByEnvironment,\n\t\t\tproductHashesByEnvironment,\n\t\t}),\n\t\tsummary: summarizeWarnings(warnings),\n\t\twarnings,\n\t};\n}\n\nfunction finalizeReport(inputs: FinalizeReportInputs): Result<MigrationReport, MigrateError> {\n\tconst validated = validateConfig(inputs.config, \"<migrate-mantle-state>\");\n\tif (!validated.success) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tcause: validated.err,\n\t\t\t\tkind: \"internalError\",\n\t\t\t\treason: \"migrator emitted a config that failed validateConfig\",\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: buildReport(inputs, validated.data), success: true };\n}\n\nasync function assembleReport(\n\tinputs: AssembleReportInputs,\n): Promise<Result<MigrationReport, MigrateError>> {\n\tconst available = Object.keys(inputs.state.environments);\n\tconst folds: ReadonlyMap<string, EnvironmentFoldResult> = new Map(\n\t\tavailable.map((name) => [name, foldEnvironment(inputs.state.environments[name] ?? [])]),\n\t);\n\tconst factorized = factorizeEnvironments({\n\t\tfolds,\n\t\tprimaryEnvironment: inputs.primaryEnvironment,\n\t});\n\tif (!factorized.success) {\n\t\treturn factorized;\n\t}\n\n\tconst iconRecomputation = await recomputeIconHashes({\n\t\tfolds,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: dirname(inputs.stateFilePath),\n\t});\n\treturn finalizeReport({\n\t\tconfig: factorized.data.config,\n\t\tconfigFormat: inputs.configFormat,\n\t\tfactorizeWarnings: factorized.data.warnings,\n\t\tfolds,\n\t\ticonRecomputation,\n\t});\n}\n\nfunction isFileMissing(err: unknown): boolean {\n\treturn (\n\t\ttypeof err === \"object\" &&\n\t\terr !== null &&\n\t\t\"code\" in err &&\n\t\ttypeof err.code === \"string\" &&\n\t\tFILE_MISSING_CODES.has(err.code)\n\t);\n}\n"],"mappings":"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"}
|