@bedrock-rbx/core 0.1.0-beta.1 → 0.1.0-beta.10

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.
@@ -8,10 +8,10 @@ import { PlacesClient } from "@bedrock-rbx/ocale/places";
8
8
  import { UniversesClient } from "@bedrock-rbx/ocale/universes";
9
9
  import { readFile } from "node:fs/promises";
10
10
  import { loadConfig } from "c12";
11
- import { execFile } from "node:child_process";
12
11
  import { existsSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
13
- import { tmpdir } from "node:os";
14
12
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
13
+ import { execFile } from "node:child_process";
14
+ import { tmpdir } from "node:os";
15
15
  import { parseYAML, stringifyYAML } from "confbox";
16
16
  //#region src/core/derive-price-fields.ts
17
17
  /**
@@ -861,6 +861,8 @@ const RETRYABLE_STATUSES = new Set([
861
861
  503,
862
862
  504
863
863
  ]);
864
+ const MAX_VISIBILITY_ATTEMPTS = 5;
865
+ const VISIBILITY_BASE_DELAY_MS = 250;
864
866
  /**
865
867
  * Build a `StatePort` that persists Bedrock state in a GitHub Gist.
866
868
  *
@@ -1066,6 +1068,38 @@ async function sendPatch(ctx, body) {
1066
1068
  method: "PATCH"
1067
1069
  });
1068
1070
  }
1071
+ async function isFileVisible(ctx, target) {
1072
+ try {
1073
+ const response = await sendGet(ctx);
1074
+ const body = JSON.parse(await response.text());
1075
+ const files = Reflect.get(body, "files");
1076
+ return typeof files === "object" && files !== null && target in files;
1077
+ } catch {
1078
+ return false;
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Polls the gist until the just-written environment file is visible on a
1083
+ * GET, with bounded retries. GitHub's gist API does not guarantee
1084
+ * read-your-write across replicas: a GET issued immediately after a
1085
+ * successful PATCH can return a body that omits the new file. The poll
1086
+ * pre-warms the cache the consumer's next read will hit, so a successful
1087
+ * write honours read-after-write at the port boundary.
1088
+ *
1089
+ * Best-effort: resolves after exhausting the visibility budget regardless
1090
+ * of whether the file became visible. The PATCH already committed; the
1091
+ * poll only narrows the window in which subsequent reads can lag.
1092
+ *
1093
+ * @param ctx - Adapter context carrying the injected fetch and sleep seams.
1094
+ * @param environment - Environment name whose file is being verified.
1095
+ */
1096
+ async function waitForFileVisibility(ctx, environment) {
1097
+ const target = fileName(environment);
1098
+ for (let attempt = 0; attempt < MAX_VISIBILITY_ATTEMPTS; attempt += 1) {
1099
+ if (await isFileVisible(ctx, target)) return;
1100
+ if (attempt < MAX_VISIBILITY_ATTEMPTS - 1) await ctx.sleep(VISIBILITY_BASE_DELAY_MS * 2 ** attempt);
1101
+ }
1102
+ }
1069
1103
  async function writePath(ctx, state) {
1070
1104
  const file = fileLabel(ctx.gistId, state.environment);
1071
1105
  const body = JSON.stringify({ files: { [fileName(state.environment)]: { content: serializeStateFile(state) } } });
@@ -1078,10 +1112,15 @@ async function writePath(ctx, state) {
1078
1112
  success: false
1079
1113
  };
1080
1114
  }
1081
- if (response.ok) return {
1082
- data: void 0,
1083
- success: true
1084
- };
1115
+ if (response.ok) {
1116
+ try {
1117
+ await waitForFileVisibility(ctx, state.environment);
1118
+ } catch {}
1119
+ return {
1120
+ data: void 0,
1121
+ success: true
1122
+ };
1123
+ }
1085
1124
  if (response.status === 422) return stateErr(file, "invalid PATCH body sent to github");
1086
1125
  return {
1087
1126
  err: mapHttpError({
@@ -1357,7 +1396,6 @@ async function publishPlace(deps, desired) {
1357
1396
  * httpClient: universeBodyHttpClient,
1358
1397
  * sleep: async () => {},
1359
1398
  * }),
1360
- * readFile: async () => new Uint8Array(),
1361
1399
  * universes: new UniversesClient({
1362
1400
  * apiKey: "rbx-your-key",
1363
1401
  * httpClient: universeBodyHttpClient,
@@ -1391,29 +1429,22 @@ function createUniverseDriver(deps) {
1391
1429
  return {
1392
1430
  async create(desired) {
1393
1431
  return reconcileUniverse({
1394
- current: void 0,
1395
1432
  deps,
1396
1433
  desired
1397
1434
  });
1398
1435
  },
1399
- async update(current, desired) {
1436
+ async update(_current, desired) {
1400
1437
  return reconcileUniverse({
1401
- current,
1402
1438
  deps,
1403
1439
  desired
1404
1440
  });
1405
1441
  }
1406
1442
  };
1407
1443
  }
1408
- function toCurrentState(inputs) {
1409
- const { desired, iconAssetIds, rootPlaceId } = inputs;
1410
- const baseOutputs = { rootPlaceId: asRobloxAssetId(rootPlaceId) };
1444
+ function toCurrentState(desired, rootPlaceId) {
1411
1445
  return {
1412
1446
  ...desired,
1413
- outputs: iconAssetIds === void 0 ? baseOutputs : {
1414
- ...baseOutputs,
1415
- iconAssetIds
1416
- }
1447
+ outputs: { rootPlaceId: asRobloxAssetId(rootPlaceId) }
1417
1448
  };
1418
1449
  }
1419
1450
  function buildParameters(desired) {
@@ -1457,46 +1488,8 @@ async function resolveUniverse(deps, desired) {
1457
1488
  success: true
1458
1489
  };
1459
1490
  }
1460
- async function captureUploadedIconAssetId(deps, desired) {
1461
- const listed = await deps.universes.icon.list({ universeId: desired.universeId });
1462
- if (!listed.success) return listed;
1463
- const enUs = listed.data.find((entry) => entry.languageCode === "en-us");
1464
- if (enUs === void 0) return {
1465
- err: new ApiError(`Malformed experience-icon list for ${desired.universeId}: en-us entry missing after upload`, { statusCode: 200 }),
1466
- success: false
1467
- };
1468
- return {
1469
- data: { "en-us": asRobloxAssetId(enUs.imageId) },
1470
- success: true
1471
- };
1472
- }
1473
- async function deleteRemovedIcon(deps, desired) {
1474
- return deps.universes.icon.delete({
1475
- languageCode: "en-us",
1476
- universeId: desired.universeId
1477
- });
1478
- }
1479
- async function reconcileIcon(inputs) {
1480
- const { current, deps, desired } = inputs;
1481
- if (desired.icon === void 0) return current?.icon === void 0 ? {
1482
- data: void 0,
1483
- success: true
1484
- } : deleteRemovedIcon(deps, desired);
1485
- if (!shouldReuploadIcon(current?.iconFileHashes, desired.iconFileHashes)) return {
1486
- data: current?.outputs.iconAssetIds,
1487
- success: true
1488
- };
1489
- const bytes = await deps.readFile(desired.icon["en-us"]);
1490
- const uploaded = await deps.universes.icon.upload({
1491
- image: bytes,
1492
- languageCode: "en-us",
1493
- universeId: desired.universeId
1494
- });
1495
- if (!uploaded.success) return uploaded;
1496
- return captureUploadedIconAssetId(deps, desired);
1497
- }
1498
1491
  async function reconcileUniverse(inputs) {
1499
- const { current, deps, desired } = inputs;
1492
+ const { deps, desired } = inputs;
1500
1493
  const universeResult = await resolveUniverse(deps, desired);
1501
1494
  if (!universeResult.success) return universeResult;
1502
1495
  const { rootPlaceId } = universeResult.data;
@@ -1511,18 +1504,8 @@ async function reconcileUniverse(inputs) {
1511
1504
  success: false
1512
1505
  };
1513
1506
  }
1514
- const iconResult = await reconcileIcon({
1515
- current,
1516
- deps,
1517
- desired
1518
- });
1519
- if (!iconResult.success) return iconResult;
1520
1507
  return {
1521
- data: toCurrentState({
1522
- desired,
1523
- iconAssetIds: iconResult.data,
1524
- rootPlaceId
1525
- }),
1508
+ data: toCurrentState(desired, rootPlaceId),
1526
1509
  success: true
1527
1510
  };
1528
1511
  }
@@ -1656,7 +1639,6 @@ const universeEntry = type({
1656
1639
  "displayName?": OPTIONAL_STRING,
1657
1640
  "facebookSocialLink?": socialLinkOrUndefined$1,
1658
1641
  "guildedSocialLink?": socialLinkOrUndefined$1,
1659
- "icon?": iconMap,
1660
1642
  "mobileEnabled?": OPTIONAL_BOOLEAN$2,
1661
1643
  "privateServerPriceRobux?": OPTIONAL_ROBUX_PRICE,
1662
1644
  "robloxGroupSocialLink?": socialLinkOrUndefined$1,
@@ -2004,7 +1986,6 @@ const entrySchema = type({
2004
1986
  "displayName?": "string | undefined",
2005
1987
  "facebookSocialLink?": socialLinkOrUndefined,
2006
1988
  "guildedSocialLink?": socialLinkOrUndefined,
2007
- "icon?": iconMap,
2008
1989
  "mobileEnabled?": OPTIONAL_BOOLEAN,
2009
1990
  "privateServerPriceRobux?": "number.integer >= 0 | undefined",
2010
1991
  "robloxGroupSocialLink?": socialLinkOrUndefined,
@@ -2032,14 +2013,10 @@ function flatten(config) {
2032
2013
  vrEnabled: entry.vrEnabled,
2033
2014
  ...copyDeclaredSocialLinks(entry)
2034
2015
  };
2035
- const withPrice = "privateServerPriceRobux" in entry ? {
2016
+ return ["privateServerPriceRobux" in entry ? {
2036
2017
  ...base,
2037
2018
  privateServerPriceRobux: entry.privateServerPriceRobux
2038
- } : base;
2039
- return [entry.icon === void 0 ? withPrice : {
2040
- ...withPrice,
2041
- icon: entry.icon
2042
- }];
2019
+ } : base];
2043
2020
  }
2044
2021
  function buildBaseDesired(input) {
2045
2022
  const base = {
@@ -2060,23 +2037,9 @@ function buildBaseDesired(input) {
2060
2037
  privateServerPriceRobux: input.privateServerPriceRobux
2061
2038
  } : base;
2062
2039
  }
2063
- async function normalize(input, io) {
2064
- const withPrice = buildBaseDesired(input);
2065
- if (input.icon === void 0) return {
2066
- data: withPrice,
2067
- success: true
2068
- };
2069
- const hashes = await hashIconLocales({
2070
- key: input.key,
2071
- icon: input.icon
2072
- }, io);
2073
- if (!hashes.success) return hashes;
2040
+ async function normalize(input, _io) {
2074
2041
  return {
2075
- data: {
2076
- ...withPrice,
2077
- icon: input.icon,
2078
- iconFileHashes: hashes.data
2079
- },
2042
+ data: buildBaseDesired(input),
2080
2043
  success: true
2081
2044
  };
2082
2045
  }
@@ -2100,7 +2063,6 @@ function fieldsEqual(desired, current) {
2100
2063
  }
2101
2064
  if (desired.displayName !== void 0 && desired.displayName !== current.displayName) return false;
2102
2065
  if ("privateServerPriceRobux" in desired && desired.privateServerPriceRobux !== current.privateServerPriceRobux) return false;
2103
- if (!iconHashesEqual(current.iconFileHashes, desired.iconFileHashes)) return false;
2104
2066
  return declaredSocialLinksEqual(desired, current);
2105
2067
  }
2106
2068
  //#endregion
@@ -2832,7 +2794,7 @@ async function dispatchOp(op, registry) {
2832
2794
  //#region src/shell/build-default-registry.ts
2833
2795
  /**
2834
2796
  * Construct the default `DriverRegistry` from `config.universe.universeId`
2835
- * and `ROBLOX_API_KEY`. Reads the API key via the injected `getEnv` seam
2797
+ * and `BEDROCK_API_KEY`. Reads the API key via the injected `getEnv` seam
2836
2798
  * and surfaces `missingCredential` or `registryConfigMissing` as typed
2837
2799
  * Results instead of throwing.
2838
2800
  *
@@ -2859,7 +2821,7 @@ async function dispatchOp(op, registry) {
2859
2821
  * missing API key or the missing universe declaration.
2860
2822
  */
2861
2823
  function buildDefaultRegistry(deps) {
2862
- const apiKey = deps.getEnv("ROBLOX_API_KEY");
2824
+ const apiKey = deps.getEnv("BEDROCK_API_KEY");
2863
2825
  if (apiKey === void 0) return missingApiKey();
2864
2826
  const rawUniverseId = deps.config.universe?.universeId;
2865
2827
  if (rawUniverseId === void 0) return missingUniverseId();
@@ -2877,7 +2839,7 @@ function missingApiKey() {
2877
2839
  err: {
2878
2840
  kind: "missingCredential",
2879
2841
  purpose: "registry",
2880
- variable: "ROBLOX_API_KEY"
2842
+ variable: "BEDROCK_API_KEY"
2881
2843
  },
2882
2844
  success: false
2883
2845
  };
@@ -2916,7 +2878,6 @@ function assembleRegistry(inputs) {
2916
2878
  }),
2917
2879
  universe: createUniverseDriver({
2918
2880
  places,
2919
- readFile,
2920
2881
  universes
2921
2882
  })
2922
2883
  };
@@ -3057,8 +3018,169 @@ function bootstrapDirectoryPrefix(pid) {
3057
3018
  return `${LUAU_BOOTSTRAP_TEMP_PREFIX}${pid}-`;
3058
3019
  }
3059
3020
  //#endregion
3021
+ //#region src/adapters/lute-luau-evaluator.ts
3022
+ const SENTINEL_BASE = "__BEDROCK_LUAU_";
3023
+ const OK_PREFIX = `${SENTINEL_BASE}OK__`;
3024
+ const ERR_PREFIX = `${SENTINEL_BASE}ERR__`;
3025
+ const LUTE_BOOTSTRAP_LUAU = `--!strict
3026
+ local json = require("@std/json")
3027
+ local process = require("@std/process")
3028
+ local io = require("@std/io")
3029
+
3030
+ local function emit(kind, payload)
3031
+ io.write("${SENTINEL_BASE}" .. kind .. "__")
3032
+ io.write(json.serialize(payload))
3033
+ end
3034
+
3035
+ local userBasename = process.args[2]
3036
+ -- The user file lives in a different directory from this bootstrap, so we
3037
+ -- require it via the @user alias defined in the .luaurc written alongside.
3038
+ local req = "@user/" .. string.gsub(userBasename, "%.luau$", "")
3039
+
3040
+ local loadOk, modOrErr = pcall(require, req)
3041
+ if not loadOk then
3042
+ emit("ERR", { kind = "loadFailed", message = tostring(modOrErr) })
3043
+ return
3044
+ end
3045
+
3046
+ local value = if type(modOrErr) == "function" then modOrErr() else modOrErr
3047
+
3048
+ local encOk, encoded = pcall(json.serialize, value)
3049
+ if not encOk then
3050
+ emit("ERR", { kind = "serializeFailed", message = tostring(encoded) })
3051
+ return
3052
+ end
3053
+
3054
+ io.write("${OK_PREFIX}")
3055
+ io.write(encoded)
3056
+ `;
3057
+ const LUAU_RUNTIME_HINT = "install lute (e.g. `mise install` with `github:luau-lang/lute`) or set BEDROCK_LUTE_PATH to the binary.";
3058
+ function isEnoentError(error) {
3059
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
3060
+ }
3061
+ const LUTE_BOOTSTRAP_TIMEOUT_MS = 5e3;
3062
+ /**
3063
+ * Build the default `LuauEvaluator` adapter that shells out to the `lute`
3064
+ * runtime. Reads `BEDROCK_LUTE_PATH` from `process.env` once per call to pick
3065
+ * the binary, so tests can override it via env var without rebuilding the
3066
+ * adapter.
3067
+ * @returns A `LuauEvaluator` that spawns `lute run` per call.
3068
+ */
3069
+ function createLuteLuauEvaluator() {
3070
+ return evaluateLuauWithLute;
3071
+ }
3072
+ function setupBootstrapDirectory(userCwd) {
3073
+ const bootstrapDirectory = mkdtempSync(join(tmpdir(), bootstrapDirectoryPrefix(process.pid)));
3074
+ writeFileSync(join(bootstrapDirectory, "bootstrap.luau"), LUTE_BOOTSTRAP_LUAU);
3075
+ writeFileSync(join(bootstrapDirectory, ".luaurc"), JSON.stringify({ aliases: { user: userCwd } }));
3076
+ return bootstrapDirectory;
3077
+ }
3078
+ async function runLuteBootstrap(runOptions) {
3079
+ const { bin, bootstrapPath, userBasename } = runOptions;
3080
+ return new Promise((resolve, reject) => {
3081
+ execFile(bin, [
3082
+ "run",
3083
+ bootstrapPath,
3084
+ userBasename
3085
+ ], {
3086
+ encoding: "utf8",
3087
+ timeout: LUTE_BOOTSTRAP_TIMEOUT_MS
3088
+ }, (error, stdout) => {
3089
+ if (error instanceof Error) {
3090
+ reject(error);
3091
+ return;
3092
+ }
3093
+ resolve(stdout);
3094
+ });
3095
+ });
3096
+ }
3097
+ function parseBootstrapOutput(stdout) {
3098
+ if (stdout.startsWith(ERR_PREFIX)) throw new Error(stdout.slice(ERR_PREFIX.length));
3099
+ const parsed = JSON.parse(stdout.slice(OK_PREFIX.length));
3100
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new TypeError("Luau config must return a table at the root");
3101
+ return parsed;
3102
+ }
3103
+ async function evaluateLuauWithLute(absPath) {
3104
+ const overridePath = process.env["BEDROCK_LUTE_PATH"];
3105
+ const lute = overridePath !== void 0 && overridePath.length > 0 ? overridePath : "lute";
3106
+ const bootstrapDirectory = setupBootstrapDirectory(dirname(absPath));
3107
+ try {
3108
+ return {
3109
+ data: parseBootstrapOutput(await runLuteBootstrap({
3110
+ bin: lute,
3111
+ bootstrapPath: join(bootstrapDirectory, "bootstrap.luau"),
3112
+ userBasename: basename(absPath)
3113
+ })),
3114
+ success: true
3115
+ };
3116
+ } catch (err) {
3117
+ if (isEnoentError(err)) return {
3118
+ err: {
3119
+ hint: LUAU_RUNTIME_HINT,
3120
+ kind: "missingRuntime"
3121
+ },
3122
+ success: false
3123
+ };
3124
+ return {
3125
+ err: {
3126
+ kind: "evaluationFailed",
3127
+ message: err instanceof Error ? err.message : String(err)
3128
+ },
3129
+ success: false
3130
+ };
3131
+ } finally {
3132
+ rmSync(bootstrapDirectory, { recursive: true });
3133
+ }
3134
+ }
3135
+ //#endregion
3060
3136
  //#region src/shell/load-config.ts
3061
3137
  /**
3138
+ * Internal entrypoint that lets tests inject a fake `LuauEvaluator`. The
3139
+ * public {@link loadConfig} wraps this with the real lute adapter; the rest
3140
+ * of the loader pipeline is identical.
3141
+ *
3142
+ * @param deps - Injected dependencies. Only the evaluator is configurable.
3143
+ * @param options - Same loader options accepted by {@link loadConfig}.
3144
+ * @returns Same `Result<Config, ConfigError>` shape as `loadConfig`.
3145
+ */
3146
+ async function loadConfigWith(deps, options) {
3147
+ const cwd = options?.cwd ?? process.cwd();
3148
+ const configFile = options?.configFile === void 0 ? void 0 : resolveConfigPath(cwd, options.configFile);
3149
+ if (configFile !== void 0 && !isExistingFile(configFile)) return {
3150
+ err: {
3151
+ kind: "fileNotFound",
3152
+ searchedFrom: cwd
3153
+ },
3154
+ success: false
3155
+ };
3156
+ let resolved;
3157
+ try {
3158
+ resolved = await loadConfig({
3159
+ name: "bedrock",
3160
+ cwd,
3161
+ resolve: makeLuauResolver({
3162
+ callerConfigFile: configFile,
3163
+ defaultCwd: cwd,
3164
+ evaluator: deps.evaluator
3165
+ }),
3166
+ ...configFile === void 0 ? {} : { configFile }
3167
+ });
3168
+ } catch (err) {
3169
+ return {
3170
+ err: attributeLoadError(err, cwd),
3171
+ success: false
3172
+ };
3173
+ }
3174
+ if (resolved._configFile === void 0) return {
3175
+ err: {
3176
+ kind: "fileNotFound",
3177
+ searchedFrom: cwd
3178
+ },
3179
+ success: false
3180
+ };
3181
+ return validateConfig(resolved.config, resolved._configFile);
3182
+ }
3183
+ /**
3062
3184
  * Discover, parse, and validate the project config.
3063
3185
  *
3064
3186
  * Looks for `bedrock.config.{ts,js,mjs,cjs,yaml,yml,json}`, `.bedrockrc*`,
@@ -3098,37 +3220,7 @@ function bootstrapDirectoryPrefix(pid) {
3098
3220
  * ```
3099
3221
  */
3100
3222
  async function loadConfig$1(options) {
3101
- const cwd = options?.cwd ?? process.cwd();
3102
- const configFile = options?.configFile === void 0 ? void 0 : resolveConfigPath(cwd, options.configFile);
3103
- if (configFile !== void 0 && !isExistingFile(configFile)) return {
3104
- err: {
3105
- kind: "fileNotFound",
3106
- searchedFrom: cwd
3107
- },
3108
- success: false
3109
- };
3110
- let resolved;
3111
- try {
3112
- resolved = await loadConfig({
3113
- name: "bedrock",
3114
- cwd,
3115
- resolve: makeLuauResolver(cwd, configFile),
3116
- ...configFile === void 0 ? {} : { configFile }
3117
- });
3118
- } catch (err) {
3119
- return {
3120
- err: attributeLoadError(err, cwd),
3121
- success: false
3122
- };
3123
- }
3124
- if (resolved._configFile === void 0) return {
3125
- err: {
3126
- kind: "fileNotFound",
3127
- searchedFrom: cwd
3128
- },
3129
- success: false
3130
- };
3131
- return validateConfig(resolved.config, resolved._configFile);
3223
+ return loadConfigWith({ evaluator: createLuteLuauEvaluator() }, options);
3132
3224
  }
3133
3225
  function resolveConfigPath(cwd, configFile) {
3134
3226
  return isAbsolute(configFile) ? configFile : join(cwd, configFile);
@@ -3181,6 +3273,19 @@ const NATIVE_CONFIG_EXTENSIONS = [
3181
3273
  "yml"
3182
3274
  ];
3183
3275
  /**
3276
+ * Internal-only wrapper used at the c12 boundary: makeLuauResolver maps an
3277
+ * evaluator `Err` into this throwable, which `attributeLoadError` unwraps
3278
+ * directly. This keeps the port on the `Result` contract per ADR-009 while
3279
+ * still satisfying c12's exception-based `resolve` callback.
3280
+ */
3281
+ var EvaluatorThrow = class extends Error {
3282
+ configError;
3283
+ constructor(configError) {
3284
+ super();
3285
+ this.configError = configError;
3286
+ }
3287
+ };
3288
+ /**
3184
3289
  * Decide which Luau file the resolver should evaluate for a given c12 source,
3185
3290
  * or `undefined` to defer to c12's built-in loaders.
3186
3291
  *
@@ -3199,120 +3304,37 @@ function pickLuauTarget(source, context) {
3199
3304
  if (source === "." && callerConfigFile !== void 0) return callerConfigFile.endsWith(".luau") ? callerConfigFile : void 0;
3200
3305
  return locateLuauConfig(source, cwd);
3201
3306
  }
3202
- function makeLuauResolver(defaultCwd, callerConfigFile) {
3307
+ function evaluationErrorToConfigError(err, sourceFile) {
3308
+ if (err.kind === "missingRuntime") return {
3309
+ hint: err.hint,
3310
+ kind: "luauRuntimeMissing",
3311
+ sourceFile
3312
+ };
3313
+ return {
3314
+ kind: "parseFailed",
3315
+ message: err.message,
3316
+ sourceFile
3317
+ };
3318
+ }
3319
+ function makeLuauResolver(deps) {
3203
3320
  return async (source, c12Options) => {
3204
- const cwd = c12Options.cwd ?? defaultCwd;
3321
+ const cwd = c12Options.cwd ?? deps.defaultCwd;
3205
3322
  const luauPath = pickLuauTarget(source, {
3206
- callerConfigFile,
3323
+ callerConfigFile: deps.callerConfigFile,
3207
3324
  cwd
3208
3325
  });
3209
3326
  if (luauPath === void 0) return;
3327
+ const result = await deps.evaluator(luauPath);
3328
+ if (!result.success) throw new EvaluatorThrow(evaluationErrorToConfigError(result.err, luauPath));
3210
3329
  return {
3211
3330
  _configFile: luauPath,
3212
- config: await evaluateLuauConfig(luauPath),
3331
+ config: result.data,
3213
3332
  configFile: luauPath,
3214
3333
  cwd
3215
3334
  };
3216
3335
  };
3217
3336
  }
3218
3337
  const LUAU_CONFIG_BASENAME = "bedrock.config.luau";
3219
- const SENTINEL_BASE = "__BEDROCK_LUAU_";
3220
- const OK_PREFIX = `${SENTINEL_BASE}OK__`;
3221
- const ERR_PREFIX = `${SENTINEL_BASE}ERR__`;
3222
- const LUTE_BOOTSTRAP_LUAU = `--!strict
3223
- local json = require("@std/json")
3224
- local process = require("@std/process")
3225
- local io = require("@std/io")
3226
-
3227
- local function emit(kind, payload)
3228
- io.write("${SENTINEL_BASE}" .. kind .. "__")
3229
- io.write(json.serialize(payload))
3230
- end
3231
-
3232
- local userBasename = process.args[2]
3233
- -- The user file lives in a different directory from this bootstrap, so we
3234
- -- require it via the @user alias defined in the .luaurc written alongside.
3235
- local req = "@user/" .. string.gsub(userBasename, "%.luau$", "")
3236
-
3237
- local loadOk, modOrErr = pcall(require, req)
3238
- if not loadOk then
3239
- emit("ERR", { kind = "loadFailed", message = tostring(modOrErr) })
3240
- return
3241
- end
3242
-
3243
- local value = if type(modOrErr) == "function" then modOrErr() else modOrErr
3244
-
3245
- local encOk, encoded = pcall(json.serialize, value)
3246
- if not encOk then
3247
- emit("ERR", { kind = "serializeFailed", message = tostring(encoded) })
3248
- return
3249
- end
3250
-
3251
- io.write("${OK_PREFIX}")
3252
- io.write(encoded)
3253
- `;
3254
- var LuauRuntimeMissingError = class extends Error {
3255
- hint;
3256
- name = "LuauRuntimeMissingError";
3257
- sourceFile;
3258
- constructor(sourceFile, hint) {
3259
- super();
3260
- this.hint = hint;
3261
- this.sourceFile = sourceFile;
3262
- }
3263
- };
3264
- const LUAU_RUNTIME_HINT = "install lute (e.g. `mise install` with `github:luau-lang/lute`) or set BEDROCK_LUTE_PATH to the binary.";
3265
- function isEnoentError(error) {
3266
- return error instanceof Error && "code" in error && error.code === "ENOENT";
3267
- }
3268
- const LUTE_BOOTSTRAP_TIMEOUT_MS = 1e4;
3269
- async function runLuteBootstrap(runOptions) {
3270
- const { bin, bootstrapPath, userBasename } = runOptions;
3271
- return new Promise((resolve, reject) => {
3272
- execFile(bin, [
3273
- "run",
3274
- bootstrapPath,
3275
- userBasename
3276
- ], {
3277
- encoding: "utf8",
3278
- timeout: LUTE_BOOTSTRAP_TIMEOUT_MS
3279
- }, (error, stdout) => {
3280
- if (error instanceof Error) {
3281
- reject(error);
3282
- return;
3283
- }
3284
- resolve(stdout);
3285
- });
3286
- });
3287
- }
3288
- function parseBootstrapOutput(stdout) {
3289
- if (stdout.startsWith(ERR_PREFIX)) throw new Error(stdout.slice(ERR_PREFIX.length));
3290
- const parsed = JSON.parse(stdout.slice(OK_PREFIX.length));
3291
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new TypeError("Luau config must return a table at the root");
3292
- return parsed;
3293
- }
3294
- async function evaluateLuauConfig(absPath) {
3295
- const overridePath = process.env["BEDROCK_LUTE_PATH"];
3296
- const lute = overridePath !== void 0 && overridePath.length > 0 ? overridePath : "lute";
3297
- const cwd = dirname(absPath);
3298
- const base = basename(absPath);
3299
- const bootstrapDirectory = mkdtempSync(join(tmpdir(), bootstrapDirectoryPrefix(process.pid)));
3300
- try {
3301
- const bootstrapPath = join(bootstrapDirectory, "bootstrap.luau");
3302
- writeFileSync(bootstrapPath, LUTE_BOOTSTRAP_LUAU);
3303
- writeFileSync(join(bootstrapDirectory, ".luaurc"), JSON.stringify({ aliases: { user: cwd } }));
3304
- return parseBootstrapOutput(await runLuteBootstrap({
3305
- bin: lute,
3306
- bootstrapPath,
3307
- userBasename: base
3308
- }).catch((err) => {
3309
- if (isEnoentError(err)) throw new LuauRuntimeMissingError(absPath, LUAU_RUNTIME_HINT);
3310
- throw err;
3311
- }));
3312
- } finally {
3313
- rmSync(bootstrapDirectory, { recursive: true });
3314
- }
3315
- }
3316
3338
  const CONFIG_FILE_IN_FRAME = /[^\s():"']*bedrock\.config\.(?:ts|js|mjs|cjs|yaml|yml|json)/;
3317
3339
  function extractConfigFileFromStack(err) {
3318
3340
  if (!(err instanceof Error) || err.stack === void 0) return;
@@ -3334,11 +3356,7 @@ function discoverConfigFile(cwd) {
3334
3356
  return match === void 0 ? void 0 : join(cwd, match);
3335
3357
  }
3336
3358
  function attributeLoadError(err, cwd) {
3337
- if (err instanceof LuauRuntimeMissingError) return {
3338
- hint: err.hint,
3339
- kind: "luauRuntimeMissing",
3340
- sourceFile: err.sourceFile
3341
- };
3359
+ if (err instanceof EvaluatorThrow) return err.configError;
3342
3360
  const message = err instanceof Error ? err.message : String(err);
3343
3361
  const frameFile = extractConfigFileFromStack(err);
3344
3362
  if (frameFile !== void 0) return {
@@ -3357,7 +3375,7 @@ function attributeLoadError(err, cwd) {
3357
3375
  /**
3358
3376
  * Run a full reconcile end-to-end. Default-constructs missing deps from
3359
3377
  * the project config and the environment variables `GITHUB_TOKEN` and
3360
- * `ROBLOX_API_KEY`; never reads `process.env` when `statePort`,
3378
+ * `BEDROCK_API_KEY`; never reads `process.env` when `statePort`,
3361
3379
  * `registry`, and `config` are all supplied explicitly.
3362
3380
  *
3363
3381
  * @param options - Target environment plus optional overrides.
@@ -3591,14 +3609,9 @@ async function runReconcile(environment, deps) {
3591
3609
  * the shell from the icon file's bytes) and fall back to the
3592
3610
  * Mantle-recorded hashes when the map omits the key. Product resources
3593
3611
  * without an icon partner omit `icon` and `iconFileHashes` entirely. The
3594
- * universe resource attaches `icon` and `iconFileHashes` only when the
3595
- * fold produced an icon path and the shell supplied a recomputed hash;
3596
- * folds without an experience icon, and folds whose icon file could not
3597
- * be read (the shell emits an `ambiguous` warning), surface a universe
3598
- * resource without those fields. The `outputs` field carries the
3599
- * Mantle-recorded identifiers (universe `rootPlaceId` and optional
3600
- * `iconAssetIds`, place `versionNumber`, pass `assetId` and
3601
- * `iconAssetIds`, product `productId` and optional `iconImageAssetId`).
3612
+ * `outputs` field carries the Mantle-recorded identifiers (universe
3613
+ * `rootPlaceId`, place `versionNumber`, pass `assetId` and `iconAssetIds`,
3614
+ * product `productId` and optional `iconImageAssetId`).
3602
3615
  *
3603
3616
  * @param inputs - Folded data plus recomputed hashes for this environment.
3604
3617
  * @returns A `BedrockState` populated with one resource per folded kind.
@@ -3611,8 +3624,8 @@ function buildState(inputs) {
3611
3624
  };
3612
3625
  }
3613
3626
  function universeResource(inputs) {
3614
- const { entry, iconHashes, outputs } = inputs;
3615
- const base = {
3627
+ const { entry, outputs } = inputs;
3628
+ return {
3616
3629
  key: UNIVERSE_SINGLETON_KEY,
3617
3630
  consoleEnabled: entry.consoleEnabled,
3618
3631
  desktopEnabled: entry.desktopEnabled,
@@ -3625,12 +3638,6 @@ function universeResource(inputs) {
3625
3638
  voiceChatEnabled: entry.voiceChatEnabled,
3626
3639
  vrEnabled: entry.vrEnabled
3627
3640
  };
3628
- if (entry.icon === void 0 || iconHashes === void 0) return base;
3629
- return {
3630
- ...base,
3631
- icon: entry.icon,
3632
- iconFileHashes: iconHashes
3633
- };
3634
3641
  }
3635
3642
  function placeResource(key, fold) {
3636
3643
  return {
@@ -3676,10 +3683,9 @@ function productResource(fold, productIconHashesByKey) {
3676
3683
  };
3677
3684
  }
3678
3685
  function composeResources(inputs) {
3679
- const { folded, passIconHashesByKey, productIconHashesByKey, universeIconHashes } = inputs;
3686
+ const { folded, passIconHashesByKey, productIconHashesByKey } = inputs;
3680
3687
  const universeResources = folded.universe === void 0 ? [] : [universeResource({
3681
3688
  entry: folded.universe.entry,
3682
- iconHashes: universeIconHashes,
3683
3689
  outputs: folded.universe.outputs
3684
3690
  })];
3685
3691
  const placeResources = [...folded.places.entries()].map(([key, entry]) => placeResource(key, entry));
@@ -4304,14 +4310,14 @@ function readPassInputs(raw) {
4304
4310
  price: readPrice$1(raw)
4305
4311
  };
4306
4312
  }
4307
- function coerceRobloxId$4(value) {
4313
+ function coerceRobloxId$3(value) {
4308
4314
  if (typeof value === "string") return value;
4309
4315
  if (Number.isInteger(value)) return String(value);
4310
4316
  }
4311
4317
  function readPassOutputs(raw) {
4312
4318
  if (!isObjectPayload$2(raw)) return;
4313
- const assetId = coerceRobloxId$4(raw["assetId"]);
4314
- const iconAssetId = coerceRobloxId$4(raw["iconAssetId"]);
4319
+ const assetId = coerceRobloxId$3(raw["assetId"]);
4320
+ const iconAssetId = coerceRobloxId$3(raw["iconAssetId"]);
4315
4321
  if (assetId === void 0 || iconAssetId === void 0) return;
4316
4322
  return {
4317
4323
  assetId,
@@ -4358,7 +4364,7 @@ function isObjectPayload$1(value) {
4358
4364
  * @param value - Raw value pulled from a Mantle resource's outputs.
4359
4365
  * @returns The stringified ID, or `undefined` when the value is not a valid wire shape.
4360
4366
  */
4361
- function coerceRobloxId$3(value) {
4367
+ function coerceRobloxId$2(value) {
4362
4368
  if (typeof value === "string") return value;
4363
4369
  if (Number.isInteger(value)) return String(value);
4364
4370
  }
@@ -4632,14 +4638,14 @@ function isStartPlace$1(resource) {
4632
4638
  if (!isObjectPayload$1(resource.inputs)) return false;
4633
4639
  return resource.inputs["isStart"] === true;
4634
4640
  }
4635
- function coerceRobloxId$2(value) {
4641
+ function coerceRobloxId$1(value) {
4636
4642
  if (typeof value === "string") return value;
4637
4643
  if (Number.isInteger(value)) return String(value);
4638
4644
  }
4639
4645
  function readPlaceOutputs(resource) {
4640
4646
  const { outputs } = resource;
4641
4647
  if (!isObjectPayload$1(outputs)) return;
4642
- const assetId = coerceRobloxId$2(outputs["assetId"]);
4648
+ const assetId = coerceRobloxId$1(outputs["assetId"]);
4643
4649
  if (assetId === void 0) return;
4644
4650
  return { assetId };
4645
4651
  }
@@ -4762,7 +4768,7 @@ function readProductInputs(raw) {
4762
4768
  }
4763
4769
  function readProductOutputs(raw) {
4764
4770
  if (!isObjectPayload$1(raw)) return;
4765
- const productId = coerceRobloxId$3(raw["productId"]);
4771
+ const productId = coerceRobloxId$2(raw["productId"]);
4766
4772
  if (productId === void 0) return;
4767
4773
  return { productId };
4768
4774
  }
@@ -4778,7 +4784,7 @@ function readProductIconInputs(raw) {
4778
4784
  }
4779
4785
  function readProductIconOutputs(raw) {
4780
4786
  if (!isObjectPayload$1(raw)) return;
4781
- const assetId = coerceRobloxId$3(raw["assetId"]);
4787
+ const assetId = coerceRobloxId$2(raw["assetId"]);
4782
4788
  if (assetId === void 0) return;
4783
4789
  return { assetId };
4784
4790
  }
@@ -4960,51 +4966,36 @@ function foldDisplayName(resources) {
4960
4966
  //#endregion
4961
4967
  //#region src/core/migrate/fold-experience-icon.ts
4962
4968
  const EXPERIENCE_ICON_KIND = "experienceIcon";
4969
+ const BLOCKED_REASON = "Open Cloud has no route to set a universe's source-language game icon; configure it via the Roblox creator portal.";
4963
4970
  /**
4964
- * Fold the Mantle `experienceIcon_<key>` resource into the universe's
4965
- * locale-keyed `icon` map and the `iconAssetIds` slot of its outputs.
4966
- * Mantle has no locale concept on `experienceIcon`; the fold assigns the
4967
- * single image to `"en-us"` and emits one `interpretive` warning so the
4968
- * migration report records the implicit locale assignment.
4971
+ * Surface every Mantle `experienceIcon_<key>` resource as a `blocked`
4972
+ * migration warning. Bedrock has no `UniverseEntry.icon` field today
4973
+ * because no Open Cloud endpoint accepts a source-language game icon, so
4974
+ * the migrator emits one warning per legacy resource (rather than the
4975
+ * first matching entry only) so the operator can audit each affected
4976
+ * environment before reconfiguring the icon by hand.
4969
4977
  *
4970
4978
  * Resources whose payload is malformed (non-object inputs/outputs,
4971
- * non-string `filePath`, missing or non-coercible `assetId`) drop
4972
- * silently. The first matching resource wins; ambiguity handling for
4973
- * multiple `experienceIcon` resources lands in a follow-up slice.
4979
+ * non-string `filePath`) are skipped silently, matching the
4980
+ * malformed-payload behaviour of the other fold rules.
4974
4981
  *
4975
4982
  * @param resources - Mantle resource list for one environment.
4976
- * @returns The folded icon entry plus per-rule diagnostics.
4983
+ * @returns A fragment whose `warnings` carries one `blocked` entry per
4984
+ * legacy experience-icon resource, or {@link EMPTY_FRAGMENT} when none
4985
+ * are present.
4977
4986
  */
4978
4987
  function foldExperienceIcon(resources) {
4979
- const [first] = resources.filter((resource) => resource.kind === EXPERIENCE_ICON_KIND);
4980
- if (first === void 0) return EMPTY_FRAGMENT;
4981
- const parts = readParts(first);
4982
- if (parts === void 0) return EMPTY_FRAGMENT;
4988
+ const warnings = resources.filter((resource) => resource.kind === EXPERIENCE_ICON_KIND && hasReadablePayload(resource)).map((resource) => blockedWarning(`${EXPERIENCE_ICON_KIND}_${resource.key}`, BLOCKED_REASON));
4989
+ if (warnings.length === 0) return EMPTY_FRAGMENT;
4983
4990
  return {
4984
- entryFragment: { icon: { "en-us": parts.filePath } },
4985
- outputsFragment: { iconAssetIds: { "en-us": parts.assetId } },
4986
- warnings: [interpretiveWarning({
4987
- bedrockPath: "universe.icon",
4988
- mantlePath: `${EXPERIENCE_ICON_KIND}_${first.key}`,
4989
- rule: "experience-icon-to-en-us-locale"
4990
- })]
4991
+ entryFragment: {},
4992
+ warnings
4991
4993
  };
4992
4994
  }
4993
- function coerceRobloxId$1(value) {
4994
- const candidate = String(value);
4995
- return isRobloxAssetId(candidate) ? candidate : void 0;
4996
- }
4997
- function readParts(resource) {
4998
- if (!isObjectPayload$1(resource.inputs)) return;
4995
+ function hasReadablePayload(resource) {
4996
+ if (!isObjectPayload$1(resource.inputs)) return false;
4999
4997
  const { filePath } = resource.inputs;
5000
- if (typeof filePath !== "string") return;
5001
- if (!isObjectPayload$1(resource.outputs)) return;
5002
- const assetId = coerceRobloxId$1(resource.outputs["assetId"]);
5003
- if (assetId === void 0) return;
5004
- return {
5005
- assetId,
5006
- filePath
5007
- };
4998
+ return typeof filePath === "string";
5008
4999
  }
5009
5000
  //#endregion
5010
5001
  //#region src/core/migrate/fold-social-links.ts
@@ -5480,20 +5471,17 @@ function summarizeWarnings(warnings) {
5480
5471
  //#endregion
5481
5472
  //#region src/shell/recompute-icon-hashes.ts
5482
5473
  /**
5483
- * Walk each environment's folded pass, product, and universe entries,
5484
- * resolve the locale-keyed icon paths against `stateFileDirectory`, read
5485
- * the bytes via the injected `readFile`, and compute the SHA-256 hex
5486
- * digest. Files that cannot be read surface as `ambiguous`
5487
- * `MigrationWarning`s with the environment-prefixed `mantlePath` and a
5488
- * hint pointing at the resolved path; the caller carries the
5489
- * Mantle-recorded hashes forward as a fallback for passes and products,
5490
- * and omits the icon entirely on the universe resource. Products without
5491
- * an icon partner and environments without an experience icon are
5492
- * silently skipped.
5474
+ * Walk each environment's folded pass and product entries, resolve the
5475
+ * locale-keyed icon paths against `stateFileDirectory`, read the bytes via
5476
+ * the injected `readFile`, and compute the SHA-256 hex digest. Files that
5477
+ * cannot be read surface as `ambiguous` `MigrationWarning`s with the
5478
+ * environment-prefixed `mantlePath` and a hint pointing at the resolved
5479
+ * path; the caller carries the Mantle-recorded hashes forward as a
5480
+ * fallback. Products without an icon partner are silently skipped.
5493
5481
  *
5494
5482
  * @param inputs - Per-environment fold results plus I/O dependencies.
5495
- * @returns Per-environment recomputed pass, product, and universe hashes
5496
- * plus accumulated ambiguous warnings.
5483
+ * @returns Per-environment recomputed pass and product hashes plus
5484
+ * accumulated ambiguous warnings.
5497
5485
  */
5498
5486
  async function recomputeIconHashes(inputs) {
5499
5487
  return collectRecomputation(await Promise.all([...inputs.folds.entries()].map(async ([environment, folded]) => {
@@ -5509,9 +5497,6 @@ function collectRecomputation(walked) {
5509
5497
  return {
5510
5498
  passHashesByEnvironment: new Map(walked.map(([environment, walk]) => [environment, walk.passHashes])),
5511
5499
  productHashesByEnvironment: new Map(walked.map(([environment, walk]) => [environment, walk.productHashes])),
5512
- universeHashByEnvironment: new Map(walked.flatMap(([environment, walk]) => {
5513
- return walk.universeHash === void 0 ? [] : [[environment, walk.universeHash]];
5514
- })),
5515
5500
  warnings: walked.flatMap(([, walk]) => walk.warnings)
5516
5501
  };
5517
5502
  }
@@ -5562,27 +5547,6 @@ async function walkIconEntries(inputs) {
5562
5547
  warnings
5563
5548
  };
5564
5549
  }
5565
- async function walkUniverseIcon(inputs) {
5566
- const iconPath = inputs.folded.universe?.entry.icon?.["en-us"];
5567
- if (iconPath === void 0) return {
5568
- hash: void 0,
5569
- warnings: []
5570
- };
5571
- const resolved = join(inputs.stateFileDirectory, iconPath);
5572
- const recomputed = await tryRecomputeHash(inputs.readFile, resolved);
5573
- if (recomputed === void 0) return {
5574
- hash: void 0,
5575
- warnings: [buildAmbiguousIconWarning({
5576
- environmentName: inputs.environmentName,
5577
- mantlePath: "experienceIcon_singleton",
5578
- resolvedPath: resolved
5579
- })]
5580
- };
5581
- return {
5582
- hash: { "en-us": recomputed },
5583
- warnings: []
5584
- };
5585
- }
5586
5550
  async function walkEnvironment(inputs) {
5587
5551
  const passWalk = await walkIconEntries({
5588
5552
  entries: inputs.folded.passes.map(passWalkEntry),
@@ -5596,21 +5560,10 @@ async function walkEnvironment(inputs) {
5596
5560
  readFile: inputs.readFile,
5597
5561
  stateFileDirectory: inputs.stateFileDirectory
5598
5562
  });
5599
- const universeWalk = await walkUniverseIcon({
5600
- environmentName: inputs.environmentName,
5601
- folded: inputs.folded,
5602
- readFile: inputs.readFile,
5603
- stateFileDirectory: inputs.stateFileDirectory
5604
- });
5605
5563
  return {
5606
5564
  passHashes: passWalk.perKey,
5607
5565
  productHashes: productWalk.perKey,
5608
- universeHash: universeWalk.hash,
5609
- warnings: [
5610
- ...passWalk.warnings,
5611
- ...productWalk.warnings,
5612
- ...universeWalk.warnings
5613
- ]
5566
+ warnings: [...passWalk.warnings, ...productWalk.warnings]
5614
5567
  };
5615
5568
  }
5616
5569
  //#endregion
@@ -5705,8 +5658,7 @@ function buildStatesByEnvironment(inputs) {
5705
5658
  environment: name,
5706
5659
  folded,
5707
5660
  passIconHashesByKey: inputs.passHashesByEnvironment.get(name) ?? EMPTY_HASHES,
5708
- productIconHashesByKey: inputs.productHashesByEnvironment.get(name) ?? EMPTY_HASHES,
5709
- universeIconHashes: inputs.universeHashByEnvironment.get(name)
5661
+ productIconHashesByKey: inputs.productHashesByEnvironment.get(name) ?? EMPTY_HASHES
5710
5662
  })];
5711
5663
  }));
5712
5664
  }
@@ -5722,7 +5674,7 @@ function collectFoldWarnings(folds) {
5722
5674
  });
5723
5675
  }
5724
5676
  function buildReport(inputs, validated) {
5725
- const { passHashesByEnvironment, productHashesByEnvironment, universeHashByEnvironment, warnings: iconWarnings } = inputs.iconRecomputation;
5677
+ const { passHashesByEnvironment, productHashesByEnvironment, warnings: iconWarnings } = inputs.iconRecomputation;
5726
5678
  const warnings = [
5727
5679
  ...collectFoldWarnings(inputs.folds),
5728
5680
  ...inputs.factorizeWarnings,
@@ -5737,8 +5689,7 @@ function buildReport(inputs, validated) {
5737
5689
  statesByEnvironment: buildStatesByEnvironment({
5738
5690
  folds: inputs.folds,
5739
5691
  passHashesByEnvironment,
5740
- productHashesByEnvironment,
5741
- universeHashByEnvironment
5692
+ productHashesByEnvironment
5742
5693
  }),
5743
5694
  summary: summarizeWarnings(warnings),
5744
5695
  warnings
@@ -5786,4 +5737,4 @@ function isFileMissing(err) {
5786
5737
  //#endregion
5787
5738
  export { asResourceKey as A, createGistStateAdapter as C, createGamePassDriver as D, validateEnvironmentName as E, isSha256Hex as F, derivePriceFields as I, asSha256Hex as M, isResourceKey as N, createDeveloperProductDriver as O, isRobloxAssetId as P, UNIVERSE_SINGLETON_KEY as S, serializeStateFile as T, isGistStateConfig as _, buildStatePort as a, createPlaceDriver as b, applyOps as c, resolveStateConfig as d, flattenConfig as f, defaultKindRegistry as g, diff as h, loadConfig$1 as i, asRobloxAssetId as j, shouldReuploadIcon as k, validatePlan as l, renderDisplayNamePrefix as m, serializeConfig as n, buildDesired as o, DEFAULT_PREFIX_FORMAT as p, deploy as r, buildDefaultRegistry as s, migrateMantleState as t, selectEnvironment as u, validateConfig as v, parseStateFile as w, SOCIAL_LINK_FIELDS as x, createUniverseDriver as y };
5788
5739
 
5789
- //# sourceMappingURL=migrate-mantle-state-DqbJ1TLq.mjs.map
5740
+ //# sourceMappingURL=migrate-mantle-state-DjAU-I39.mjs.map